/**
 * Pre-cast validation gates. Runs before any resource deduction or roll.
 * Returns structured ValidationResult; caller decides whether to block,
 * warn, or proceed with bypass flags.
 */

import type { CastableActor, SpellLike, CastOptions } from './spell-cast';

export interface ValidationIssue {
  code: string;
  message: string;
  i18nKey?: string;
  i18nArgs?: Record<string, unknown>;
}

export interface ValidationResult {
  errors: ValidationIssue[];   // hard blocks
  warnings: ValidationIssue[]; // soft, may be bypassed
  ok: boolean;                  // errors.length === 0
}

function getActorRaceId(actor: CastableActor): string {
  // Prefer embedded race item; fall back to details.raceId / details.race.
  const raceItem = (actor as any).items?.find?.((i: any) => i.type === 'race');
  if (raceItem) {
    return String(raceItem.system?.raceId ?? raceItem.system?.id ?? raceItem.name ?? '').toLowerCase();
  }
  const d = (actor as any).system?.details ?? {};
  return String(d.raceId ?? d.race ?? '').toLowerCase();
}

function actorHasTalent(actor: CastableActor, talentId: string): boolean {
  const id = talentId.toLowerCase();
  const items = (actor as any).items as Iterable<any> | undefined;
  if (!items) return false;
  for (const it of items) {
    if (it.type !== 'talent') continue;
    const t = String(it.system?.talentId ?? it.system?.id ?? it.name ?? '').toLowerCase();
    if (t === id) return true;
  }
  return false;
}

function actorHasDiscipline(actor: CastableActor, disciplineId: string): boolean {
  const id = disciplineId.toLowerCase();
  const items = (actor as any).items as Iterable<any> | undefined;
  if (!items) return false;
  for (const it of items) {
    if (it.type !== 'discipline') continue;
    const d = String(it.system?.disciplineId ?? it.system?.id ?? it.name ?? '').toLowerCase();
    if (d === id) return true;
  }
  // Also accept characters whose details.discipline matches
  const detail = String((actor as any).system?.details?.discipline ?? '').toLowerCase();
  return detail === id;
}

function actorIsInCombat(actor: CastableActor): boolean {
  const combat = (game as any).combat;
  if (!combat?.started) return false;
  return Boolean(combat.combatants?.find?.((c: any) => c.actorId === actor.id));
}

export function validateCast(actor: CastableActor, spell: SpellLike, opts: CastOptions = {}): ValidationResult {
  const errors: ValidationIssue[] = [];
  const warnings: ValidationIssue[] = [];

  const sys = spell.system as any;
  const a = (actor as any).system?.attributes ?? {};
  const mode = sys.castingMode ?? 'standard';

  // Resource gates
  const baseMana = Math.max(0, Number(sys.manaCost ?? 0));
  const manaSpent = Math.max(baseMana, Math.floor(opts.manaSpent ?? baseMana));
  const bloodSpent = Math.max(0, Math.floor(opts.bloodSpent ?? Number(sys.bloodCost ?? 0)));

  if (mode === 'standard' || mode === 'witch' || (mode === 'sacred' && opts.hekateMode === 'witch')) {
    if (manaSpent > Number(a.mana?.value ?? 0)) {
      errors.push({ code: 'no-mana', message: 'Not enough mana', i18nKey: 'HBM.spellCast.notEnoughMana' });
    }
  }
  if (mode === 'sacred' && opts.hekateMode !== 'witch') {
    const zealNeeded = Math.max(1, opts.zealSpent ?? 1);
    if (zealNeeded > Number(a.zeal?.value ?? 0)) {
      errors.push({ code: 'no-zeal', message: 'Not enough zeal', i18nKey: 'HBM.spellCast.notEnoughZeal' });
    }
  }
  if (mode === 'blood') {
    if (bloodSpent > Number(a.blood?.value ?? 0)) {
      errors.push({ code: 'no-blood', message: 'Not enough blood', i18nKey: 'HBM.spellCast.notEnoughBlood' });
    }
  }

  // Race gate
  const raceReq: string[] = Array.isArray(sys.requirements?.race) ? sys.requirements.race : [];
  if (raceReq.length > 0) {
    const actorRace = getActorRaceId(actor);
    const ok = raceReq.some((r) => r.toLowerCase() === actorRace);
    if (!ok) {
      const translatedRaces = raceReq.map(r => {
        const key = r.toLowerCase();
        return game.i18n.has(`HBM.racesList.${key}`) ? game.i18n.localize(`HBM.racesList.${key}`) : r;
      }).join(', ');
      errors.push({
        code: 'race-locked', message: `Spell requires race: ${raceReq.join(', ')}`,
        i18nKey: 'HBM.spellCast.raceLocked', i18nArgs: { races: translatedRaces },
      });
    }
  }

  // Talent gate
  const talentReq: string[] = Array.isArray(sys.requirements?.talent) ? sys.requirements.talent : [];
  for (const t of talentReq) {
    if (!actorHasTalent(actor, t)) {
      errors.push({
        code: 'talent-missing', message: `Missing required talent: ${t}`,
        i18nKey: 'HBM.spellCast.talentMissing', i18nArgs: { talent: t },
      });
    }
  }

  // Discipline gate (any-of)
  const discReq: string[] = Array.isArray(sys.requirements?.discipline) ? sys.requirements.discipline : [];
  if (discReq.length > 0) {
    const ok = discReq.some((d) => actorHasDiscipline(actor, d));
    if (!ok) {
      const translatedDisciplines = discReq.map(d => {
        return game.i18n.has(`HBM.spellSchool.${d}`) ? game.i18n.localize(`HBM.spellSchool.${d}`) : d;
      }).join(', ');
      errors.push({
        code: 'discipline-missing', message: `Requires one of disciplines: ${discReq.join(', ')}`,
        i18nKey: 'HBM.spellCast.disciplineMissing', i18nArgs: { disciplines: translatedDisciplines },
      });
    }
  }

  // Deity gate (sacred + non-common)
  const deity = String(sys.deity ?? '').trim();
  if ((sys.school === 'sacred' || mode === 'sacred') && deity && deity !== 'common') {
    const actorDeity = String((actor as any).system?.details?.deity ?? '').trim();
    if (actorDeity && actorDeity !== deity) {
      warnings.push({
        code: 'deity-mismatch',
        message: `Spell deity (${deity}) differs from devoted deity (${actorDeity})`,
        i18nKey: 'HBM.spellCast.deityMismatch', i18nArgs: { spell: deity, actor: actorDeity },
      });
    }
  }

  // Group cast
  if (sys.requiresGroupCast) {
    const min = Math.max(1, Number(sys.minCasters ?? 1));
    const provided = (opts.groupCasters?.length ?? 0) + 1;
    if (provided < min) {
      errors.push({
        code: 'group-cast', message: `Requires ${min} co-casters; have ${provided}`,
        i18nKey: 'HBM.spellCast.groupCastRequired', i18nArgs: { min, provided },
      });
    }
  }

  // Witch symbol cap
  if (mode === 'witch' || opts.hekateMode === 'witch') {
    const required = Math.max(0, Number(sys.complexityLevel ?? sys.components?.symbols?.length ?? 0));
    const maxSymbols = Math.ceil(Number(a.magic?.value ?? 0) / 2);
    if (required > maxSymbols) {
      errors.push({
        code: 'witch-symbols', message: `Symbols ${required}/${maxSymbols}`,
        i18nKey: 'HBM.spellCast.witchSymbols', i18nArgs: { used: required, max: maxSymbols },
      });
    }
  }

  // Non-combat-only spells
  if (sys.nonCombatOnly && actorIsInCombat(actor) && !opts.bypassNonCombatBlock) {
    errors.push({
      code: 'non-combat', message: 'Spell cannot be cast in combat',
      i18nKey: 'HBM.spellCast.nonCombatOnly',
    });
  }

  // Superspell warning (non-blocking)
  if (sys.isSuperspell && actorIsInCombat(actor) && !opts.bypassSuperspellWarning) {
    warnings.push({
      code: 'superspell-combat',
      message: 'Casting superspell during combat is generally inadvisable',
      i18nKey: 'HBM.spellCast.superspellInCombat',
    });
  }

  return { errors, warnings, ok: errors.length === 0 };
}
