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

namespace ArtificersScrollwork.Sidecar.Parsing;

/// <summary>
/// Walks a syntax tree and extracts ClassInfo for every class declaration.
/// </summary>
public class ClassWalker : CSharpSyntaxWalker
{
    private readonly SemanticModel _model;
    private readonly string _filePath;

    public List<ClassInfo> Classes { get; } = new();

    public ClassWalker(SemanticModel model, string filePath)
    {
        _model = model;
        _filePath = filePath;
    }

    public override void VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        var symbol = _model.GetDeclaredSymbol(node);
        if (symbol is null)
        {
            base.VisitClassDeclaration(node);
            return;
        }

        var baseClass = symbol.BaseType?.Name;
        var interfaces = symbol.Interfaces.Select(i => i.Name).ToList();
        var ns = symbol.ContainingNamespace?.ToDisplayString() ?? "";
        var attrs = symbol.GetAttributes().Select(a => a.AttributeClass?.Name ?? "").ToList();

        // Heuristics for class category
        bool isGump = IsGump(symbol);
        bool isMobile = IsMobile(symbol);
        bool isItem = IsItem(symbol);

        // Methods
        var methods = node.Members
            .OfType<MethodDeclarationSyntax>()
            .Select(m => ParseMethod(m, symbol))
            .ToList();

        // Properties
        var properties = node.Members
            .OfType<PropertyDeclarationSyntax>()
            .Select(p => new Models.PropertyInfo(
                p.Identifier.Text,
                p.Type.ToString(),
                p.AccessorList?.Accessors.Any(a => a.IsKind(SyntaxKind.GetAccessorDeclaration)) ?? false,
                p.AccessorList?.Accessors.Any(a => a.IsKind(SyntaxKind.SetAccessorDeclaration)) ?? false
            ))
            .ToList();

        Classes.Add(new ClassInfo(
            symbol.Name,
            ns,
            _filePath,
            baseClass,
            interfaces,
            properties,
            methods,
            attrs,
            isGump,
            isMobile,
            isItem
        ));

        base.VisitClassDeclaration(node);
    }

    private Models.MethodInfo ParseMethod(MethodDeclarationSyntax m, INamedTypeSymbol classSymbol)
    {
        var isOverride = m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.OverrideKeyword));
        var isVirtual  = m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.VirtualKeyword));

        var parameters = m.ParameterList.Parameters
            .Select(p => new ParameterInfo(p.Identifier.Text, p.Type?.ToString() ?? ""))
            .ToList();

        // Check if this method sends a Gump
        bool callsGump = false;
        string? gumpClass = null;

        if (m.Body is not null)
        {
            var sendGumps = m.Body.DescendantNodes()
                .OfType<InvocationExpressionSyntax>()
                .Where(inv => inv.ToString().Contains("SendGump"))
                .FirstOrDefault();

            if (sendGumps is not null)
            {
                callsGump = true;
                // Try to extract the Gump class name from `new XxxGump(...)`
                var newExpr = sendGumps.DescendantNodes()
                    .OfType<ObjectCreationExpressionSyntax>()
                    .FirstOrDefault();
                gumpClass = newExpr?.Type.ToString();
            }
        }

        return new Models.MethodInfo(
            m.Identifier.Text,
            m.ReturnType.ToString(),
            parameters,
            isOverride,
            isVirtual,
            callsGump,
            gumpClass
        );
    }

    private static bool IsGump(INamedTypeSymbol symbol)
    {
        var current = symbol.BaseType;
        while (current is not null)
        {
            if (current.Name is "Gump" or "GumpPlus") return true;
            current = current.BaseType;
        }
        return false;
    }

    private static bool IsMobile(INamedTypeSymbol symbol)
    {
        var current = symbol.BaseType;
        while (current is not null)
        {
            if (current.Name is "Mobile" or "BaseCreature" or "BaseVendor") return true;
            current = current.BaseType;
        }
        return false;
    }

    private static bool IsItem(INamedTypeSymbol symbol)
    {
        var current = symbol.BaseType;
        while (current is not null)
        {
            if (current.Name is "Item" or "BaseWeapon" or "BaseArmor" or "BaseClothing") return true;
            current = current.BaseType;
        }
        return false;
    }
}
