{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:cheerbook:schema:liveglow-cheer:1",
  "title": "Liveglow cheer file (.cheer.json), format liveglow-cheer, version 1",
  "description": "Structure of a shared cheer / MIX file. Rules that JSON Schema cannot state (file size, nesting depth, member references, timing against the song, word counts, calendar dates, author requirement for shared files) are listed in README.md §6; a conforming reader applies both. Optional fields may be written as null, which reads as absent. Unknown fields are allowed anywhere and ignored, but may not use a lyric key. Duplicate keys, which JSON parsers resolve differently, make a file invalid and must be detected before parsing (README §6). License: CC0-1.0.",
  "type": "object",
  "required": ["format", "version", "song", "events", "credits"],
  "properties": {
    "format": { "const": "liveglow-cheer" },
    "version": { "const": 1 },
    "song": { "$ref": "#/$defs/song" },
    "offset": { "type": ["number", "null"], "minimum": -10, "maximum": 10 },
    "members": { "type": ["array", "null"], "maxItems": 64, "items": { "$ref": "#/$defs/member" } },
    "events": {
      "type": "array",
      "description": "At most 2000 events, events of unknown kinds included.",
      "maxItems": 2000,
      "items": { "$ref": "#/$defs/event" }
    },
    "credits": { "$ref": "#/$defs/credits" },
    "generator": { "type": ["string", "null"], "maxLength": 64 }
  },
  "allOf": [{ "$ref": "#/$defs/noLyricKeys" }],
  "$defs": {
    "noLyricKeys": {
      "description": "No object at any depth may have a key that, lowercased, is one of: lyrics, lyric, lrc, yrc, krc, qrc, tlyric, lines, words (U+212A KELVIN SIGN lowercases to k).",
      "propertyNames": {
        "not": {
          "pattern": "^(?:[Ll][Yy][Rr][Ii][Cc][Ss]?|[Ll][Rr][Cc]|[Yy][Rr][Cc]|[Kk\\u212A][Rr][Cc]|[Qq][Rr][Cc]|[Tt][Ll][Yy][Rr][Ii][Cc]|[Ll][Ii][Nn][Ee][Ss]|[Ww][Oo][Rr][Dd][Ss])$"
        }
      },
      "additionalProperties": { "$ref": "#/$defs/noLyricKeys" },
      "items": { "$ref": "#/$defs/noLyricKeys" }
    },
    "nonBlank": {
      "description": "Holds at least one character outside the trimmed white space: Unicode White_Space plus U+200B and U+FEFF (U+0009–U+000D, U+0020, U+0085, U+00A0, U+1680, U+2000–U+200B, U+2028, U+2029, U+202F, U+205F, U+3000, U+FEFF).",
      "pattern": "[^\\t\\n\\v\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200B\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF]"
    },
    "singleLine": {
      "description": "No line break (U+000A–U+000D, U+0085, U+2028, U+2029).",
      "pattern": "^[^\\n\\v\\f\\r\\u0085\\u2028\\u2029]*$"
    },
    "webURL": {
      "description": "Starts with http:// or https:// in any case. That it also parses as a URL is a check outside the Schema (README §6).",
      "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://"
    },
    "song": {
      "type": "object",
      "required": ["title", "artist", "duration"],
      "properties": {
        "title": { "type": "string", "$ref": "#/$defs/nonBlank", "maxLength": 200 },
        "artist": { "type": "string", "$ref": "#/$defs/nonBlank", "maxLength": 200 },
        "duration": { "description": "Seconds of the recording the timeline was tapped against.", "type": "number", "exclusiveMinimum": 0, "maximum": 7200 },
        "album": { "type": ["string", "null"] },
        "isrc": {
          "description": "Hyphens and white space (the trimmed set) are removed anywhere and ASCII letters uppercased; the result must be 2 letters, 3 letters or digits and 7 digits, all ASCII.",
          "type": ["string", "null"],
          "pattern": "^[-\\t\\n\\v\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200B\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF]*(?:[A-Za-z][-\\t\\n\\v\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200B\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF]*){2}(?:[A-Za-z0-9][-\\t\\n\\v\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200B\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF]*){3}(?:[0-9][-\\t\\n\\v\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200B\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF]*){7}$"
        },
        "appleMusicID": { "description": "Apple Music catalog id, digits only.", "type": ["string", "null"], "pattern": "^[0-9]{1,20}$" }
      }
    },
    "member": {
      "type": "object",
      "required": ["id", "name", "color"],
      "properties": {
        "id": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,32}$" },
        "name": { "type": "string", "$ref": "#/$defs/nonBlank", "maxLength": 32 },
        "color": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$" }
      }
    },
    "knownKind": {
      "type": "object",
      "required": ["kind"],
      "properties": { "kind": { "enum": ["mix", "call", "part", "note"] } }
    },
    "event": {
      "description": "Any event: the field types. Events of a known kind must also satisfy knownEvent; events of another kind are skipped with a warning.",
      "type": "object",
      "required": ["start", "end", "kind"],
      "properties": {
        "start": { "type": "number" },
        "end": { "type": "number" },
        "kind": { "type": "string" },
        "text": { "type": ["string", "null"] },
        "members": { "type": ["array", "null"], "items": { "type": "string" } },
        "echo": { "type": ["boolean", "null"] },
        "mixRef": { "type": ["string", "null"] },
        "steps": { "type": ["array", "null"], "items": { "type": "number" } },
        "readings": {
          "type": ["object", "null"],
          "properties": {
            "kana": { "type": ["array", "null"], "items": { "type": "string" } },
            "romaji": { "type": ["array", "null"], "items": { "type": "string" } }
          }
        },
        "tapped": { "type": ["boolean", "null"] },
        "name": { "type": ["string", "null"] }
      },
      "if": { "$ref": "#/$defs/knownKind" },
      "then": { "$ref": "#/$defs/knownEvent" }
    },
    "knownEvent": {
      "properties": {
        "start": { "minimum": 0 },
        "text": {
          "description": "Name slots: {name} (the event's members), {name:<member id>}; {{ and }} are literal braces; any other brace is an error.",
          "pattern": "^(?:[^{}]|\\{\\{|\\}\\}|\\{name(?::[^{}]+)?\\})*$"
        },
        "mixRef": { "pattern": "^[a-z0-9][a-z0-9:._-]{0,63}$" },
        "steps": { "minItems": 1, "maxItems": 64, "items": { "minimum": 0 } },
        "name": { "allOf": [{ "$ref": "#/$defs/nonBlank" }, { "$ref": "#/$defs/singleLine" }], "maxLength": 32 }
      },
      "allOf": [
        {
          "if": { "required": ["echo"], "properties": { "echo": { "const": true } } },
          "then": { "properties": { "kind": { "const": "call" }, "text": { "type": "null" } } },
          "else": {
            "if": { "properties": { "kind": { "const": "part" } } },
            "then": { "properties": { "text": { "type": "null" } } },
            "else": { "required": ["text"], "properties": { "text": { "type": "string", "$ref": "#/$defs/nonBlank" } } }
          }
        },
        {
          "if": { "properties": { "kind": { "const": "part" } } },
          "then": { "required": ["members"], "properties": { "members": { "type": "array", "minItems": 1 } } }
        },
        {
          "if": { "properties": { "kind": { "const": "mix" } } },
          "then": { "properties": { "text": { "maxLength": 120 } } },
          "else": { "properties": { "text": { "maxLength": 60 } } }
        },
        {
          "if": { "required": ["mixRef"], "properties": { "mixRef": { "type": "string" } } },
          "then": { "properties": { "kind": { "const": "mix" } } }
        },
        {
          "if": { "required": ["steps"], "properties": { "steps": { "type": "array" } } },
          "then": { "required": ["text"], "properties": { "kind": { "enum": ["mix", "call"] }, "text": { "type": "string" } } }
        },
        {
          "if": { "required": ["name"], "properties": { "name": { "type": "string" } } },
          "then": { "properties": { "kind": { "enum": ["mix", "call"] } } }
        }
      ]
    },
    "author": {
      "type": "object",
      "required": ["name"],
      "properties": {
        "name": { "type": "string", "$ref": "#/$defs/nonBlank", "maxLength": 64 },
        "url": { "type": ["string", "null"], "$ref": "#/$defs/webURL" }
      }
    },
    "source": {
      "type": "object",
      "required": ["kind"],
      "properties": {
        "kind": { "enum": ["self", "call-sheet", "video", "official", "other"] },
        "title": { "type": ["string", "null"], "maxLength": 120 },
        "url": { "type": ["string", "null"], "$ref": "#/$defs/webURL" },
        "retrieved": { "description": "A calendar date, YYYY-MM-DD.", "type": ["string", "null"], "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", "format": "date" }
      }
    },
    "credits": {
      "type": "object",
      "required": ["authors", "permission"],
      "properties": {
        "authors": { "description": "A shared file names at least one author.", "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/author" } },
        "sources": { "type": ["array", "null"], "items": { "$ref": "#/$defs/source" } },
        "permission": { "enum": ["original", "open-license", "consented", "public-only", "official", "unknown"] },
        "license": { "type": ["string", "null"] },
        "note": { "type": ["string", "null"], "maxLength": 200 }
      },
      "if": { "properties": { "permission": { "const": "open-license" } } },
      "then": { "required": ["license"], "properties": { "license": { "type": "string", "$ref": "#/$defs/nonBlank" } } }
    }
  }
}
