{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://www.privacypixtools.com/tools/privacy-sketch/dsl/schema.json",
  "title": "SketchDSL v2",
  "description": "Declarative JSON for describing hand-drawn diagrams (privacy-sketch). Compiles to v1 element list rendered with rough.js.",
  "type": "object",
  "required": ["version", "elements"],
  "properties": {
    "version": {
      "const": "2.0",
      "description": "Schema version. Must be the literal string \"2.0\"."
    },
    "canvas": {
      "type": "object",
      "properties": {
        "width": { "type": "number", "minimum": 1, "default": 1200 },
        "height": {
          "oneOf": [
            { "type": "number", "minimum": 1 },
            { "const": "auto" }
          ],
          "default": "auto"
        },
        "padding": { "type": "number", "minimum": 0, "default": 0 },
        "background": { "$ref": "#/definitions/color" }
      }
    },
    "theme": {
      "type": "object",
      "description": "Defaults inherited by every element unless overridden. Use this to set the diagram's overall look.",
      "properties": {
        "roughness": { "type": "number", "minimum": 0, "maximum": 10, "default": 1.8 },
        "bowing": { "type": "number", "minimum": 0, "maximum": 10, "default": 1.2 },
        "fillStyle": { "$ref": "#/definitions/fillStyle" },
        "fontFamily": { "type": "string", "default": "Comic Sans MS" }
      }
    },
    "vars": {
      "type": "object",
      "description": "Reusable values. Reference from any element as \"$varName\" (in numeric fields) or substitute in expressions like \"=$gap*2\".",
      "additionalProperties": {
        "oneOf": [{ "type": "number" }, { "type": "string" }, { "type": "boolean" }]
      }
    },
    "styles": {
      "type": "object",
      "description": "Named style presets. Reference from any element via the \"style\" field. Supports \"extends\" for inheritance.",
      "additionalProperties": { "$ref": "#/definitions/styleDefinition" }
    },
    "elements": {
      "type": "array",
      "description": "Root element list. May mix primitives and layout containers.",
      "items": { "$ref": "#/definitions/element" }
    },
    "appState": {
      "type": "object",
      "properties": {
        "zoom": { "type": "number", "default": 1 },
        "panX": { "type": "number", "default": 0 },
        "panY": { "type": "number", "default": 0 }
      }
    }
  },

  "definitions": {
    "color": {
      "description": "Tailwind-inspired token (e.g. \"amber.500\") OR hex (e.g. \"#fde68a\") OR special (\"transparent\", \"white\", \"black\", \"cream\", \"paper\").",
      "oneOf": [
        {
          "type": "string",
          "enum": [
            "transparent", "white", "black", "cream", "paper",
            "gray.50", "gray.100", "gray.200", "gray.300", "gray.400", "gray.500", "gray.600", "gray.700", "gray.800", "gray.900",
            "slate.50", "slate.100", "slate.200", "slate.300", "slate.400", "slate.500", "slate.600", "slate.700", "slate.800", "slate.900",
            "red.50", "red.100", "red.200", "red.300", "red.400", "red.500", "red.600", "red.700", "red.800", "red.900",
            "orange.50", "orange.100", "orange.200", "orange.300", "orange.400", "orange.500", "orange.600", "orange.700", "orange.800", "orange.900",
            "amber.50", "amber.100", "amber.200", "amber.300", "amber.400", "amber.500", "amber.600", "amber.700", "amber.800", "amber.900",
            "yellow.50", "yellow.100", "yellow.200", "yellow.300", "yellow.400", "yellow.500", "yellow.600", "yellow.700", "yellow.800", "yellow.900",
            "green.50", "green.100", "green.200", "green.300", "green.400", "green.500", "green.600", "green.700", "green.800", "green.900",
            "cyan.50", "cyan.100", "cyan.200", "cyan.300", "cyan.400", "cyan.500", "cyan.600", "cyan.700", "cyan.800", "cyan.900",
            "blue.50", "blue.100", "blue.200", "blue.300", "blue.400", "blue.500", "blue.600", "blue.700", "blue.800", "blue.900",
            "indigo.50", "indigo.100", "indigo.200", "indigo.300", "indigo.400", "indigo.500", "indigo.600", "indigo.700", "indigo.800", "indigo.900",
            "purple.50", "purple.100", "purple.200", "purple.300", "purple.400", "purple.500", "purple.600", "purple.700", "purple.800", "purple.900",
            "pink.50", "pink.100", "pink.200", "pink.300", "pink.400", "pink.500", "pink.600", "pink.700", "pink.800", "pink.900"
          ]
        },
        { "type": "string", "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$" },
        { "type": "string", "pattern": "^rgba?\\(.+\\)$" }
      ]
    },
    "fillStyle": {
      "type": "string",
      "enum": ["solid", "hachure", "cross-hatch", "zigzag", "dots", "dashed", "zigzag-line"],
      "default": "hachure"
    },
    "lineStyle": {
      "type": "string",
      "enum": ["solid", "dashed", "dotted"],
      "default": "solid"
    },
    "numericValue": {
      "description": "A number, a variable reference (\"$name\"), or a math expression (\"=$base + 20\"). Expressions support +, -, *, /, parens, $vars, and element refs like \"id.right\".",
      "oneOf": [{ "type": "number" }, { "type": "string" }]
    },
    "animation": {
      "type": "object",
      "properties": {
        "type": { "type": "string", "enum": ["none", "fade", "slide", "draw"] },
        "delay": { "type": "number", "minimum": 0, "default": 0 },
        "duration": { "type": "number", "minimum": 0, "default": 500 }
      }
    },
    "elementOptions": {
      "type": "object",
      "description": "Visual options. Anything omitted inherits from theme.",
      "properties": {
        "stroke": { "$ref": "#/definitions/color" },
        "fill": { "$ref": "#/definitions/color" },
        "strokeWidth": { "type": "number", "minimum": 0 },
        "fillStyle": { "$ref": "#/definitions/fillStyle" },
        "lineStyle": { "$ref": "#/definitions/lineStyle" },
        "roughness": { "type": "number", "minimum": 0, "maximum": 10 },
        "bowing": { "type": "number", "minimum": 0, "maximum": 10 },
        "seed": {
          "type": ["integer", "null"],
          "description": "Optional. Omit to let the compiler derive a deterministic seed from the element's id (recommended). Set explicitly to lock a specific rough.js seed."
        }
      }
    },
    "styleDefinition": {
      "type": "object",
      "description": "Reusable style preset. Inherits from \"extends\" first, then overrides.",
      "properties": {
        "extends": { "type": "string", "description": "Name of another style to inherit from." },
        "stroke": { "$ref": "#/definitions/color" },
        "fill": { "$ref": "#/definitions/color" },
        "strokeWidth": { "type": "number", "minimum": 0 },
        "fillStyle": { "$ref": "#/definitions/fillStyle" },
        "lineStyle": { "$ref": "#/definitions/lineStyle" },
        "roughness": { "type": "number", "minimum": 0, "maximum": 10 },
        "bowing": { "type": "number", "minimum": 0, "maximum": 10 }
      },
      "additionalProperties": false
    },
    "position": {
      "type": "object",
      "description": "Relative positioning. The compiler resolves these against elements already declared earlier in the document.",
      "properties": {
        "below": { "type": "string", "description": "Place below this element (by id or #id)." },
        "above": { "type": "string" },
        "rightOf": { "type": "string" },
        "leftOf": { "type": "string" },
        "inside": { "type": "string", "description": "Place inside this element (anchored by align)." },
        "centerIn": { "type": "string" },
        "align": {
          "type": "string",
          "enum": ["top-left", "top-right", "bottom-left", "bottom-right", "center", "top-center", "bottom-center", "left-center", "right-center"]
        },
        "gap": { "type": "number", "default": 0 },
        "padding": { "type": "number", "default": 0 },
        "offset": {
          "type": "object",
          "properties": {
            "x": { "type": "number" },
            "y": { "type": "number" }
          }
        }
      }
    },

    "element": {
      "oneOf": [
        { "$ref": "#/definitions/primitiveElement" },
        { "$ref": "#/definitions/layoutContainer" }
      ]
    },

    "primitiveElement": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "id": { "type": "string", "description": "Unique id. Used for relative positioning references and stable seed derivation." },
        "type": {
          "type": "string",
          "enum": [
            "rectangle", "ellipse", "diamond", "triangle",
            "star", "heart", "hexagon", "pentagon", "trapezoid", "parallelogram", "cross", "banner", "cloud",
            "speechBubble", "thoughtBubble", "frame", "brackets", "cylinder", "cube",
            "process", "decision", "data", "terminator", "document", "preparation", "manualInput",
            "line", "arrow", "curvedArrow", "connector", "thickPath",
            "text",
            "image", "icon", "tablerIcon", "actor", "pen",
            "group"
          ]
        },
        "x": { "$ref": "#/definitions/numericValue" },
        "y": { "$ref": "#/definitions/numericValue" },
        "w": { "$ref": "#/definitions/numericValue" },
        "h": { "$ref": "#/definitions/numericValue" },
        "width": { "$ref": "#/definitions/numericValue", "description": "Alias for w." },
        "height": { "$ref": "#/definitions/numericValue", "description": "Alias for h." },
        "angle": { "type": "number", "default": 0 },
        "borderRadius": { "type": "number", "minimum": 0, "default": 0 },
        "position": { "$ref": "#/definitions/position" },
        "style": { "type": "string", "description": "Name of a preset from the document's \"styles\" map." },
        "options": { "$ref": "#/definitions/elementOptions" },
        "animation": { "$ref": "#/definitions/animation" },

        "text": { "type": "string", "description": "Text content. Required when type is \"text\"." },
        "fontSize": { "type": "number", "minimum": 1 },
        "fontFamily": { "type": "string" },
        "fontWeight": { "type": "string", "enum": ["normal", "bold"] },
        "fontStyle": { "type": "string", "enum": ["normal", "italic"] },
        "align": { "type": "string", "enum": ["left", "center", "right"] },
        "backgroundColor": { "$ref": "#/definitions/color" },
        "backgroundPadding": { "type": "number", "minimum": 0 },
        "backgroundBorderRadius": { "type": "number", "minimum": 0 },
        "lineHeight": { "type": "number", "minimum": 0 },

        "src": { "type": "string", "description": "Image data URL or path. Required when type is \"image\"." },
        "iconCategory": { "type": "string" },
        "iconName": { "type": "string" },
        "svgContent": { "type": "string" },
        "iconPath": { "type": "string" },
        "points": {
          "oneOf": [
            { "type": "number" },
            { "type": "array", "items": { "type": "object" } }
          ],
          "description": "Star: number of points. Pen: array of {x,y} freehand points."
        },
        "pointerDirection": { "type": "string", "enum": ["top-left", "top-right", "bottom-left", "bottom-right"] },

        "startMarker": { "type": "string", "enum": ["none", "arrow", "circle", "square", "diamond"] },
        "endMarker": { "type": "string", "enum": ["none", "arrow", "circle", "square", "diamond"] },
        "startArrowhead": { "type": "string", "enum": ["none", "arrow"] },
        "endArrowhead": { "type": "string", "enum": ["none", "arrow"] },
        "connectorStyle": { "type": "string", "enum": ["straight", "orthogonal", "curved"] },
        "controlX": { "type": "number" },
        "controlY": { "type": "number" },
        "pathColor": { "$ref": "#/definitions/color" },
        "pathWidth": { "type": "number", "minimum": 0 },
        "pathStyle": { "type": "string" },

        "children": { "type": "array", "description": "Group: child element ids (group element is a logical grouping, not a layout)." }
      },
      "allOf": [
        {
          "if": { "properties": { "type": { "const": "text" } } },
          "then": { "required": ["text"] }
        },
        {
          "if": { "properties": { "type": { "const": "image" } } },
          "then": { "required": ["src"] }
        }
      ]
    },

    "layoutContainer": {
      "type": "object",
      "required": ["type", "children"],
      "properties": {
        "type": { "type": "string", "enum": ["VStack", "HStack", "Grid", "ZStack", "Absolute"] },
        "id": { "type": "string" },
        "x": { "$ref": "#/definitions/numericValue" },
        "y": { "$ref": "#/definitions/numericValue" },
        "width": { "$ref": "#/definitions/numericValue" },
        "height": { "$ref": "#/definitions/numericValue" },
        "w": { "$ref": "#/definitions/numericValue" },
        "h": { "$ref": "#/definitions/numericValue" },
        "gap": {
          "description": "Spacing between children. Number, or { x, y } for Grid to set column and row gaps independently.",
          "oneOf": [
            { "type": "number" },
            { "type": "object", "properties": { "x": { "type": "number" }, "y": { "type": "number" } } }
          ]
        },
        "padding": { "type": "number", "minimum": 0 },
        "align": {
          "type": "string",
          "description": "VStack: how children align horizontally (start/center/end). HStack: how they align vertically (start/center/end). ZStack: anchor corner (top-left, center, etc.).",
          "enum": ["start", "center", "end", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right"]
        },
        "distribute": {
          "type": "string",
          "description": "HStack only. How children distribute across the available width.",
          "enum": ["start", "center", "end", "space-between", "space-around"]
        },
        "columns": { "type": "integer", "minimum": 1, "description": "Grid only. Number of columns." },
        "position": { "$ref": "#/definitions/position" },
        "children": { "type": "array", "items": { "$ref": "#/definitions/element" } }
      }
    }
  },

  "examples": [
    {
      "version": "2.0",
      "canvas": { "width": 800, "height": "auto", "background": "cream" },
      "theme": { "roughness": 1.8, "fillStyle": "hachure" },
      "elements": [
        {
          "type": "HStack",
          "x": 40, "y": 40, "gap": 20,
          "children": [
            { "id": "a", "type": "rectangle", "w": 120, "h": 80, "options": { "fill": "amber.100", "stroke": "amber.500" } },
            { "id": "b", "type": "rectangle", "w": 120, "h": 80, "options": { "fill": "blue.100", "stroke": "blue.500" } },
            { "id": "c", "type": "rectangle", "w": 120, "h": 80, "options": { "fill": "green.100", "stroke": "green.500" } }
          ]
        },
        {
          "type": "arrow",
          "x": 160, "y": 80, "w": 20, "h": 0,
          "options": { "stroke": "gray.700" }
        }
      ]
    },
    {
      "version": "2.0",
      "vars": { "cardW": 140, "cardH": 80 },
      "styles": {
        "card": { "fill": "blue.100", "stroke": "blue.500", "strokeWidth": 2 },
        "highlight": { "extends": "card", "fill": "amber.200", "stroke": "amber.600" }
      },
      "elements": [
        {
          "type": "Grid",
          "x": 50, "y": 50, "columns": 3, "gap": 16,
          "children": [
            { "id": "g1", "type": "rectangle", "style": "card",      "w": "$cardW", "h": "$cardH" },
            { "id": "g2", "type": "rectangle", "style": "highlight", "w": "$cardW", "h": "$cardH" },
            { "id": "g3", "type": "rectangle", "style": "card",      "w": "$cardW", "h": "$cardH" },
            { "id": "g4", "type": "rectangle", "style": "card",      "w": "$cardW", "h": "$cardH" },
            { "id": "g5", "type": "rectangle", "style": "card",      "w": "$cardW", "h": "$cardH" },
            { "id": "g6", "type": "rectangle", "style": "highlight", "w": "$cardW", "h": "$cardH" }
          ]
        }
      ]
    },
    {
      "version": "2.0",
      "elements": [
        { "id": "api", "type": "rectangle", "x": 100, "y": 100, "w": 160, "h": 80, "options": { "fill": "blue.100" } },
        { "id": "db",  "type": "cylinder",  "position": { "below": "#api", "gap": 40 }, "w": 160, "h": 100, "options": { "fill": "amber.100" } },
        { "id": "edge", "type": "arrow", "x": 180, "y": 180, "w": 0, "h": 40, "options": { "stroke": "gray.700" } }
      ]
    }
  ]
}
