{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://shop.example.com/cdn/shop/files/pdp-layout-schema.json",
  "title": "Kairos Dynamic PDP Layout",
  "description": "Contract for the kairos.pdp_layout JSON metafield. An external app writes one object per product; the Kairos theme dispatcher (dynamic-pdp-renderer) reads it and renders the editorial PDP stack below the buy-box. All design (colors, typography, padding) is owned by the theme. The payload carries content + a small number of structural flags only.",
  "type": "object",
  "required": ["schema_version", "sections"],
  "additionalProperties": false,
  "properties": {
    "schema_version": {
      "description": "SemVer of this payload's contract. The renderer compares major version: same major = full render; different major = renderer skips unknown section types gracefully and emits an HTML comment for each skipped entry.",
      "type": "string",
      "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
    },
    "sections": {
      "description": "Ordered array of section entries. Render order = array order. Same `type` may appear N times.",
      "type": "array",
      "items": { "$ref": "#/$defs/sectionEntry" }
    }
  },
  "$defs": {
    "imageRef": {
      "description": "Shopify image reference. Accepts a MediaImage GID returned by the Files API OR a shop-image handle as used by the theme editor's image picker. Both forms resolve through Liquid's image_url filter.",
      "type": "string",
      "pattern": "^(gid://shopify/(MediaImage|GenericFile|ProductImage)/[0-9]+|shopify://shop_images/[A-Za-z0-9._-]+|https?://[^\\s]+)$"
    },
    "videoRef": {
      "description": "Shopify video reference (MediaImage GID or external URL).",
      "type": "string",
      "pattern": "^(gid://shopify/(Video|GenericFile)/[0-9]+|https?://[^\\s]+)$"
    },
    "richtext": {
      "description": "HTML-allowed rich text. The renderer trusts the external app to provide safe HTML; sanitize at the boundary in the app.",
      "type": "string"
    },
    "sectionEntry": {
      "description": "One section entry. `type` selects the per-type renderer snippet; `data` carries that type's content payload.",
      "type": "object",
      "required": ["type", "data"],
      "additionalProperties": false,
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "image-with-text",
            "statistics-column",
            "cards",
            "roadmap",
            "product-benefits",
            "customer-reviews",
            "before-after",
            "steps",
            "product-comparison",
            "store-faq",
            "image-reviews"
          ]
        },
        "id": {
          "description": "Optional external-app-stable identifier for debugging and partial-update tracking.",
          "type": "string"
        },
        "data": { "type": "object" }
      },
      "allOf": [
        { "if": { "properties": { "type": { "const": "image-with-text" } } },           "then": { "properties": { "data": { "$ref": "#/$defs/imageWithTextData" } } } },
        { "if": { "properties": { "type": { "const": "statistics-column" } } },         "then": { "properties": { "data": { "$ref": "#/$defs/statisticsColumnData" } } } },
        { "if": { "properties": { "type": { "const": "cards" } } },                     "then": { "properties": { "data": { "$ref": "#/$defs/cardsData" } } } },
        { "if": { "properties": { "type": { "const": "roadmap" } } },                   "then": { "properties": { "data": { "$ref": "#/$defs/roadmapData" } } } },
        { "if": { "properties": { "type": { "const": "product-benefits" } } },          "then": { "properties": { "data": { "$ref": "#/$defs/productBenefitsData" } } } },
        { "if": { "properties": { "type": { "const": "customer-reviews" } } },          "then": { "properties": { "data": { "$ref": "#/$defs/customerReviewsData" } } } },
        { "if": { "properties": { "type": { "const": "before-after" } } },              "then": { "properties": { "data": { "$ref": "#/$defs/beforeAfterData" } } } },
        { "if": { "properties": { "type": { "const": "steps" } } },                     "then": { "properties": { "data": { "$ref": "#/$defs/stepsData" } } } },
        { "if": { "properties": { "type": { "const": "product-comparison" } } },        "then": { "properties": { "data": { "$ref": "#/$defs/productComparisonData" } } } },
        { "if": { "properties": { "type": { "const": "store-faq" } } },                 "then": { "properties": { "data": { "$ref": "#/$defs/storeFaqData" } } } },
        { "if": { "properties": { "type": { "const": "image-reviews" } } },             "then": { "properties": { "data": { "$ref": "#/$defs/imageReviewsData" } } } }
      ]
    },

    "imageWithTextData": {
      "description": "Image-with-text section (3.1). One image (or video) paired with a text column. Renders nothing if both `heading` and `paragraph` are empty AND `image` is absent.",
      "type": "object",
      "additionalProperties": false,
      "required": [],
      "properties": {
        "heading":         { "type": "string" },
        "heading_accent":  { "type": "string", "description": "Optional accent fragment appended to heading with theme accent styling." },
        "subtitle":        { "type": "string" },
        "paragraph":       { "$ref": "#/$defs/richtext" },
        "image":           { "$ref": "#/$defs/imageRef" },
        "video":           { "$ref": "#/$defs/videoRef", "description": "If both image and video are set, video wins on the media column." },
        "image_position":  { "type": "string", "enum": ["left", "right"], "default": "left" },
        "bullets":         { "type": "array", "items": { "type": "string" }, "description": "Optional bullet list rendered below paragraph." },
        "cta":             {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "label": { "type": "string" },
            "url":   { "type": "string" }
          },
          "required": ["label", "url"]
        }
      }
    },

    "statisticsColumnData": {
      "description": "Statistics section (3.2). Heading + ordered list of statistic items + optional image and disclaimer. Renders nothing if `stats` is empty.",
      "type": "object",
      "additionalProperties": false,
      "required": ["stats"],
      "properties": {
        "heading":        { "type": "string" },
        "heading_accent": { "type": "string" },
        "subtitle":       { "$ref": "#/$defs/richtext" },
        "image":          { "$ref": "#/$defs/imageRef" },
        "image_position": { "type": "string", "enum": ["left", "right"], "default": "right" },
        "disclaimer":     { "$ref": "#/$defs/richtext" },
        "stats": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["number", "title"],
            "properties": {
              "number":      { "type": "string", "description": "Display value, e.g. \"95%\", \"24/7\", \"3 of 4\". The renderer attempts to animate the leading numeric portion." },
              "title":       { "type": "string" },
              "description": { "type": "string" }
            }
          }
        }
      }
    },

    "cardsData": {
      "description": "Cards section (3.3). Grid of cards, each with image/title/description/bullets. Renders nothing if `cards` is empty.",
      "type": "object",
      "additionalProperties": false,
      "required": ["cards"],
      "properties": {
        "heading":         { "type": "string" },
        "heading_accent":  { "type": "string" },
        "subtitle":        { "$ref": "#/$defs/richtext" },
        "columns_desktop": { "type": "integer", "minimum": 2, "maximum": 4, "default": 4 },
        "cards": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["title"],
            "properties": {
              "image":       { "$ref": "#/$defs/imageRef" },
              "title":       { "type": "string" },
              "description": { "$ref": "#/$defs/richtext" },
              "bullets":     { "type": "array", "items": { "type": "string" } }
            }
          }
        }
      }
    },

    "roadmapData": {
      "description": "Roadmap section (3.4). Ordered sequence of steps, each with badge, title, description, optional icon. Renders nothing if `steps` is empty.",
      "type": "object",
      "additionalProperties": false,
      "required": ["steps"],
      "properties": {
        "heading":        { "type": "string" },
        "heading_accent": { "type": "string" },
        "subtitle":       { "$ref": "#/$defs/richtext" },
        "steps": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["title"],
            "properties": {
              "badge":       { "type": "string", "description": "Short label, e.g. \"Day 1\" or \"Step 1\"." },
              "title":       { "type": "string" },
              "description": { "$ref": "#/$defs/richtext" },
              "icon_image":  { "$ref": "#/$defs/imageRef" },
              "icon_svg":    { "type": "string", "description": "Raw inline SVG markup. Trust boundary in the external app." }
            }
          }
        }
      }
    },

    "productBenefitsData": {
      "description": "Product benefits section (3.5). Icon-bullet list paired with image or video. Renders nothing if `benefits` is empty.",
      "type": "object",
      "additionalProperties": false,
      "required": ["benefits"],
      "properties": {
        "heading":        { "type": "string" },
        "heading_accent": { "type": "string" },
        "subtitle":       { "$ref": "#/$defs/richtext" },
        "image":          { "$ref": "#/$defs/imageRef" },
        "video":          { "$ref": "#/$defs/videoRef" },
        "image_position": { "type": "string", "enum": ["left", "right"], "default": "left" },
        "benefits": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["title"],
            "properties": {
              "title":       { "type": "string" },
              "description": { "$ref": "#/$defs/richtext" },
              "icon_image":  { "$ref": "#/$defs/imageRef" },
              "icon_svg":    { "type": "string" }
            }
          }
        }
      }
    },

    "customerReviewsData": {
      "description": "Customer reviews carousel section (3.6). Full reviews with text + reviewer name + rating. Renders nothing if `reviews` is empty.",
      "type": "object",
      "additionalProperties": false,
      "required": ["reviews"],
      "properties": {
        "heading":        { "type": "string" },
        "heading_accent": { "type": "string" },
        "subtitle":       { "$ref": "#/$defs/richtext" },
        "reviews": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["text", "reviewer_name"],
            "properties": {
              "image":         { "$ref": "#/$defs/imageRef" },
              "rating":        { "type": "number", "minimum": 0, "maximum": 5, "default": 5 },
              "text":          { "$ref": "#/$defs/richtext" },
              "reviewer_name": { "type": "string" },
              "reviewer_title":{ "type": "string" }
            }
          }
        }
      }
    },

    "beforeAfterData": {
      "description": "Before/after slider section (3.7). One or more pairs. Renders nothing if `pairs` is empty.",
      "type": "object",
      "additionalProperties": false,
      "required": ["pairs"],
      "properties": {
        "heading":        { "type": "string" },
        "heading_accent": { "type": "string" },
        "subtitle":       { "$ref": "#/$defs/richtext" },
        "pairs": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["before_image", "after_image"],
            "properties": {
              "category":     { "type": "string", "description": "Optional grouping label shown above the pair." },
              "before_image": { "$ref": "#/$defs/imageRef" },
              "after_image":  { "$ref": "#/$defs/imageRef" },
              "before_label": { "type": "string", "default": "Before" },
              "after_label":  { "type": "string", "default": "After" }
            }
          }
        }
      }
    },

    "stepsData": {
      "description": "Steps/results section (3.8). Each step has image, icon, title, description. Renders nothing if `steps` is empty.",
      "type": "object",
      "additionalProperties": false,
      "required": ["steps"],
      "properties": {
        "heading":        { "type": "string" },
        "heading_accent": { "type": "string" },
        "subtitle":       { "$ref": "#/$defs/richtext" },
        "steps": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["title"],
            "properties": {
              "image":       { "$ref": "#/$defs/imageRef" },
              "icon_svg":    { "type": "string" },
              "title":       { "type": "string" },
              "description": { "$ref": "#/$defs/richtext" }
            }
          }
        }
      }
    },

    "productComparisonData": {
      "description": "Product comparison table (3.9). `columns` are products being compared (the first column is conventionally 'our product'). `features` are rows. Each `cells` array MUST be the same length as `columns`. Renders nothing if `columns` is empty or `features` is empty.",
      "type": "object",
      "additionalProperties": false,
      "required": ["columns", "features"],
      "properties": {
        "heading":        { "type": "string" },
        "heading_accent": { "type": "string" },
        "subtitle":       { "$ref": "#/$defs/richtext" },
        "columns": {
          "type": "array",
          "minItems": 2,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["label"],
            "properties": {
              "label":     { "type": "string" },
              "image":     { "$ref": "#/$defs/imageRef" },
              "highlight": { "type": "boolean", "default": false, "description": "Mark column as 'our product' for visual emphasis." }
            }
          }
        },
        "features": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["label", "cells"],
            "properties": {
              "label": { "type": "string" },
              "cells": {
                "type": "array",
                "minItems": 2,
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "properties": {
                    "value":    { "type": ["string", "boolean", "null"], "description": "Display text. true/false render as check/cross." },
                    "icon_svg": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },

    "storeFaqData": {
      "description": "FAQ section (3.10). Merges with shop.metafields.kairos.faq_default. Merge rule: mode=append (default) concatenates shop FAQs then product items; mode=replace uses only product items. Renders nothing if final merged item list is empty AND heading is blank.",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "heading":        { "type": "string" },
        "heading_accent": { "type": "string" },
        "subtitle":       { "$ref": "#/$defs/richtext" },
        "mode":           { "type": "string", "enum": ["append", "replace"], "default": "append" },
        "items": {
          "type": "array",
          "items": { "$ref": "#/$defs/faqItem" }
        }
      }
    },

    "faqItem": {
      "type": "object",
      "additionalProperties": false,
      "required": ["question", "answer"],
      "properties": {
        "question": { "type": "string" },
        "answer":   { "$ref": "#/$defs/richtext" },
        "image":    { "$ref": "#/$defs/imageRef" }
      }
    },

    "imageReviewsData": {
      "description": "Image-only review tiles section (3.11). Renders nothing if `images` is empty.",
      "type": "object",
      "additionalProperties": false,
      "required": ["images"],
      "properties": {
        "heading":        { "type": "string" },
        "heading_accent": { "type": "string" },
        "subtitle":       { "$ref": "#/$defs/richtext" },
        "images": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["image"],
            "properties": {
              "image":   { "$ref": "#/$defs/imageRef" },
              "caption": { "type": "string" }
            }
          }
        }
      }
    },

    "shopFaqDefault": {
      "description": "Schema for shop.metafields.kairos.faq_default (SHOP-owner JSON metafield). Used as the base list merged by store-faq entries.",
      "type": "array",
      "items": { "$ref": "#/$defs/faqItem" }
    }
  }
}
