using Theriapolis.Core.Data;

namespace Theriapolis.Tools.Commands;

/// <summary>
/// Loads every Phase 5 content file via <see cref="ContentLoader"/>, runs
/// per-file referential integrity checks, and exits non-zero on any error.
/// CI runs this so a broken JSON edit fails the build instead of crashing
/// the game at runtime.
///
/// Usage:
///   dotnet run --project Theriapolis.Tools -- content-validate
///   dotnet run --project Theriapolis.Tools -- content-validate --data-dir ./Content/Data
/// </summary>
public static class ContentValidate
{
    public static int Run(string[] args)
    {
        string dataDir = "./Content/Data";
        for (int i = 0; i < args.Length; i++)
        {
            if (args[i] == "--data-dir" && i + 1 < args.Length)
            {
                dataDir = args[i + 1];
                i++;
            }
        }

        Console.WriteLine($"Validating content in: {dataDir}");
        var loader = new ContentLoader(dataDir);
        int errorCount = 0;

        // Phase 1+ content (already validated elsewhere, but check it loads)
        var biomes   = TryLoad("biomes",   () => loader.LoadBiomes(),   ref errorCount);
        var factions = TryLoad("factions", () => loader.LoadFactions(), ref errorCount);
        TryLoad("macro_template", () => loader.LoadMacroTemplate(), ref errorCount);

        // Phase 5 content
        var clades  = TryLoad("clades",      () => loader.LoadClades(),                ref errorCount);
        var species = clades is not null
            ? TryLoad("species", () => loader.LoadSpecies(clades), ref errorCount)
            : null;
        var classes = TryLoad("classes",     () => loader.LoadClasses(),               ref errorCount);
        if (classes is not null)
            TryLoad("subclasses", () => loader.LoadSubclasses(classes), ref errorCount);
        TryLoad("backgrounds",   () => loader.LoadBackgrounds(),    ref errorCount);
        var items   = TryLoad("items",       () => loader.LoadItems(),                 ref errorCount);
        TryLoad("npc_templates", () => loader.LoadNpcTemplates(items, factions), ref errorCount);
        TryLoad("loot_tables",   () => loader.LoadLootTables(items),   ref errorCount);

        // Phase 6 M0 content
        var buildings = TryLoad("building_templates", () => loader.LoadBuildingTemplates(), ref errorCount);
        var layouts   = buildings is not null
            ? TryLoad("settlement_layouts", () => loader.LoadSettlementLayouts(buildings), ref errorCount)
            : null;

        // Phase 6 M1 content
        var biasProfiles = TryLoad("bias_profiles",
            () => loader.LoadBiasProfiles(clades, factions),
            ref errorCount);
        var residents = TryLoad("resident_templates",
            () => loader.LoadResidentTemplates(biasProfiles, clades, species, factions),
            ref errorCount);

        // Phase 6 M3 content
        var dialogues = TryLoad("dialogues",
            () => loader.LoadDialogues(items),
            ref errorCount);

        // Phase 6 M4 content
        var quests = TryLoad("quests",
            () => loader.LoadQuests(items),
            ref errorCount);

        // Phase 7 M0 content
        var lootTables = items is not null
            ? TryLoad("loot_tables_for_dungeons", () => loader.LoadLootTables(items), ref errorCount)
            : null;
        var roomTemplates = TryLoad("room_templates",
            () => loader.LoadRoomTemplates(),
            ref errorCount);
        var dungeonLayouts = TryLoad("dungeon_layouts",
            () => loader.LoadDungeonLayouts(roomTemplates, lootTables),
            ref errorCount);

        // Cross-file referential checks
        if (clades is not null && species is not null)
        {
            CrossCheck(
                "every species references a real clade",
                () => {
                    var cladeIds = clades.Select(c => c.Id).ToHashSet(StringComparer.OrdinalIgnoreCase);
                    foreach (var sp in species)
                        if (!cladeIds.Contains(sp.CladeId))
                            throw new InvalidDataException($"species '{sp.Id}' references unknown clade_id '{sp.CladeId}'");
                },
                ref errorCount);
        }

        if (errorCount == 0)
        {
            Console.WriteLine();
            Console.WriteLine("All content valid.");
            Console.WriteLine($"  {biomes?.Length  ?? 0} biomes");
            Console.WriteLine($"  {factions?.Length ?? 0} factions");
            Console.WriteLine($"  {clades?.Length  ?? 0} clades");
            Console.WriteLine($"  {species?.Length ?? 0} species");
            Console.WriteLine($"  {classes?.Length ?? 0} classes");
            Console.WriteLine($"  {items?.Length   ?? 0} items");
            Console.WriteLine($"  {buildings?.Length ?? 0} building templates");
            Console.WriteLine($"  {layouts?.Length   ?? 0} settlement layouts");
            Console.WriteLine($"  {biasProfiles?.Length ?? 0} bias profiles");
            Console.WriteLine($"  {residents?.Length    ?? 0} resident templates");
            Console.WriteLine($"  {dialogues?.Length    ?? 0} dialogue trees");
            Console.WriteLine($"  {quests?.Length       ?? 0} quest trees");
            Console.WriteLine($"  {roomTemplates?.Length ?? 0} room templates");
            Console.WriteLine($"  {dungeonLayouts?.Length ?? 0} dungeon layouts");
            return 0;
        }

        Console.Error.WriteLine();
        Console.Error.WriteLine($"Validation failed with {errorCount} error(s).");
        return 1;
    }

    private static T? TryLoad<T>(string label, Func<T> action, ref int errorCount) where T : class
    {
        try
        {
            var result = action();
            Console.WriteLine($"  ✓ {label}");
            return result;
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"  ✗ {label}: {ex.Message}");
            errorCount++;
            return null;
        }
    }

    private static void CrossCheck(string description, Action check, ref int errorCount)
    {
        try
        {
            check();
            Console.WriteLine($"  ✓ {description}");
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"  ✗ {description}: {ex.Message}");
            errorCount++;
        }
    }
}
