{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://golfsga.com/sga-ois/schema/0.1/course.schema.json",
  "title": "SGA-OIS course details",
  "description": "The course as a physical thing: its holes, and what each one measures from every set of markers it offers. Sent once at the start of a round, beside the opening scorecard. Separate from the scorecard because it is a different KIND of fact -- the course does not change while the round is played, and a table of eighteen holes across eight tees is a hundred and forty-four numbers that would otherwise be repeated on every checkpoint. A consumer that already holds this digest has the whole document and may ignore the body.",
  "type": "object",
  "required": [
    "sga_kind",
    "course_id",
    "name",
    "digest",
    "holes",
    "tees"
  ],
  "additionalProperties": false,
  "properties": {
    "sga_kind": {
      "const": "course",
      "description": "Document type discriminator. Always course."
    },
    "course_id": {
      "type": "string",
      "description": "Stable identifier for this course, as the simulator names it. Two bays that played the same course report the same id."
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "description": "What the course is called, for a human."
    },
    "version": {
      "type": "string",
      "description": "The simulator's own version for this course file, where it has one. A course is edited -- a tee moved, a green rebuilt -- and two rounds on different versions are not quite the same course."
    },
    "digest": {
      "type": "string",
      "pattern": "^sha256:[0-9a-f]{64}$",
      "description": "Digest over the RFC 8785 canonical form of this document with `digest` itself removed. It is what makes 'send once' safe: a consumer caches by this, and a course whose tees were re-measured produces a different digest rather than quietly overwriting the one a finished round was scored against."
    },
    "holes": {
      "type": "array",
      "minItems": 1,
      "maxItems": 36,
      "description": "Every hole, independent of which tee anybody plays. This is the part of a printed card that is the same in every column.",
      "items": {
        "$ref": "#/$defs/hole"
      }
    },
    "tees": {
      "type": "array",
      "minItems": 1,
      "maxItems": 8,
      "description": "Every set of markers the course offers, longest first by convention. Eight is the practical ceiling on a real card.",
      "items": {
        "$ref": "#/$defs/tee"
      }
    }
  },
  "$defs": {
    "hole": {
      "type": "object",
      "required": [
        "number",
        "par"
      ],
      "additionalProperties": false,
      "properties": {
        "number": {
          "type": "integer",
          "minimum": 1,
          "maximum": 36
        },
        "par": {
          "type": "integer",
          "minimum": 3,
          "maximum": 6
        },
        "stroke_index": {
          "type": "integer",
          "minimum": 1,
          "maximum": 36,
          "description": "Where this hole ranks for difficulty, 1 hardest. Decides which holes a player receives a stroke on, so it must be stable for a given course."
        },
        "name": {
          "type": "string",
          "description": "The hole's name, where it has one."
        }
      },
      "patternProperties": {
        "^x-[a-z0-9_-]+\\.": {}
      }
    },
    "tee": {
      "type": "object",
      "required": [
        "name",
        "holes"
      ],
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 32,
          "description": "What this set of markers is called, verbatim from the course. FREEFORM on purpose: real courses use colors (Blue, White, Red), names (Championship, Members), numbers (1 through 5) and combinations, and no enumeration this standard invented would survive contact with them. It is an identifier for display and for joining a player to their yardages -- never something to parse meaning out of. A slot's tee_set on the scorecard MUST match one of these exactly."
        },
        "rating": {
          "type": "number",
          "description": "Course rating from these markers, where the course states one. Not computed here: a rating is issued by a governing body and a simulator that invented one would be inventing a player rating."
        },
        "slope": {
          "type": "integer",
          "minimum": 55,
          "maximum": 155,
          "description": "Slope rating from these markers, where the course states one."
        },
        "holes": {
          "type": "object",
          "description": "Hole number (as a string key) -> what that hole measures from THESE markers. Every hole in `holes` MUST appear. This is the whole point of the document: a real tee deck moves each hole by a different amount, so a single multiplier against a course length is not a course, it is a guess with the shape of one.",
          "patternProperties": {
            "^(?:[1-9]|[12][0-9]|3[0-6])$": {
              "$ref": "#/$defs/teeHole"
            }
          },
          "additionalProperties": false
        }
      },
      "patternProperties": {
        "^x-[a-z0-9_-]+\\.": {}
      }
    },
    "teeHole": {
      "type": "object",
      "required": [
        "length_m"
      ],
      "additionalProperties": false,
      "properties": {
        "length_m": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Card yardage for this hole from this tee, in meters. What the scorecard says, not what any particular shot played -- a pin position moves the day's distance and that belongs on the scorecard, per slot."
        }
      },
      "patternProperties": {
        "^x-[a-z0-9_-]+\\.": {}
      }
    }
  },
  "patternProperties": {
    "^x-[a-z0-9_-]+\\.": {}
  }
}
