{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.kapiworks.com/schemas/questions-2.json",
  "title": "Kapi Works questions (questions.json)",
  "description": "questions.json: every question, with its answer, feedback and explanation. Practice questions sit on pages; held questions sit only in a test's pool.",
  "type": "object",
  "required": [
    "schema",
    "questions"
  ],
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "description": "This file's schema, https://docs.kapiworks.com/schemas/questions-2.json, so editors check the file as you type. Kapi ignores it."
    },
    "schema": {
      "const": "kapi.works.questions/2",
      "description": "The format, kapi.works.questions/2."
    },
    "questions": {
      "type": "array",
      "maxItems": 2000,
      "items": {
        "$ref": "#/$defs/question"
      },
      "description": "The questions, up to 2,000. Question IDs are all different."
    }
  },
  "$defs": {
    "question": {
      "description": "A question. Its kind sets the keys it takes besides id, kind, prompt, situation and explanation.",
      "type": "object",
      "required": [
        "id",
        "kind",
        "prompt"
      ],
      "properties": {
        "kind": {
          "enum": [
            "choice",
            "multi",
            "exact",
            "order",
            "match"
          ],
          "description": "The kind of question."
        }
      },
      "allOf": [
        {
          "if": {
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "const": "choice"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/question-choice"
          }
        },
        {
          "if": {
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "const": "multi"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/question-multi"
          }
        },
        {
          "if": {
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "const": "exact"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/question-exact"
          }
        },
        {
          "if": {
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "const": "order"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/question-order"
          }
        },
        {
          "if": {
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "const": "match"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/question-match"
          }
        }
      ]
    },
    "question-choice": {
      "description": "One right choice.",
      "type": "object",
      "required": [
        "id",
        "kind",
        "prompt",
        "choices",
        "answer"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The question's ID. Its text IDs start with it, such as q1.prompt."
        },
        "kind": {
          "const": "choice",
          "description": "The question's kind."
        },
        "prompt": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the question."
        },
        "situation": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the situation it's set in, shown before the question."
        },
        "explanation": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of why the answer is right, shown after an answer."
        },
        "choices": {
          "$ref": "#/$defs/choices",
          "description": "The choices."
        },
        "answer": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The ID of the right choice."
        }
      }
    },
    "question-multi": {
      "description": "One or more right choices.",
      "type": "object",
      "required": [
        "id",
        "kind",
        "prompt",
        "choices",
        "answer"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The question's ID. Its text IDs start with it, such as q1.prompt."
        },
        "kind": {
          "const": "multi",
          "description": "The question's kind."
        },
        "prompt": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the question."
        },
        "situation": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the situation it's set in, shown before the question."
        },
        "explanation": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of why the answer is right, shown after an answer."
        },
        "choices": {
          "$ref": "#/$defs/choices",
          "description": "The choices."
        },
        "answer": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
            "description": "A choice ID."
          },
          "description": "The IDs of every right choice."
        }
      }
    },
    "question-exact": {
      "description": "A typed answer.",
      "type": "object",
      "required": [
        "id",
        "kind",
        "prompt",
        "accepted",
        "match"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The question's ID. Its text IDs start with it, such as q1.prompt."
        },
        "kind": {
          "const": "exact",
          "description": "The question's kind."
        },
        "prompt": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the question."
        },
        "situation": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the situation it's set in, shown before the question."
        },
        "explanation": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of why the answer is right, shown after an answer."
        },
        "accepted": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512,
            "description": "An answer, up to 512 bytes."
          },
          "description": "The answers Kapi accepts, as the words themselves."
        },
        "match": {
          "enum": [
            "prose",
            "exact"
          ],
          "description": "prose ignores case, accents, punctuation and extra spaces. exact keeps them all, for code and symbols."
        }
      }
    },
    "question-order": {
      "description": "Put items in order.",
      "type": "object",
      "required": [
        "id",
        "kind",
        "prompt",
        "items",
        "answer"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The question's ID. Its text IDs start with it, such as q1.prompt."
        },
        "kind": {
          "const": "order",
          "description": "The question's kind."
        },
        "prompt": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the question."
        },
        "situation": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the situation it's set in, shown before the question."
        },
        "explanation": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of why the answer is right, shown after an answer."
        },
        "items": {
          "$ref": "#/$defs/items",
          "description": "The items."
        },
        "answer": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
            "description": "An item ID."
          },
          "description": "The item IDs, in the right order."
        }
      }
    },
    "question-match": {
      "description": "Sort items into groups.",
      "type": "object",
      "required": [
        "id",
        "kind",
        "prompt",
        "items",
        "groups",
        "answer"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The question's ID. Its text IDs start with it, such as q1.prompt."
        },
        "kind": {
          "const": "match",
          "description": "The question's kind."
        },
        "prompt": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the question."
        },
        "situation": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the situation it's set in, shown before the question."
        },
        "explanation": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of why the answer is right, shown after an answer."
        },
        "items": {
          "$ref": "#/$defs/items",
          "description": "The items to sort."
        },
        "groups": {
          "$ref": "#/$defs/items",
          "description": "The groups."
        },
        "answer": {
          "type": "object",
          "additionalProperties": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
            "description": "A group ID."
          },
          "description": "Each item's ID, with the ID of its group."
        }
      }
    },
    "choices": {
      "type": "array",
      "minItems": 2,
      "maxItems": 10,
      "items": {
        "$ref": "#/$defs/choice"
      },
      "description": "2 to 10 choices. Their IDs are all different."
    },
    "choice": {
      "description": "A choice.",
      "type": "object",
      "required": [
        "id",
        "text"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "The choice's ID."
        },
        "text": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of the choice."
        },
        "feedback": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of why this choice is right or wrong, shown after an answer."
        }
      }
    },
    "items": {
      "type": "array",
      "minItems": 2,
      "items": {
        "$ref": "#/$defs/item"
      },
      "description": "At least 2. Their IDs are all different."
    },
    "item": {
      "description": "An item or a group.",
      "type": "object",
      "required": [
        "id",
        "text"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
          "description": "Its ID."
        },
        "text": {
          "type": "string",
          "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
          "description": "The text ID of its words."
        }
      }
    }
  }
}
