{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Pivograph document",
  "description": "A graph of projects and their connections, rendered with Pivotick.",
  "type": "object",
  "properties": {
    "version": {
      "const": 1
    },
    "meta": {
      "type": "object",
      "properties": {
        "title": {
          "type": "string"
        },
        "description": {
          "type": "string"
        },
        "linkDistance": {
          "type": "number",
          "minimum": 1,
          "description": "Length of the edges in the layout (default 150). Raise it for big nodes or long edge labels."
        },
        "fixedLayout": {
          "type": "boolean",
          "description": "Nodes stay exactly at their x / y (no force layout), as in a drawn diagram."
        }
      }
    },
    "nodeTypes": {
      "type": "object",
      "description": "Reusable node styles, keyed by type name.",
      "additionalProperties": {
        "$ref": "#/$defs/nodeAppearance"
      }
    },
    "edgeTypes": {
      "type": "object",
      "description": "Reusable edge styles, keyed by type name.",
      "additionalProperties": {
        "$ref": "#/$defs/edgeAppearance"
      }
    },
    "sections": {
      "type": "array",
      "description": "Titled frames drawn behind the graph, for the picture only (they hold no nodes).",
      "items": {
        "type": "object",
        "required": [
          "x",
          "y"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "x": {
            "type": "number",
            "description": "Top-left corner, in graph coordinates."
          },
          "y": {
            "type": "number"
          },
          "width": {
            "type": "number",
            "minimum": 1,
            "description": "Default 400."
          },
          "height": {
            "type": "number",
            "minimum": 1,
            "description": "Default 240."
          },
          "color": {
            "$ref": "#/$defs/color",
            "description": "Title and underline colour."
          },
          "underline": {
            "type": "boolean",
            "description": "Line under the title. Default: when the section has a colour."
          },
          "titleSize": {
            "type": "number",
            "minimum": 1,
            "description": "Default 22."
          },
          "titleFont": {
            "type": "string"
          },
          "fill": {
            "type": "string",
            "description": "Background colour, or \"none\"."
          },
          "borderColor": {
            "type": "string",
            "description": "Border colour, or \"none\"."
          }
        }
      }
    },
    "arrows": {
      "type": "array",
      "description": "Arrows drawn over the graph, from / to a node, a section or a free point.",
      "items": {
        "type": "object",
        "required": [
          "from",
          "to"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "from": {
            "type": "object",
            "description": "{ node, at? }, { section, at? } or { x, y }. `at` = [0..1, 0..1] place on the target's box; without it, the side facing the other end.",
            "properties": {
              "node": {
                "type": [
                  "string",
                  "number"
                ]
              },
              "section": {
                "type": "string"
              },
              "at": {
                "type": "array",
                "items": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                },
                "minItems": 2,
                "maxItems": 2
              },
              "x": {
                "type": "number"
              },
              "y": {
                "type": "number"
              }
            }
          },
          "to": {
            "type": "object",
            "description": "{ node, at? }, { section, at? } or { x, y }. `at` = [0..1, 0..1] place on the target's box; without it, the side facing the other end.",
            "properties": {
              "node": {
                "type": [
                  "string",
                  "number"
                ]
              },
              "section": {
                "type": "string"
              },
              "at": {
                "type": "array",
                "items": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                },
                "minItems": 2,
                "maxItems": 2
              },
              "x": {
                "type": "number"
              },
              "y": {
                "type": "number"
              }
            }
          },
          "label": {
            "type": "string",
            "description": "A \"\\n\" starts a new line."
          },
          "description": {
            "type": "string"
          },
          "direction": {
            "enum": [
              "forward",
              "backward",
              "both",
              "none"
            ]
          },
          "route": {
            "enum": [
              "straight",
              "elbow"
            ]
          },
          "color": {
            "$ref": "#/$defs/color"
          },
          "width": {
            "type": "number"
          },
          "dashed": {
            "type": "boolean"
          },
          "labelColor": {
            "$ref": "#/$defs/color"
          },
          "labelSize": {
            "type": "number"
          },
          "labelFont": {
            "type": "string"
          },
          "labelBackground": {
            "type": "string"
          },
          "labelOffset": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "minItems": 2,
            "maxItems": 2,
            "description": "Shift of the label from the middle of the arrow."
          }
        }
      }
    },
    "nodes": {
      "type": "array",
      "items": {
        "allOf": [
          {
            "$ref": "#/$defs/nodeAppearance"
          },
          {
            "type": "object",
            "required": [
              "id"
            ],
            "properties": {
              "id": {
                "type": [
                  "string",
                  "number"
                ]
              },
              "type": {
                "type": "string",
                "description": "Key of nodeTypes."
              },
              "description": {
                "type": "string"
              },
              "url": {
                "type": "string"
              },
              "x": {
                "type": "number",
                "description": "Saved layout position."
              },
              "y": {
                "type": "number"
              },
              "tags": {
                "description": "#tags shown as pills under the node.",
                "oneOf": [
                  {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  {
                    "type": "string",
                    "description": "\"#a #b\""
                  }
                ]
              },
              "github": {
                "type": "string",
                "description": "GitHub repository: owner/repo or a github.com URL. Its details are fetched from the GitHub API."
              },
              "links": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "url"
                  ],
                  "properties": {
                    "label": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                }
              },
              "details": {
                "type": "object",
                "description": "Extra fields shown in the details panel.",
                "additionalProperties": {
                  "oneOf": [
                    {
                      "type": [
                        "string",
                        "number",
                        "boolean"
                      ]
                    },
                    {
                      "type": "array",
                      "items": {
                        "type": [
                          "string",
                          "number",
                          "boolean"
                        ]
                      }
                    }
                  ]
                }
              },
              "subtitle": {
                "type": "string",
                "description": "Card shape: a second line inside the card."
              }
            }
          }
        ]
      }
    },
    "edges": {
      "type": "array",
      "items": {
        "allOf": [
          {
            "$ref": "#/$defs/edgeAppearance"
          },
          {
            "type": "object",
            "required": [
              "from",
              "to"
            ],
            "properties": {
              "id": {
                "type": [
                  "string",
                  "number"
                ]
              },
              "from": {
                "type": [
                  "string",
                  "number"
                ],
                "description": "Source node id."
              },
              "to": {
                "type": [
                  "string",
                  "number"
                ],
                "description": "Target node id."
              },
              "type": {
                "type": "string",
                "description": "Key of edgeTypes."
              },
              "description": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "description": "Extra fields shown in the details panel.",
                "additionalProperties": {
                  "oneOf": [
                    {
                      "type": [
                        "string",
                        "number",
                        "boolean"
                      ]
                    },
                    {
                      "type": "array",
                      "items": {
                        "type": [
                          "string",
                          "number",
                          "boolean"
                        ]
                      }
                    }
                  ]
                }
              }
            }
          }
        ]
      }
    },
    "tags": {
      "type": "object",
      "description": "Tag registry: how each #tag is drawn (as a pill under the node).",
      "additionalProperties": {
        "type": "object",
        "properties": {
          "color": {
            "$ref": "#/$defs/color"
          },
          "icon": {
            "type": "string",
            "description": "Name of a bundled Font Awesome Free icon, e.g. shield-halved, github."
          }
        }
      }
    }
  },
  "$defs": {
    "color": {
      "type": "string",
      "description": "Any CSS colour, e.g. #3b63f3."
    },
    "nodeAppearance": {
      "type": "object",
      "properties": {
        "label": {
          "type": "string"
        },
        "color": {
          "$ref": "#/$defs/color"
        },
        "shape": {
          "enum": [
            "circle",
            "square",
            "triangle",
            "hexagon",
            "card"
          ]
        },
        "size": {
          "type": "number",
          "minimum": 1
        },
        "image": {
          "type": "string",
          "description": "URL, relative path or data: URL."
        },
        "imageFit": {
          "enum": [
            "cover",
            "contain",
            "icon",
            "frame"
          ]
        },
        "borderColor": {
          "$ref": "#/$defs/color"
        },
        "borderWidth": {
          "type": "number",
          "minimum": 0,
          "description": "0 = no border."
        },
        "hideLabel": {
          "type": "boolean",
          "description": "Hide the label under the node (e.g. when the image shows the name)."
        },
        "labelColor": {
          "$ref": "#/$defs/color"
        },
        "labelBackground": {
          "type": "string",
          "description": "CSS colour, or \"none\" for no background."
        },
        "labelSize": {
          "type": "number",
          "minimum": 1,
          "description": "Font size in pixels."
        },
        "labelFont": {
          "type": "string",
          "description": "sans, serif, mono, rounded, condensed — or any CSS font-family."
        },
        "hideBadges": {
          "type": "boolean",
          "description": "Hide the tag pills."
        },
        "width": {
          "type": "number",
          "description": "Card shape: width in pixels (default: its content)."
        },
        "height": {
          "type": "number",
          "description": "Card shape: height in pixels."
        },
        "padding": {
          "type": "number",
          "description": "Card shape: space between the border and the content (default 16)."
        },
        "imageSize": {
          "type": "number",
          "description": "Card shape: image height (default 56)."
        },
        "imagePosition": {
          "enum": [
            "top",
            "left"
          ],
          "description": "Card shape: image above (default) or beside the text."
        }
      }
    },
    "edgeAppearance": {
      "type": "object",
      "properties": {
        "label": {
          "type": "string"
        },
        "color": {
          "$ref": "#/$defs/color"
        },
        "width": {
          "type": "number",
          "minimum": 0.5
        },
        "dashed": {
          "type": "boolean"
        },
        "direction": {
          "enum": [
            "forward",
            "backward",
            "both",
            "none"
          ],
          "description": "forward: from → to, backward: to → from, both: ↔, none: no arrow."
        }
      }
    }
  }
}
