import type { Spell } from '@/lib/compendium/types';
import type { CompendiumEntry } from '@/lib/compendium/types';
import { asDamageType, type AbilityKey, type DamageType, type SpellMechanics, type SpellSave } from '../types';

const ABILITY_WORD: Record<string, AbilityKey> = {
  strength: 'str', dexterity: 'dex', constitution: 'con',
  intelligence: 'int', wisdom: 'wis', charisma: 'cha',
};

/** A truthy "yes"/"true"/boolean flag from the messy SRD fields. */
function flag(v: unknown): boolean {
  if (typeof v === 'boolean') return v;
  if (typeof v === 'string') return /^(yes|true|1)$/i.test(v.trim());
  return false;
}

/** Pull the save ability + basis out of a spell's prose description. */
function parseSave(desc: string): SpellSave | null {
  const m = /(strength|dexterity|constitution|intelligence|wisdom|charisma)\s+saving throw/i.exec(desc);
  if (!m) return null;
  const ability = ABILITY_WORD[m[1]!.toLowerCase()];
  if (!ability) return null;
  const half = /half as much|half the damage|half damage|halve the damage/i.test(desc);
  return { ability, basis: half ? 'half' : 'negates' };
}

/** Best-effort dice/type pairs, e.g. "12d6 fire damage" or "takes 1d8 cold damage". */
function parseDamage(desc: string): { dice: string; type: DamageType }[] {
  const out: { dice: string; type: DamageType }[] = [];
  const seen = new Set<string>();
  const re = /(\d+d\d+)\s+(\w+)\s+damage/gi;
  let m: RegExpExecArray | null;
  while ((m = re.exec(desc)) !== null) {
    const dice = m[1]!.toLowerCase();
    const type = asDamageType(m[2]!);
    if (!type) continue;
    const key = `${dice}:${type}`;
    if (seen.has(key)) continue;
    seen.add(key);
    out.push({ dice, type });
  }
  return out;
}

/** Open5e 5e spell → unified mechanics. Returns null if it lacks a slug/name. */
export function normalizeSpell5e(raw: Spell): SpellMechanics | null {
  if (!raw.slug || !raw.name) return null;
  const desc = raw.desc ?? '';
  return {
    slug: raw.slug,
    name: raw.name,
    level: typeof raw.level_int === 'number' ? raw.level_int : 0,
    school: raw.school ?? '',
    concentration: flag(raw.requires_concentration) || flag(raw.concentration),
    ritual: flag(raw.can_be_cast_as_ritual) || flag(raw.ritual),
    save: parseSave(desc),
    damage: parseDamage(desc),
  };
}

/**
 * PF2e Foundry spell entry → unified mechanics. The pf2e shape nests under
 * `system`; saves are explicit (`system.save.value`) rather than prose-parsed.
 */
export function normalizeSpellPf2e(raw: CompendiumEntry): SpellMechanics | null {
  const name = typeof raw.name === 'string' ? raw.name : '';
  const slug = typeof raw.slug === 'string' ? raw.slug : name.toLowerCase().replace(/\s+/g, '-');
  if (!name || !slug) return null;
  const sys = (raw.system ?? {}) as Record<string, unknown>;
  const level = Number((sys.level as { value?: number } | undefined)?.value ?? 0) || 0;
  const traits = ((sys.traits as { value?: string[] } | undefined)?.value ?? []) as string[];
  const concentration = traits.includes('concentrate') || /sustained/i.test(String((sys.duration as { value?: string } | undefined)?.value ?? ''));
  const saveAbilityWord = String((sys.defense as { save?: { statistic?: string } } | undefined)?.save?.statistic ?? '');
  const saveAbil = ABILITY_WORD[saveAbilityWord.toLowerCase()] ?? pf2eSaveToAbility(saveAbilityWord);
  return {
    slug,
    name,
    level,
    school: traits.find((t) => SCHOOLS.has(t)) ?? '',
    concentration,
    ritual: traits.includes('ritual'),
    save: saveAbil ? { ability: saveAbil, basis: 'half' } : null,
    damage: [],
  };
}

const SCHOOLS = new Set(['abjuration', 'conjuration', 'divination', 'enchantment', 'evocation', 'illusion', 'necromancy', 'transmutation']);

/** pf2e saves are fortitude/reflex/will → their keyed ability. */
function pf2eSaveToAbility(s: string): AbilityKey | undefined {
  const k = s.toLowerCase();
  if (k === 'fortitude') return 'con';
  if (k === 'reflex') return 'dex';
  if (k === 'will') return 'wis';
  return undefined;
}
