using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using ArtificersScrollwork.Sidecar.Models;

namespace ArtificersScrollwork.Sidecar.Parsing;

/// <summary>
/// Traces method call chains across ServUO scripts using Roslyn and produces a FlowNode tree.
/// Entry point: TraceMethod(className, methodName).
/// Max recursion depth: 8 levels.
/// </summary>
public class CallChainTracer
{
    private const int MaxDepth = 8;

    private readonly CSharpCompilation _compilation;
    private readonly Dictionary<string, (ClassDeclarationSyntax Syntax, SemanticModel Model)> _classMap;

    public CallChainTracer(CSharpCompilation compilation)
    {
        _compilation = compilation;
        _classMap = BuildClassMap();
    }

    // ── Public entry point ────────────────────────────────────────────────────

    public FlowNode TraceMethod(string className, string methodName)
    {
        if (!_classMap.TryGetValue(className, out var entry))
            throw new KeyNotFoundException($"Class '{className}' not found");

        var methodSyntax = entry.Syntax.Members
            .OfType<MethodDeclarationSyntax>()
            .FirstOrDefault(m => m.Identifier.Text == methodName)
            ?? throw new KeyNotFoundException($"Method '{methodName}' not found in '{className}'");

        var rootNode = new FlowNode(
            Id: NewId(),
            Type: "method_call",
            Label: $"{className}.{methodName}",
            Children: TraceMethodBody(methodSyntax, entry.Model, depth: 0),
            FakeInputKey: null,
            ResolvedGump: null,
            AssetRef: null
        );

        return rootNode;
    }

    // ── Internal traversal ────────────────────────────────────────────────────

    private List<FlowNode> TraceMethodBody(
        MethodDeclarationSyntax method,
        SemanticModel model,
        int depth)
    {
        if (depth >= MaxDepth) return [];
        if (method.Body is null && method.ExpressionBody is null) return [];

        SyntaxNode body = (SyntaxNode?)method.Body ?? method.ExpressionBody!;
        return TraceStatements(body.ChildNodes(), model, depth);
    }

    private List<FlowNode> TraceStatements(
        IEnumerable<SyntaxNode> nodes,
        SemanticModel model,
        int depth)
    {
        var result = new List<FlowNode>();
        foreach (var node in nodes)
        {
            var children = TraceNode(node, model, depth);
            result.AddRange(children);
        }
        return result;
    }

    private List<FlowNode> TraceNode(SyntaxNode node, SemanticModel model, int depth)
    {
        return node switch
        {
            IfStatementSyntax ifStmt       => [TraceIf(ifStmt, model, depth)],
            SwitchStatementSyntax sw       => [TraceSwitch(sw, model, depth)],
            ReturnStatementSyntax ret      => [TraceReturn(ret, model, depth)],
            ExpressionStatementSyntax expr => TraceExpressionStatement(expr, model, depth),
            LocalDeclarationStatementSyntax local => TraceLocal(local, model, depth),
            ForEachStatementSyntax forEach => [TraceForEach(forEach, model, depth)],
            ForStatementSyntax forStmt     => [TraceFor(forStmt, model, depth)],
            BlockSyntax block              => TraceStatements(block.Statements, model, depth),
            _ => [],
        };
    }

    // ── Statement handlers ────────────────────────────────────────────────────

    private FlowNode TraceIf(IfStatementSyntax ifStmt, SemanticModel model, int depth)
    {
        var conditionText = ifStmt.Condition.ToString().Trim();
        var fakeKey = ExtractFakeInputKey(ifStmt.Condition);

        var trueChildren = TraceNode(ifStmt.Statement, model, depth + 1);
        var falseChildren = ifStmt.Else is not null
            ? TraceNode(ifStmt.Else.Statement, model, depth + 1)
            : [];

        var branches = new List<FlowNode>();
        if (trueChildren.Count > 0)
            branches.Add(new FlowNode(NewId(), "branch_true", "true", trueChildren));
        if (falseChildren.Count > 0)
            branches.Add(new FlowNode(NewId(), "branch_false", "false", falseChildren));

        return new FlowNode(
            Id: NewId(),
            Type: "condition",
            Label: $"if ({Truncate(conditionText, 60)})",
            Children: branches,
            FakeInputKey: fakeKey,
            ResolvedGump: null,
            AssetRef: null
        );
    }

    private FlowNode TraceSwitch(SwitchStatementSyntax sw, SemanticModel model, int depth)
    {
        var conditionText = sw.Expression.ToString().Trim();
        var branches = new List<FlowNode>();

        foreach (var section in sw.Sections)
        {
            var label = string.Join(", ", section.Labels.Select(l => l.ToString().Trim()));
            var sectionChildren = TraceStatements(section.Statements, model, depth + 1);
            if (sectionChildren.Count > 0)
                branches.Add(new FlowNode(NewId(), "branch_case", label, sectionChildren));
        }

        return new FlowNode(
            Id: NewId(),
            Type: "condition",
            Label: $"switch ({Truncate(conditionText, 60)})",
            Children: branches,
            FakeInputKey: ExtractFakeInputKey(sw.Expression),
            ResolvedGump: null,
            AssetRef: null
        );
    }

    private FlowNode TraceReturn(ReturnStatementSyntax ret, SemanticModel model, int depth)
    {
        var label = ret.Expression is not null
            ? $"return {Truncate(ret.Expression.ToString(), 50)}"
            : "return";

        return new FlowNode(NewId(), "return", label, []);
    }

    private List<FlowNode> TraceExpressionStatement(
        ExpressionStatementSyntax expr,
        SemanticModel model,
        int depth)
    {
        return TraceExpression(expr.Expression, model, depth);
    }

    private List<FlowNode> TraceLocal(
        LocalDeclarationStatementSyntax local,
        SemanticModel model,
        int depth)
    {
        // Only interesting if the initializer contains a method call
        var nodes = new List<FlowNode>();
        foreach (var variable in local.Declaration.Variables)
        {
            if (variable.Initializer?.Value is InvocationExpressionSyntax invoc)
                nodes.AddRange(TraceInvocation(invoc, model, depth, label: variable.Identifier.Text + " = …"));
        }
        return nodes;
    }

    private FlowNode TraceForEach(ForEachStatementSyntax forEach, SemanticModel model, int depth)
    {
        var label = $"foreach ({forEach.Type} {forEach.Identifier} in {Truncate(forEach.Expression.ToString(), 40)})";
        var children = TraceNode(forEach.Statement, model, depth + 1);
        return new FlowNode(NewId(), "condition", label, children, FakeInputKey: null);
    }

    private FlowNode TraceFor(ForStatementSyntax forStmt, SemanticModel model, int depth)
    {
        var label = $"for (…; {Truncate(forStmt.Condition?.ToString() ?? "", 40)}; …)";
        var children = TraceNode(forStmt.Statement, model, depth + 1);
        return new FlowNode(NewId(), "condition", label, children, FakeInputKey: null);
    }

    // ── Expression handlers ───────────────────────────────────────────────────

    private List<FlowNode> TraceExpression(ExpressionSyntax expr, SemanticModel model, int depth)
    {
        return expr switch
        {
            InvocationExpressionSyntax invoc         => TraceInvocation(invoc, model, depth),
            AssignmentExpressionSyntax assign        => TraceAssignment(assign, model, depth),
            ConditionalExpressionSyntax ternary      => TraceExpressionList([ternary.WhenTrue, ternary.WhenFalse], model, depth),
            AwaitExpressionSyntax awaitExpr          => TraceExpression(awaitExpr.Expression, model, depth),
            _                                        => [],
        };
    }

    private List<FlowNode> TraceExpressionList(IEnumerable<ExpressionSyntax> exprs, SemanticModel model, int depth)
    {
        return exprs.SelectMany(e => TraceExpression(e, model, depth)).ToList();
    }

    private List<FlowNode> TraceAssignment(AssignmentExpressionSyntax assign, SemanticModel model, int depth)
    {
        return TraceExpression(assign.Right, model, depth);
    }

    private List<FlowNode> TraceInvocation(
        InvocationExpressionSyntax invoc,
        SemanticModel model,
        int depth,
        string? label = null)
    {
        var invocStr = invoc.ToString();

        // ── SendGump check ────────────────────────────────────────────────────
        if (IsSendGump(invocStr))
        {
            var gumpClass = ExtractGumpClass(invoc);
            return [new FlowNode(
                Id: NewId(),
                Type: "gump_send",
                Label: label ?? invocStr.Truncate(80),
                Children: [],
                FakeInputKey: null,
                ResolvedGump: gumpClass,
                AssetRef: null
            )];
        }

        // ── Property / member access (not a real call chain node) ────────────
        if (IsPropertyAccess(invoc))
        {
            return [new FlowNode(NewId(), "property_access", label ?? Truncate(invocStr, 80), [])];
        }

        // ── Try to resolve and recurse into the called method ─────────────────
        if (depth < MaxDepth)
        {
            var calledMethod = TryResolveMethod(invoc, model);
            if (calledMethod is not null)
            {
                var resolvedLabel = label ?? BuildCallLabel(invoc);
                var children = TraceMethodBody(calledMethod.Syntax, calledMethod.Model, depth + 1);
                if (children.Count > 0)
                {
                    return [new FlowNode(NewId(), "method_call", resolvedLabel, children)];
                }
            }
        }

        // ── Leaf call node ────────────────────────────────────────────────────
        var callLabel = label ?? BuildCallLabel(invoc);
        return [new FlowNode(NewId(), "method_call", callLabel, [])];
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    private Dictionary<string, (ClassDeclarationSyntax, SemanticModel)> BuildClassMap()
    {
        var map = new Dictionary<string, (ClassDeclarationSyntax, SemanticModel)>(
            StringComparer.OrdinalIgnoreCase);

        foreach (var tree in _compilation.SyntaxTrees)
        {
            var model = _compilation.GetSemanticModel(tree);
            foreach (var cls in tree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>())
            {
                var symbol = model.GetDeclaredSymbol(cls);
                if (symbol is not null && !map.ContainsKey(symbol.Name))
                    map[symbol.Name] = (cls, model);
            }
        }

        return map;
    }

    private record ResolvedMethod(MethodDeclarationSyntax Syntax, SemanticModel Model);

    private ResolvedMethod? TryResolveMethod(InvocationExpressionSyntax invoc, SemanticModel model)
    {
        try
        {
            var symbolInfo = model.GetSymbolInfo(invoc);
            var symbol = symbolInfo.Symbol as IMethodSymbol;
            if (symbol is null) return null;

            var containingClass = symbol.ContainingType?.Name;
            if (containingClass is null) return null;

            if (!_classMap.TryGetValue(containingClass, out var entry)) return null;

            var methodSyntax = entry.Syntax.Members
                .OfType<MethodDeclarationSyntax>()
                .FirstOrDefault(m => m.Identifier.Text == symbol.Name);

            return methodSyntax is not null ? new ResolvedMethod(methodSyntax, entry.Model) : null;
        }
        catch
        {
            return null;
        }
    }

    private static bool IsSendGump(string invocStr) =>
        invocStr.Contains("SendGump", StringComparison.OrdinalIgnoreCase);

    private static bool IsPropertyAccess(InvocationExpressionSyntax invoc)
    {
        var memberAccess = invoc.Expression as MemberAccessExpressionSyntax;
        return memberAccess?.Name.Identifier.Text is "ToString" or "GetType";
    }

    private static string? ExtractGumpClass(InvocationExpressionSyntax invoc)
    {
        var newExpr = invoc.DescendantNodes().OfType<ObjectCreationExpressionSyntax>().FirstOrDefault();
        return newExpr?.Type.ToString();
    }

    private static string? ExtractFakeInputKey(ExpressionSyntax expr)
    {
        // If the condition references a simple identifier or member access, use it as the key
        return expr switch
        {
            IdentifierNameSyntax id => id.Identifier.Text,
            MemberAccessExpressionSyntax ma => ma.Name.Identifier.Text,
            BinaryExpressionSyntax bin => ExtractFakeInputKey(bin.Left),
            PrefixUnaryExpressionSyntax pre => ExtractFakeInputKey(pre.Operand),
            _ => null,
        };
    }

    private static string BuildCallLabel(InvocationExpressionSyntax invoc)
    {
        var name = invoc.Expression switch
        {
            MemberAccessExpressionSyntax ma => $"{ma.Expression}.{ma.Name}",
            IdentifierNameSyntax id => id.Identifier.Text,
            _ => invoc.Expression.ToString(),
        };
        return Truncate(name, 80);
    }

    private static string Truncate(string s, int max) =>
        s.Length <= max ? s : s[..max] + "…";

    private static int _counter;
    private static string NewId() => $"n{System.Threading.Interlocked.Increment(ref _counter)}";
}

internal static class StringExtensions
{
    public static string Truncate(this string s, int max) =>
        s.Length <= max ? s : s[..max] + "…";
}
