namespace Theriapolis.GodotHost.UI;

/// <summary>
/// Per-step validation against a CharacterDraft. Mirrors <c>app.jsx</c>'s
/// <c>validate(i)</c> exactly: returns null when step <c>i</c>'s
/// requirements are met, or a short error message otherwise. Static so
/// the Wizard can ask "is step N satisfied?" without instantiating each
/// step's UI — needed to compute Locked/Pending states across the whole
/// flow, not just the current step.
///
/// React prototype's app.jsx logic:
///   firstIncomplete = STEPS.findIndex(j => validate(j));
///   locked          = i > step && firstIncomplete !== -1 && firstIncomplete < i;
///
/// (i.e. a step is locked iff some EARLIER step fails its validator).
/// </summary>
public static class WizardValidation
{
    public static string? Validate(int step, CharacterDraft draft) => step switch
    {
        0 => ValidateClade(draft),
        1 => ValidateSpecies(draft),
        2 => string.IsNullOrEmpty(draft.ClassId)      ? "Pick a calling."  : null,
        3 => string.IsNullOrEmpty(draft.SubclassId)   ? "Pick a subclass." : null,
        4 => string.IsNullOrEmpty(draft.BackgroundId) ? "Pick a history."  : null,
        5 => draft.StatAssign.Count == 6
            ? null
            : $"Assign all six abilities ({draft.StatAssign.Count}/6).",
        6 => ValidateSkills(draft),
        7 => string.IsNullOrWhiteSpace(draft.CharacterName) ? "Enter a name." : null,
        _ => null,
    };

    private static string? ValidateClade(CharacterDraft draft)
    {
        if (string.IsNullOrEmpty(draft.Sex)) return "Pick a sex.";
        if (draft.IsHybrid)
        {
            if (string.IsNullOrEmpty(draft.SireCladeId)) return "Pick a sire clade.";
            if (string.IsNullOrEmpty(draft.DamCladeId))  return "Pick a dam clade.";
            if (draft.SireCladeId == draft.DamCladeId)
                return "Sire and dam must be different clades.";

            // Pick one ability mod from each parent clade. Stacking on
            // the same ability is allowed (the rule was simplified to
            // permit duplicate picks summing).
            if (string.IsNullOrEmpty(draft.SireChosenAbility))
                return "Pick a lineage bonus from the sire clade.";
            if (string.IsNullOrEmpty(draft.DamChosenAbility))
                return "Pick a lineage bonus from the dam clade.";

            // Phase B: 2 clade traits from dominant + 1 from non-dominant.
            int sireNeed = draft.CladeTraitLimit("sire");
            int damNeed  = draft.CladeTraitLimit("dam");
            if (draft.SireChosenCladeTraits.Count != sireNeed)
                return $"Pick {sireNeed} sire clade trait{(sireNeed == 1 ? "" : "s")} ({draft.SireChosenCladeTraits.Count}/{sireNeed}).";
            if (draft.DamChosenCladeTraits.Count != damNeed)
                return $"Pick {damNeed} dam clade trait{(damNeed == 1 ? "" : "s")} ({draft.DamChosenCladeTraits.Count}/{damNeed}).";

            return null;
        }
        return string.IsNullOrEmpty(draft.CladeId) ? "Pick a clade." : null;
    }

    private static string? ValidateSpecies(CharacterDraft draft)
    {
        if (draft.IsHybrid)
        {
            if (string.IsNullOrEmpty(draft.SireSpeciesId)) return "Pick a sire species.";
            if (string.IsNullOrEmpty(draft.DamSpeciesId))  return "Pick a dam species.";

            // Phase B: one species trait + one species detriment per lineage.
            // A species with an empty detriment list still requires explicit
            // confirmation — UI shows "(none)" affordance.
            if (string.IsNullOrEmpty(draft.SireChosenSpeciesTrait))
                return "Pick a sire species trait.";
            if (string.IsNullOrEmpty(draft.DamChosenSpeciesTrait))
                return "Pick a dam species trait.";

            var sireSp = CodexContent.SpeciesById(draft.SireSpeciesId);
            var damSp  = CodexContent.SpeciesById(draft.DamSpeciesId);
            if (sireSp is not null && sireSp.Detriments.Length > 0
                && string.IsNullOrEmpty(draft.SireChosenSpeciesDetriment))
                return "Pick a sire species detriment.";
            if (damSp is not null && damSp.Detriments.Length > 0
                && string.IsNullOrEmpty(draft.DamChosenSpeciesDetriment))
                return "Pick a dam species detriment.";

            // Lineage-axis variants: each parent species needs an
            // explicit lineage pick when applicable.
            if (sireSp is not null && sireSp.VariantAxis == "lineage"
                && string.IsNullOrEmpty(draft.SireSpeciesVariant))
                return "Pick a sire species lineage.";
            if (damSp is not null && damSp.VariantAxis == "lineage"
                && string.IsNullOrEmpty(draft.DamSpeciesVariant))
                return "Pick a dam species lineage.";

            return null;
        }
        if (string.IsNullOrEmpty(draft.SpeciesId)) return "Pick a species.";
        var purebredSp = CodexContent.SpeciesById(draft.SpeciesId);
        if (purebredSp is not null && purebredSp.VariantAxis == "lineage"
            && string.IsNullOrEmpty(draft.SpeciesVariant))
            return "Pick a species lineage.";
        return null;
    }

    private static string? ValidateSkills(CharacterDraft draft)
    {
        var cls = CodexContent.Class(draft.ClassId);
        int need = cls?.SkillsChoose ?? 0;
        int got = draft.ChosenSkills.Count;
        if (need == 0) return null;  // class has no skill picks (shouldn't happen, defensive)
        if (got == need) return null;
        return $"Pick {need} skill{(need == 1 ? "" : "s")} ({got}/{need}).";
    }

    /// <summary>
    /// Index of the lowest step whose validator currently fails, or -1 when
    /// every step is satisfied. The forward-lock rule is: a step <c>i</c>
    /// after the current step is locked iff <c>FirstIncomplete &lt; i</c>.
    /// </summary>
    public static int FirstIncomplete(CharacterDraft draft, int stepCount = 8)
    {
        for (int i = 0; i < stepCount; i++)
            if (Validate(i, draft) is not null) return i;
        return -1;
    }
}
