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

namespace ArtificersScrollwork.Sidecar.Parsing;

/// <summary>
/// Walks a ServUO Scripts directory, parses all .cs files with Roslyn,
/// and builds a searchable in-memory index of classes and methods.
/// </summary>
public class ScriptIndexer
{
    private readonly Dictionary<string, ClassInfo> _classes =
        new(StringComparer.OrdinalIgnoreCase);

    /// <summary>The Roslyn compilation built during indexing. Used by CallChainTracer.</summary>
    public CSharpCompilation? Compilation { get; private set; }

    public ScriptIndex IndexDirectory(string path)
    {
        var files = Directory
            .EnumerateFiles(path, "*.cs", SearchOption.AllDirectories)
            .ToList();

        var syntaxTrees = files
            .Select(f =>
            {
                try { return CSharpSyntaxTree.ParseText(File.ReadAllText(f), path: f); }
                catch { return null; }
            })
            .Where(t => t is not null)
            .Select(t => t!)
            .ToList();

        Compilation = CSharpCompilation.Create(
            "ServUOAnalysis",
            syntaxTrees,
            references: null,
            options: new CSharpCompilationOptions(
                OutputKind.DynamicallyLinkedLibrary,
                reportSuppressedDiagnostics: false
            )
        );

        _classes.Clear();

        foreach (var tree in syntaxTrees)
        {
            SemanticModel model;
            try { model = Compilation.GetSemanticModel(tree); }
            catch { continue; }

            var walker = new ClassWalker(model, tree.FilePath);
            try { walker.Visit(tree.GetRoot()); }
            catch { continue; }

            foreach (var cls in walker.Classes)
                _classes[cls.Name] = cls;
        }

        var summaries = _classes.Values
            .Select(c => new ClassSummary(
                c.Name, c.Namespace, c.FilePath, c.BaseClass,
                c.IsGump, c.IsMobile, c.IsItem))
            .OrderBy(c => c.Namespace)
            .ThenBy(c => c.Name)
            .ToList();

        return new ScriptIndex(_classes.Count, files.Count, summaries);
    }

    public ClassInfo? GetClass(string name) =>
        _classes.TryGetValue(name, out var cls) ? cls : null;

    public List<ClassSummary> Search(string query) =>
        _classes.Values
            .Where(c =>
                c.Name.Contains(query, StringComparison.OrdinalIgnoreCase) ||
                c.Namespace.Contains(query, StringComparison.OrdinalIgnoreCase) ||
                c.Methods.Any(m => m.Name.Contains(query, StringComparison.OrdinalIgnoreCase)))
            .Select(c => new ClassSummary(
                c.Name, c.Namespace, c.FilePath, c.BaseClass,
                c.IsGump, c.IsMobile, c.IsItem))
            .Take(100)
            .ToList();
}
