{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "wfrp4-character-schema",
  "title": "WFRP4 Character",
  "description": "Warhammer Fantasy Roleplay 4th Edition character data. Stores raw inputs; derived values (skill totals, wound points, etc.) are calculated by the app.",
  "type": "object",
  "required": ["name", "species", "career", "characteristics", "skills", "talents", "experience"],
  "properties": {
    "name": { "type": "string" },
    "species": {
      "type": "string",
      "enum": ["Human", "Dwarf", "Halfling", "High Elf", "Wood Elf"]
    },
    "subspecies": {
      "type": "object",
      "description": "Species-specific details (kingdom, bloodline, etc.)",
      "properties": {
        "kingdom": { "type": "string" },
        "region": { "type": "string" },
        "bloodline": { "type": "string" },
        "madness": {
          "type": "object",
          "description": "Blood of Aenarion madness (High Elf only)",
          "properties": {
            "name": { "type": "string" },
            "roll": { "type": "integer", "minimum": 1, "maximum": 10 },
            "effects": {
              "type": "object",
              "description": "Characteristic modifiers from the madness",
              "patternProperties": {
                "^(WS|BS|S|T|I|Ag|Dex|Int|WP|Fel)$": { "type": "integer" }
              },
              "additionalProperties": false
            },
            "description": { "type": "string" }
          },
          "required": ["name", "roll", "effects"]
        }
      }
    },
    "gender": { "type": "string" },
    "age": { "type": "integer", "minimum": 0 },
    "born": { "type": "string", "description": "Approximate birth date in IC" },
    "height": { "type": "string" },
    "hair": { "type": "string" },
    "eyes": { "type": "string" },

    "career": {
      "type": "object",
      "required": ["class", "name", "level", "levelName", "status", "path", "skills", "talents"],
      "properties": {
        "class": {
          "type": "string",
          "enum": ["Academics", "Burghers", "Courtiers", "Peasants", "Rangers", "Riverfolk", "Rogues", "Warriors"]
        },
        "name": { "type": "string" },
        "level": { "type": "integer", "minimum": 1, "maximum": 5 },
        "levelName": { "type": "string" },
        "status": {
          "type": "object",
          "required": ["tier", "standing"],
          "properties": {
            "tier": { "type": "string", "enum": ["Brass", "Silver", "Gold"] },
            "standing": { "type": "integer", "minimum": 0, "maximum": 7 }
          }
        },
        "path": {
          "type": "array",
          "description": "All career levels in order",
          "items": {
            "type": "object",
            "required": ["level", "name", "status"],
            "properties": {
              "level": { "type": "integer" },
              "name": { "type": "string" },
              "status": { "type": "string" },
              "characteristics": {
                "type": "array",
                "description": "Characteristic advances available at this career level",
                "items": {
                  "type": "string",
                  "enum": ["WS", "BS", "S", "T", "I", "Ag", "Dex", "Int", "WP", "Fel"]
                }
              }
            }
          }
        },
        "skills": {
          "type": "array",
          "description": "Skills available at the current career level",
          "items": { "type": "string" }
        },
        "talents": {
          "type": "array",
          "description": "Talents available at the current career level",
          "items": { "type": "string" }
        }
      }
    },

    "elder": {
      "type": "object",
      "description": "Per HE Player's Guide p. 52: Elder Asur prior careers and per-Time burdens. Also reused by openChangeCareer to record former careers (those entries carry level/levelName/class).",
      "properties": {
        "era": {
          "type": "string",
          "enum": ["Time of Voyages", "Time of Incursion", "Time of Steel", "Time of Ending"],
          "description": "The Time the character was born in. Burdens accumulate from this Time forward."
        },
        "careers": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["name", "skills"],
            "properties": {
              "name": { "type": "string" },
              "source": {
                "type": "string",
                "description": "Per HE PG p. 52: 'Random' (15 advances) or 'Chosen' (10 advances). May also describe a former career added when changing careers."
              },
              "advances": {
                "type": "integer",
                "minimum": 0,
                "description": "Skill advances granted by this Elder career (10 chosen, 15 random; max 5 per single skill per Time)."
              },
              "levelName": { "type": "string", "description": "Set when this entry is a former career recorded by openChangeCareer." },
              "level": { "type": "integer", "minimum": 1, "maximum": 5 },
              "class": { "type": "string" },
              "skills": {
                "type": "object",
                "description": "Skill name -> advances allocated.",
                "patternProperties": {
                  ".+": { "type": "integer", "minimum": 0 }
                }
              }
            }
          }
        },
        "burdens": {
          "type": "object",
          "description": "Cumulative Burdens per HE PG p. 52 (Yenlui Burdens are not cumulative — earliest applies).",
          "properties": {
            "corruption": { "type": "integer", "minimum": 0, "description": "Starting Corruption (Time of Incursion adds 5)." },
            "extraPoints": { "type": "string", "description": "Free-form note about Fate/Resilience reduction (Time of Steel: 1 instead of 2)." },
            "endeavoursCap": { "type": "integer", "description": "Max endeavours per downtime (Time of Voyages: 1)." },
            "yenlui": { "type": "string", "description": "Earliest Yenlui difficulty (Time of Voyages: Easy +40 Light; Time of Steel: Challenging +0 Light, etc.)." }
          }
        }
      }
    },

    "characteristics": {
      "type": "object",
      "description": "Raw characteristic inputs. Initial = roll + speciesBase. Current = Initial + freeAdvances. Effective = Current + auto-derived talent bonuses (Coolheaded, Savvy, Hollow Heart, etc.).",
      "patternProperties": {
        "^(WS|BS|S|T|I|Ag|Dex|Int|WP|Fel)$": {
          "type": "object",
          "required": ["roll", "speciesBase", "freeAdvances"],
          "properties": {
            "roll": { "type": "integer", "minimum": 1, "maximum": 20 },
            "speciesBase": { "type": "integer", "enum": [20, 30, 40] },
            "freeAdvances": { "type": "integer", "minimum": 0 }
          }
        }
      },
      "additionalProperties": false
    },

    "fate": {
      "type": "object",
      "required": ["base", "total"],
      "properties": {
        "base": { "type": "integer", "minimum": 0 },
        "bloodOfAenarion": { "type": "integer", "minimum": 0 },
        "total": { "type": "integer", "minimum": 0 }
      }
    },
    "fortune": {
      "oneOf": [
        { "type": "integer", "minimum": 0 },
        { "type": "object", "required": ["max", "current"], "properties": { "max": { "type": "integer", "minimum": 0 }, "current": { "type": "integer", "minimum": 0 } } }
      ]
    },
    "resilience": { "type": "integer", "minimum": 0 },
    "resolve": {
      "oneOf": [
        { "type": "integer", "minimum": 0 },
        { "type": "object", "required": ["max", "current"], "properties": { "max": { "type": "integer", "minimum": 0 }, "current": { "type": "integer", "minimum": 0 } } }
      ]
    },
    "movement": { "type": "integer", "minimum": 1 },
    "currentWounds": { "type": "integer", "minimum": 0, "description": "Current wound points (max is calculated from characteristics)" },

    "skills": {
      "type": "object",
      "description": "Skill advances by source. The app sums all sources for a skill and adds the governing characteristic to get the effective value.",
      "required": ["species", "career"],
      "properties": {
        "species": {
          "type": "object",
          "patternProperties": { ".+": { "type": "integer", "minimum": 0 } }
        },
        "career": {
          "type": "object",
          "description": "Advances from the current career. 0 means career skill with no advances yet.",
          "patternProperties": { ".+": { "type": "integer", "minimum": 0 } }
        }
      }
    },

    "skillDefinitions": {
      "type": "object",
      "description": "Maps skill base names to their governing characteristic, type (basic/advanced), and whether they are grouped. This is reference data used by the app for calculations.",
      "patternProperties": {
        ".+": {
          "type": "object",
          "required": ["characteristic", "type"],
          "properties": {
            "characteristic": {
              "type": "string",
              "enum": ["WS", "BS", "S", "T", "I", "Ag", "Dex", "Int", "WP", "Fel"]
            },
            "type": {
              "type": "string",
              "enum": ["basic", "advanced"]
            },
            "grouped": { "type": "boolean", "default": false }
          }
        }
      }
    },

    "talents": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["name", "source", "timesTaken"],
        "properties": {
          "name": { "type": "string" },
          "source": { "type": "string", "description": "Where this talent came from (Species, Career, Blood of Aenarion, etc.)" },
          "timesTaken": { "type": "integer", "minimum": 1 },
          "max": { "description": "Maximum times this talent can be taken", "oneOf": [{ "type": "integer" }, { "type": "string" }] },
          "description": { "type": "string" }
        }
      }
    },

    "spells": {
      "type": "object",
      "description": "Spells keyed by lore name (e.g. 'Petty', 'High Magic', 'Heavens')",
      "additionalProperties": { "type": "array", "items": { "$ref": "#/$defs/spell" } }
    },

    "prayers": {
      "type": "object",
      "description": "Blessings and miracles known by the character.",
      "properties": {
        "blessings": { "type": "array", "items": { "$ref": "#/$defs/prayer" } },
        "miracles": { "type": "array", "items": { "$ref": "#/$defs/prayer" } }
      }
    },

    "mutations": {
      "type": "array",
      "description": "Physical and mental mutations. Descriptions matching 'Permanent +/-X to STAT' auto-apply to characteristics.",
      "items": {
        "type": "object",
        "required": ["name", "type"],
        "properties": {
          "name": { "type": "string" },
          "type": { "type": "string", "enum": ["physical", "mental"] },
          "description": { "type": "string" }
        }
      }
    },

    "injuries": {
      "type": "array",
      "description": "Critical injuries by hit location. Effect field with '+/-X STAT' patterns auto-applies to characteristics when not healed.",
      "items": {
        "type": "object",
        "required": ["name", "location"],
        "properties": {
          "name": { "type": "string" },
          "location": { "type": "string", "enum": ["Head", "Left Arm", "Right Arm", "Body", "Left Leg", "Right Leg"] },
          "description": { "type": "string", "description": "Description of the injury and its non-stat effects" },
          "effect": { "type": "string", "description": "Stat effects, e.g. '-10 WS, -20 Ag'" },
          "daysRemaining": { "type": "integer", "minimum": 0, "description": "Days until recovery. When 0 and not permanent, injury is recovered and penalties lift." },
          "permanent": { "type": "boolean", "default": false, "description": "Permanent injury (amputation). Penalties never lift." }
        }
      }
    },

    "diseases": {
      "type": "array",
      "description": "Active diseases affecting the character.",
      "items": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": { "type": "string" },
          "symptoms": { "type": "array", "items": { "type": "string" } },
          "daysRemaining": { "type": "integer", "minimum": 0 },
          "notes": { "type": "string" }
        }
      }
    },

    "corruption": {
      "type": "object",
      "required": ["current"],
      "properties": {
        "current": { "type": "integer", "minimum": 0 },
        "source": { "type": "string" }
      }
    },

    "psychology": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": { "type": "string" },
          "description": { "type": "string" }
        }
      }
    },

    "ambitions": {
      "type": "object",
      "properties": {
        "shortTerm": {
          "type": "object",
          "properties": {
            "type": { "type": "string" },
            "description": { "type": "string" },
            "benefit": { "type": "string" }
          }
        },
        "longTerm": {
          "type": "object",
          "properties": {
            "type": { "type": "string" },
            "description": { "type": "string" },
            "benefit": { "type": "string" },
            "negative": { "type": "string" },
            "note": { "type": "string" }
          }
        }
      }
    },

    "trappings": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": { "type": "string" },
          "enc": { "type": "integer", "minimum": 0 },
          "source": { "type": "string" },
          "contains": { "type": "array", "items": { "type": "string" } }
        }
      }
    },

    "weapons": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["name", "group"],
        "properties": {
          "name": { "type": "string" },
          "group": { "type": "string" },
          "enc": { "type": "integer", "minimum": 0 },
          "reach": { "type": "string" },
          "range": { "type": "string" },
          "damage": { "type": "string" },
          "qualities": { "type": "array", "items": { "type": "string" } }
        }
      }
    },

    "armour": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["name", "locations", "ap"],
        "properties": {
          "name": { "type": "string" },
          "locations": {
            "type": "array",
            "items": { "type": "string", "enum": ["Head", "Left Arm", "Right Arm", "Body", "Left Leg", "Right Leg"] }
          },
          "enc": { "type": "integer", "minimum": 0 },
          "ap": { "type": "integer", "minimum": 0 },
          "magical": { "type": "boolean", "default": false, "description": "Magical armour cannot be bypassed by non-magical attacks that ignore AP" },
          "qualities": { "type": "array", "items": { "type": "string" } }
        }
      }
    },

    "wealth": {
      "type": "object",
      "properties": {
        "gold": { "type": "integer", "minimum": 0 },
        "silver": { "type": "integer", "minimum": 0 },
        "brass": { "type": "integer", "minimum": 0 }
      }
    },

    "experience": {
      "type": "object",
      "required": ["earned", "spent", "current"],
      "properties": {
        "earned": { "type": "integer", "minimum": 0 },
        "spent": { "type": "integer", "minimum": 0 },
        "current": { "type": "integer", "minimum": 0 },
        "log": {
          "type": "array",
          "description": "Ordered history of all XP transactions. Each entry records what changed so the full history is recoverable.",
          "items": {
            "type": "object",
            "required": ["amount", "type", "reason"],
            "properties": {
              "amount": { "type": "integer", "minimum": 0 },
              "type": { "type": "string", "enum": ["earned", "spent"] },
              "reason": { "type": "string", "description": "Human-readable description" },
              "date": { "type": "string", "format": "date", "description": "Date when this happened (YYYY-MM-DD)" },
              "change": {
                "type": "object",
                "description": "What specifically changed. Keys describe the change type.",
                "properties": {
                  "skill": { "type": "string", "description": "Skill name, e.g. 'Channelling (Azyr)'" },
                  "from": { "type": "integer", "description": "Previous advance count" },
                  "to": { "type": "integer", "description": "New advance count" },
                  "characteristic": { "type": "string", "description": "Characteristic name for stat advances" },
                  "talent": { "type": "string", "description": "Talent name if purchasing a talent" },
                  "spell": { "type": "string", "description": "Spell name if learning a spell" },
                  "spellCategory": { "type": "string", "description": "Lore name the spell belongs to" }
                }
              }
            }
          }
        }
      }
    },

    "yenlui": {
      "type": "object",
      "description": "High Elf Yenlui (emotional balance) tracking. Tests are derived by the app from age and bloodline.",
      "properties": {
        "currentState": {
          "type": "string",
          "enum": ["Balanced", "Light", "Dark"]
        }
      }
    },

    "conditions": {
      "type": "object",
      "description": "Active conditions with their stack count. Only present conditions are listed.",
      "patternProperties": {
        "^(Ablaze|Bleeding|Blinded|Broken|Deafened|Entangled|Fatigued|Poisoned|Stunned|Prone|Surprised|Unconscious)$": {
          "type": "integer", "minimum": 1
        }
      },
      "additionalProperties": false
    },
    "advantage": {
      "type": "integer",
      "minimum": 0,
      "description": "Current Advantage count. Capped at Initiative Bonus by the app."
    },
    "temporaryModifiers": {
      "type": "array",
      "description": "Temporary modifiers from spells, injuries, potions, mutations, etc. Each modifier targets a characteristic, a specific skill, or all Tests.",
      "items": {
        "type": "object",
        "required": ["name", "target", "value"],
        "properties": {
          "name": { "type": "string", "description": "Source of the modifier (e.g. 'Torn Muscle', 'Healing Potion', 'Elven Cloak')" },
          "target": {
            "type": "string",
            "description": "What this modifies: a characteristic (WS, BS, etc.), a skill name, or 'All' for all Tests"
          },
          "value": { "type": "integer", "description": "Modifier value (positive or negative)" },
          "ap": { "type": "object", "description": "AP deltas this modifier applies, e.g. { \"all\": 1 } or { \"Head\": 1 }", "additionalProperties": { "type": "number" } },
          "fromSpell": { "type": "string", "description": "Display name of the spell that created this buff (activation link)" }
        }
      }
    },

    "sin": { "type": "integer", "minimum": 0 },

    "settingLanguage": {
      "type": "string",
      "description": "The common language of the setting that all characters speak fluently (e.g. 'Reikspiel' in the Empire). Displayed as a basic Language skill with no advances needed.",
      "default": "Reikspiel"
    },

    "notes": {
      "type": "object",
      "description": "Freeform key-value notes",
      "patternProperties": {
        ".+": { "type": "string" }
      }
    }
  },

  "$defs": {
    "spell": {
      "type": "object",
      "required": ["name"],
      "description": "Minimal spell reference. All other data (CN, range, effect, etc.) is resolved from spells-full.json at render time.",
      "properties": {
        "name": { "type": "string", "description": "Display name (custom or original)" },
        "originalName": { "type": "string", "description": "Rulebook name, used as lookup key if name differs" },
        "favorite": { "type": "boolean", "description": "Pinned to the Favorites tab in the Spells UI" }
      }
    },
    "prayer": {
      "type": "object",
      "required": ["name"],
      "properties": {
        "name": { "type": "string" },
        "range": { "type": "string" },
        "target": { "type": "string" },
        "duration": { "type": "string" },
        "description": { "type": "string" }
      }
    }
  }
}
