{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://flarebuilder.com/schemas/template-v10.0.json",
  "title": "FlareBuilder Template Schema v10.0",
  "description": "Schema v10.0: schemaVersion (camelCase) replaces schema_version; richText/contentJson/oneOf replace rich_text/content_json/one-of; props replaces config on field definitions; props.types replaces config.templates on oneOf; props.outputFormat replaces config.output_format; section.fields replaces section.items; field-level validation rules use 'constraints' (not 'validations'); title-level rules continue to use 'validations'.",
  "type": "object",
  "properties": {
    "schemaVersion": {
      "type": "string",
      "enum": ["10.0"],
      "description": "Schema version identifier, must be '10.0' for this format"
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100,
      "description": "Template display name shown to users"
    },
    "description": {
      "type": "string",
      "description": "Template description shown at the top of the content editor (supports markdown)"
    },
    "is_type": {
      "type": "boolean",
      "default": false,
      "description": "When true, marks this template as usable as a type in oneOf fields. Type templates cannot contain oneOf fields themselves."
    },
    "indexes": {
      "type": "object",
      "description": "Maps search index columns to field paths (sectionId.fieldId). Used to make content filterable via the Feed API. Supported keys: _event_start, _event_end, _geo, _search_text. _search_text accepts a single path or an array of paths.",
      "properties": {
        "_event_start": {
          "type": "string",
          "description": "Path to a datetime field used for event_start / event_end feed filtering (e.g. 'schedule.eventStart')"
        },
        "_event_end": {
          "type": "string",
          "description": "Path to a datetime field used for event_start / event_end feed filtering (e.g. 'schedule.eventEnd')"
        },
        "_geo": {
          "type": "string",
          "description": "Path to a location field used for geobox feed filtering (e.g. 'location.venueCoords')"
        },
        "_search_text": {
          "oneOf": [
            { "type": "string" },
            { "type": "array", "items": { "type": "string" }, "minItems": 1 }
          ],
          "description": "Path or array of paths to text/multiline fields indexed for full-text search"
        }
      },
      "additionalProperties": false
    },
    "title": {
      "type": "object",
      "description": "Top-level title field configuration (required for all content)",
      "properties": {
        "label": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100,
          "description": "Display label for the title field"
        },
        "props": {
          "$ref": "#/definitions/TitleProps"
        },
        "validations": {
          "type": "object",
          "description": "Validation rules for the title field",
          "properties": {
            "required": { "$ref": "#/definitions/RequiredValidation" },
            "maxLength": { "$ref": "#/definitions/MaxLengthValidation" },
            "pattern": { "$ref": "#/definitions/PatternValidation" }
          },
          "additionalProperties": false
        }
      },
      "required": ["label"],
      "additionalProperties": true
    },
    "sections": {
      "type": "array",
      "description": "Array of sections containing related fields (title is NOT included here)",
      "minItems": 1,
      "items": {
        "$ref": "#/definitions/TemplateSection"
      }
    }
  },
  "required": ["schemaVersion", "name", "title", "sections"],
  "additionalProperties": false,
  "definitions": {
    "TitleProps": {
      "type": "object",
      "description": "Title presentation settings",
      "properties": {
        "description": {
          "type": "string",
          "description": "Help text shown below the title input"
        },
        "placeholder": {
          "type": "string",
          "description": "Placeholder text shown in empty title field"
        },
        "color": {
          "type": "string",
          "pattern": "^#[0-9A-Fa-f]{6}$",
          "description": "Hex color for title visual identification (e.g., '#e8ca93')"
        }
      },
      "additionalProperties": false
    },
    "SectionConfig": {
      "type": "object",
      "description": "Section presentation settings (sections use 'config', not 'props')",
      "properties": {
        "color": {
          "type": "string",
          "pattern": "^#[0-9A-Fa-f]{6}$",
          "description": "Hex color for section visual identification (e.g., '#3F51B5')"
        },
        "collapsible": {
          "type": "boolean",
          "default": true,
          "description": "Whether the section can be collapsed in the content editor. Default: true"
        }
      },
      "additionalProperties": false
    },
    "FieldHelper": {
      "type": "object",
      "description": "URL generation helper (link and text types only). Adds a button next to the field that fills it with a generated value.",
      "properties": {
        "label": {
          "type": "string",
          "description": "Button label for helper (e.g., 'Map', 'Wikipedia')"
        },
        "template": {
          "type": "string",
          "description": "URL template with {{ $field_id | @transform }} expression syntax"
        }
      },
      "required": ["label", "template"],
      "additionalProperties": false
    },
    "ConditionRule": {
      "type": "object",
      "description": "A single condition rule — show element only when the field matches the operator/value",
      "properties": {
        "type": {
          "type": "string",
          "const": "rule",
          "description": "Node type discriminator, must be 'rule'"
        },
        "field": {
          "type": "string",
          "description": "ID of the field to evaluate"
        },
        "operator": {
          "type": "string",
          "enum": [
            "contains",
            "notContains",
            "in",
            "exists",
            "notExists",
            "equals",
            "notEquals",
            "lessThan",
            "greaterThan",
            "startsWith"
          ],
          "description": "Comparison operator. contains/notContains: whole-word match (case-insensitive). in: value is one of array. exists/notExists: presence check. equals/notEquals: value equality with type coercion. lessThan/greaterThan: numeric. startsWith: prefix match (case-insensitive)."
        },
        "value": {
          "oneOf": [
            { "type": "string" },
            { "type": "number" },
            { "type": "boolean" },
            { "type": "array" },
            { "type": "null" }
          ],
          "description": "Value to compare against. Not used for 'exists' and 'notExists' operators. Must be an array for 'in'."
        }
      },
      "required": ["type", "field", "operator"],
      "additionalProperties": false
    },
    "ConditionRuleset": {
      "type": "object",
      "description": "A compound condition combining multiple rules or nested rulesets with a logical operator. Maximum nesting depth: 3.",
      "properties": {
        "type": {
          "type": "string",
          "const": "ruleset",
          "description": "Node type discriminator, must be 'ruleset'"
        },
        "combinator": {
          "type": "string",
          "enum": ["&&", "||"],
          "description": "Logical combinator: '&&' (all children must match) or '||' (any child must match)"
        },
        "children": {
          "type": "array",
          "description": "Child condition nodes (rules or nested rulesets)",
          "minItems": 1,
          "items": {
            "$ref": "#/definitions/ConditionNode"
          }
        }
      },
      "required": ["type", "combinator", "children"],
      "additionalProperties": false
    },
    "ConditionNode": {
      "description": "A condition AST node — either a single rule or a ruleset combining multiple rules",
      "oneOf": [
        { "$ref": "#/definitions/ConditionRule" },
        { "$ref": "#/definitions/ConditionRuleset" }
      ]
    },
    "Conditionals": {
      "type": "object",
      "description": "Logic and conditional behavior",
      "properties": {
        "visibility": {
          "$ref": "#/definitions/ConditionNode",
          "description": "AST condition controlling element visibility. Element is shown when the condition evaluates to true."
        }
      },
      "additionalProperties": false
    },
    "TemplateSection": {
      "type": "object",
      "description": "A section grouping related fields together",
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_]*$",
          "description": "Section identifier (lowercase, alphanumeric + underscores)"
        },
        "label": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100,
          "description": "Display label for the section shown in the editor"
        },
        "description": {
          "type": "string",
          "description": "Section description shown to users in the content editor (supports markdown)"
        },
        "config": {
          "$ref": "#/definitions/SectionConfig"
        },
        "conditionals": {
          "$ref": "#/definitions/Conditionals"
        },
        "fields": {
          "type": "array",
          "description": "Array of fields and groups within this section",
          "items": {
            "$ref": "#/definitions/TemplateField"
          }
        }
      },
      "required": ["id", "label", "fields"],
      "additionalProperties": true
    },
    "TemplateField": {
      "type": "object",
      "description": "Individual field or group configuration. Identity goes in root properties (id, type, label). Data behavior goes in 'props'. Validation rules go in 'constraints'. Conditional logic goes in 'conditionals'.",
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_]*$",
          "description": "Field identifier (lowercase, alphanumeric + underscores)"
        },
        "type": {
          "type": "string",
          "enum": [
            "text", "multiline", "richText", "contentJson",
            "image", "media", "date", "datetime",
            "integer", "decimal", "boolean", "select",
            "link", "location", "group", "oneOf", "reference"
          ],
          "description": "Field data type"
        },
        "label": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100,
          "description": "Display label shown to users when editing"
        },
        "description": {
          "type": "string",
          "description": "Help text shown below the field in the content editor"
        },
        "placeholder": {
          "type": "string",
          "description": "Placeholder text shown inside empty inputs (text, multiline, link fields)"
        },
        "repeatable": {
          "type": "boolean",
          "description": "When true on a group field, allows editors to add multiple instances. Use with constraints.minItems / constraints.maxItems to set bounds."
        },
        "props": {
          "type": "object",
          "description": "Type-specific data behavior. Valid keys depend on field type: outputFormat (richText/contentJson), types (oneOf), options (select), allowedTemplates (reference), inline (group), includeAltitude (location), defaultValue (most types).",
          "properties": {
            "outputFormat": {
              "type": "string",
              "enum": ["html", "json"],
              "description": "Output format for richText and contentJson fields. 'html' (default) renders ProseMirror to HTML; 'json' returns raw ProseMirror JSON."
            },
            "types": {
              "type": "array",
              "description": "Array of type-template ID strings (oneOf type only). Each ID must reference a template with is_type=true.",
              "minItems": 1,
              "items": { "type": "string" }
            },
            "options": {
              "type": "array",
              "description": "Array of string options for select fields.",
              "minItems": 1,
              "items": { "type": "string" }
            },
            "allowedTemplates": {
              "type": "array",
              "description": "Array of template ID strings that can be referenced (reference type only). Empty array means any template is allowed.",
              "items": { "type": "string" }
            },
            "inline": {
              "type": "boolean",
              "description": "When true on a group field, displays its nested fields side-by-side in a horizontal row."
            },
            "includeAltitude": {
              "type": "boolean",
              "description": "When true on a location field, includes an altitude input in the editor."
            },
            "defaultValue": {
              "description": "Default value pre-filled when creating new content. A reset button appears in the editor when the current value differs from this."
            }
          },
          "additionalProperties": false
        },
        "constraints": {
          "type": "object",
          "description": "Validation rules for this field. Each rule is an object with 'value' and 'message'.",
          "properties": {
            "required": { "$ref": "#/definitions/RequiredValidation" },
            "pattern": { "$ref": "#/definitions/PatternValidation" },
            "maxLength": { "$ref": "#/definitions/MaxLengthValidation" },
            "minLength": { "$ref": "#/definitions/MinLengthValidation" },
            "min": { "$ref": "#/definitions/MinValidation" },
            "max": { "$ref": "#/definitions/MaxValidation" },
            "allowedTypes": { "$ref": "#/definitions/AllowedTypesValidation" },
            "maxItems": { "$ref": "#/definitions/MaxItemsValidation" },
            "minItems": { "$ref": "#/definitions/MinItemsValidation" },
            "precision": { "$ref": "#/definitions/PrecisionValidation" },
            "altMin": { "$ref": "#/definitions/AltMinValidation" },
            "altMax": { "$ref": "#/definitions/AltMaxValidation" },
            "maxUploadSizeMB": {
              "type": "number",
              "minimum": 0.1,
              "description": "Maximum file size in megabytes (media fields)."
            }
          },
          "additionalProperties": false
        },
        "helper": {
          "$ref": "#/definitions/FieldHelper",
          "description": "URL generation helper button (text and link fields only)."
        },
        "conditionals": {
          "$ref": "#/definitions/Conditionals"
        },
        "fields": {
          "type": "array",
          "description": "Sub-fields within this group (group type only).",
          "minItems": 1,
          "items": { "$ref": "#/definitions/TemplateField" }
        }
      },
      "required": ["id", "type", "label"],
      "additionalProperties": true,
      "allOf": [
        {
          "$comment": "group type requires 'fields'",
          "if": { "properties": { "type": { "const": "group" } }, "required": ["type"] },
          "then": { "required": ["fields"] }
        },
        {
          "$comment": "'fields' is exclusive to group type",
          "if": { "properties": { "type": { "not": { "const": "group" } } }, "required": ["type"] },
          "then": { "not": { "required": ["fields"] } }
        }
      ]
    },
    "RequiredValidation": {
      "type": "object",
      "properties": {
        "value": { "type": "boolean" },
        "message": { "type": "string" }
      },
      "required": ["value", "message"],
      "additionalProperties": false
    },
    "PatternValidation": {
      "type": "object",
      "properties": {
        "value": { "type": "string" },
        "message": { "type": "string" }
      },
      "required": ["value", "message"],
      "additionalProperties": false
    },
    "MaxLengthValidation": {
      "type": "object",
      "properties": {
        "value": { "type": "integer", "minimum": 1 },
        "message": { "type": "string" }
      },
      "required": ["value", "message"],
      "additionalProperties": false
    },
    "MinLengthValidation": {
      "type": "object",
      "properties": {
        "value": { "type": "integer", "minimum": 0 },
        "message": { "type": "string" }
      },
      "required": ["value", "message"],
      "additionalProperties": false
    },
    "MinValidation": {
      "type": "object",
      "properties": {
        "value": { "type": "number" },
        "message": { "type": "string" }
      },
      "required": ["value", "message"],
      "additionalProperties": false
    },
    "MaxValidation": {
      "type": "object",
      "properties": {
        "value": { "type": "number" },
        "message": { "type": "string" }
      },
      "required": ["value", "message"],
      "additionalProperties": false
    },
    "AllowedTypesValidation": {
      "type": "object",
      "properties": {
        "value": { "type": "array", "items": { "type": "string" } },
        "message": { "type": "string" }
      },
      "required": ["value", "message"],
      "additionalProperties": false
    },
    "MaxItemsValidation": {
      "type": "object",
      "properties": {
        "value": { "type": "integer", "minimum": 1 },
        "message": { "type": "string" }
      },
      "required": ["value", "message"],
      "additionalProperties": false
    },
    "MinItemsValidation": {
      "type": "object",
      "properties": {
        "value": { "type": "integer", "minimum": 0 },
        "message": { "type": "string" }
      },
      "required": ["value", "message"],
      "additionalProperties": false
    },
    "PrecisionValidation": {
      "type": "object",
      "properties": {
        "value": { "type": "integer", "minimum": 0, "maximum": 10 },
        "message": { "type": "string" }
      },
      "required": ["value", "message"],
      "additionalProperties": false
    },
    "AltMinValidation": {
      "type": "object",
      "properties": {
        "value": { "type": "number" },
        "message": { "type": "string" }
      },
      "required": ["value", "message"],
      "additionalProperties": false
    },
    "AltMaxValidation": {
      "type": "object",
      "properties": {
        "value": { "type": "number" },
        "message": { "type": "string" }
      },
      "required": ["value", "message"],
      "additionalProperties": false
    }
  },
  "examples": [
    {
      "schemaVersion": "10.0",
      "name": "Product",
      "description": "Template demonstrating a field group with inline layout",
      "title": {
        "label": "Product Name",
        "props": {
          "placeholder": "e.g., Wireless Keyboard"
        },
        "validations": {
          "required": { "value": true, "message": "Product name is required" },
          "maxLength": { "value": 256, "message": "Must be 256 characters or less" }
        }
      },
      "sections": [
        {
          "id": "details",
          "label": "Physical Details",
          "config": { "color": "#546E7A" },
          "fields": [
            {
              "id": "dimensions",
              "type": "group",
              "label": "Dimensions (cm)",
              "description": "Width, height, and depth displayed side-by-side",
              "props": { "inline": true },
              "fields": [
                {
                  "id": "width",
                  "type": "decimal",
                  "label": "Width",
                  "constraints": { "min": { "value": 0, "message": "Must be positive" } }
                },
                {
                  "id": "height",
                  "type": "decimal",
                  "label": "Height",
                  "constraints": { "min": { "value": 0, "message": "Must be positive" } }
                },
                {
                  "id": "depth",
                  "type": "decimal",
                  "label": "Depth",
                  "constraints": { "min": { "value": 0, "message": "Must be positive" } }
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "schemaVersion": "10.0",
      "name": "Event",
      "description": "Template for events with indexes, conditionals, and repeatable speakers",
      "indexes": {
        "_event_start": "scheduling.eventStartDate",
        "_search_text": ["details.description"]
      },
      "title": {
        "label": "Event Title",
        "props": {
          "description": "The name of your event",
          "placeholder": "e.g., Community Game Night",
          "color": "#e8ca93"
        },
        "validations": {
          "required": { "value": true, "message": "Title is required" },
          "maxLength": { "value": 256, "message": "Title must be 256 characters or less" }
        }
      },
      "sections": [
        {
          "id": "details",
          "label": "Event Details",
          "config": {
            "color": "#64B5F6",
            "collapsible": true
          },
          "fields": [
            {
              "id": "description",
              "type": "multiline",
              "label": "Description",
              "description": "Short event description",
              "constraints": {
                "maxLength": { "value": 256, "message": "Description must be 256 characters or less" }
              }
            },
            {
              "id": "event_detail",
              "type": "oneOf",
              "label": "Event Detail",
              "description": "Select the type of event detail (visible only when description is filled)",
              "props": {
                "types": ["template-id-for-type-a", "template-id-for-type-b"]
              },
              "conditionals": {
                "visibility": {
                  "type": "rule",
                  "field": "description",
                  "operator": "exists"
                }
              },
              "constraints": {
                "required": { "value": true, "message": "Event detail is required." }
              }
            }
          ]
        },
        {
          "id": "scheduling",
          "label": "Scheduling",
          "config": {
            "color": "#81C784",
            "collapsible": true
          },
          "conditionals": {
            "visibility": {
              "type": "ruleset",
              "combinator": "&&",
              "children": [
                {
                  "type": "rule",
                  "field": "description",
                  "operator": "exists"
                },
                {
                  "type": "rule",
                  "field": "description",
                  "operator": "notContains",
                  "value": "TBD"
                }
              ]
            }
          },
          "fields": [
            {
              "id": "eventStartDate",
              "type": "datetime",
              "label": "Start Time",
              "description": "Event start date and time",
              "constraints": {
                "required": { "value": true, "message": "Start time is required" }
              }
            }
          ]
        }
      ]
    }
  ]
}
