/**
 * Structured game mechanics derived from the loosely-typed compendium data.
 *
 * The raw SRD/Foundry records (src/lib/compendium/types.ts) stay as-is; these
 * are the *mechanical* projections the rules-enforcement kernel reads. One
 * unified model across 5e and pf2e — per-system divergence is handled inside
 * the normalizers, and genuinely system-specific fields are optional.
 */

export type AbilityKey = 'str' | 'dex' | 'con' | 'int' | 'wis' | 'cha';

/** Damage types across both systems. 5e core + the pf2e additions + 'untyped'. */
export const DAMAGE_TYPES = [
  'acid', 'bludgeoning', 'cold', 'fire', 'force', 'lightning', 'necrotic',
  'piercing', 'poison', 'psychic', 'radiant', 'slashing', 'thunder',
  // pf2e
  'bleed', 'mental', 'spirit', 'vitality', 'void', 'precision',
  'untyped',
] as const;
export type DamageType = (typeof DAMAGE_TYPES)[number];

const DAMAGE_SET = new Set<string>(DAMAGE_TYPES);
/** Narrow an arbitrary string to a DamageType, or undefined if unrecognized. */
export function asDamageType(s: string): DamageType | undefined {
  const k = s.trim().toLowerCase();
  return DAMAGE_SET.has(k) ? (k as DamageType) : undefined;
}

/** How a spell's saving throw resolves. */
export interface SpellSave {
  ability: AbilityKey;
  /** 'negates' = save avoids the effect; 'half' = save halves the damage. */
  basis: 'negates' | 'half';
}

export interface SpellMechanics {
  slug: string;
  name: string;
  /** 0 = cantrip */
  level: number;
  school: string;
  concentration: boolean;
  ritual: boolean;
  /** parsed from the description; null when there is no save or it is unclear */
  save: SpellSave | null;
  /** best-effort dice/type pairs parsed from the description (not enforced yet) */
  damage: { dice: string; type: DamageType }[];
}

export interface ArmorMechanics {
  name: string;
  category: 'light' | 'medium' | 'heavy' | 'shield' | 'unarmored';
  /** for shields this is the flat AC bonus (e.g. +2) */
  baseAc: number;
  /** null = no cap on the Dex bonus; 0 = Dex does not apply (heavy) */
  dexCap: number | null;
  stealthDisadvantage: boolean;
}

/**
 * Effective AC from a worn armor + Dex modifier, optionally adding a shield and
 * miscellaneous bonus. Pure; used by the sheet and combat. Unarmored callers
 * should pass `{ baseAc: 10, dexCap: null }`-style mechanics.
 */
export function effectiveArmorClass(
  armor: Pick<ArmorMechanics, 'baseAc' | 'dexCap'>,
  dexMod: number,
  shieldBonus = 0,
  miscBonus = 0,
): number {
  const dex = armor.dexCap === null ? dexMod : Math.min(dexMod, armor.dexCap);
  return armor.baseAc + dex + shieldBonus + miscBonus;
}

export interface DamageDefenses {
  resist: DamageType[];
  immune: DamageType[];
  vulnerable: DamageType[];
  /** condition slugs the creature is immune to (lowercased) */
  conditionImmune: string[];
  /** qualified clauses we could not cleanly parse — surfaced to the GM, never enforced */
  notes: string[];
}

/** Advantage state used by the combat condition-effects engine (a later phase). */
export type AdvState = 'normal' | 'advantage' | 'disadvantage';
