using Theriapolis.Core.Data;

namespace Theriapolis.Tools.Commands;

/// <summary>
/// Phase 6 M4 — quest-validate. Loads every <c>quests/*.json</c> through
/// <see cref="ContentLoader.LoadQuests"/> (which validates structure +
/// references) and runs reachability analysis from each quest's entry
/// step. CI gate: exits non-zero on any error.
///
/// Usage:
///   dotnet run --project Theriapolis.Tools -- quest-validate
///   dotnet run --project Theriapolis.Tools -- quest-validate --data-dir ./Content/Data
/// </summary>
public static class QuestValidate
{
    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]; }
        }

        Console.WriteLine($"Validating quests in: {dataDir}");
        var loader = new ContentLoader(dataDir);
        QuestDef[] quests;
        try
        {
            var items = loader.LoadItems();
            quests = loader.LoadQuests(items);
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"  ✗ {ex.Message}");
            return 1;
        }

        Console.WriteLine($"  ✓ {quests.Length} quest tree(s) loaded.");
        foreach (var q in quests.OrderBy(q => q.Id, StringComparer.Ordinal))
        {
            int outcomes = q.Steps.Sum(s => s.Outcomes.Length);
            int terminals = q.Steps.Count(s => s.CompletesQuest || s.FailsQuest);
            Console.WriteLine($"    [{q.Id}]  '{q.Title}'  steps={q.Steps.Length}  outcomes={outcomes}  terminals={terminals}");

            // Reachability from entry step.
            var stepsById = q.Steps.ToDictionary(s => s.Id, StringComparer.OrdinalIgnoreCase);
            var reached = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { q.EntryStep };
            var queue = new Queue<string>();
            queue.Enqueue(q.EntryStep);
            while (queue.Count > 0)
            {
                var cur = queue.Dequeue();
                if (!stepsById.TryGetValue(cur, out var s)) continue;
                foreach (var o in s.Outcomes)
                {
                    if (string.IsNullOrEmpty(o.Next)) continue;
                    if (string.Equals(o.Next, "<end>", StringComparison.OrdinalIgnoreCase)) continue;
                    if (reached.Add(o.Next)) queue.Enqueue(o.Next);
                }
                // start_quest / end_quest effects can transition out, but we
                // don't follow them here — quest-graph reachability is
                // single-quest by design.
            }
            int unreachable = q.Steps.Length - reached.Count;
            if (unreachable > 0)
            {
                Console.Error.WriteLine($"    ✗ {unreachable} unreachable step(s) in '{q.Id}': "
                    + string.Join(", ", q.Steps.Where(s => !reached.Contains(s.Id)).Select(s => s.Id)));
                return 1;
            }
            // Every quest must have at least one terminal path.
            if (terminals == 0)
                Console.Error.WriteLine($"    ⚠ quest '{q.Id}' has no completes_quest / fails_quest step (warning only)");
        }

        Console.WriteLine();
        Console.WriteLine("All quest trees valid.");
        return 0;
    }
}
