using System.Globalization;
using NubLang.Ast;
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;

namespace NubLang.LSP;

internal class HoverHandler(WorkspaceManager workspaceManager) : HoverHandlerBase
{
    protected override HoverRegistrationOptions CreateRegistrationOptions(HoverCapability capability, ClientCapabilities clientCapabilities)
    {
        return new HoverRegistrationOptions
        {
            DocumentSelector = TextDocumentSelector.ForLanguage("nub")
        };
    }

    public override Task<Hover?> Handle(HoverParams request, CancellationToken cancellationToken)
    {
        return Task.FromResult(HandleSync(request, cancellationToken));
    }

    private Hover? HandleSync(HoverParams request, CancellationToken cancellationToken)
    {
        var compilationUnit = workspaceManager.GetCompilationUnit(request.TextDocument.Uri);
        if (compilationUnit == null)
        {
            return null;
        }

        var line = request.Position.Line;
        var character = request.Position.Character;

        var hoveredNode = compilationUnit.DeepestNodeAtPosition(line, character);

        if (hoveredNode == null)
        {
            return null;
        }

        var message = CreateMessage(hoveredNode, compilationUnit);
        if (message == null)
        {
            return null;
        }

        return new Hover
        {
            Contents = new MarkedStringsOrMarkupContent(new MarkupContent
            {
                Value = message,
                Kind = MarkupKind.Markdown,
            })
        };
    }

    private static string? CreateMessage(Node hoveredNode, CompilationUnit compilationUnit)
    {
        return hoveredNode switch
        {
            FuncNode funcNode => CreateFuncPrototypeMessage(funcNode.Prototype),
            FuncPrototypeNode funcPrototypeNode => CreateFuncPrototypeMessage(funcPrototypeNode),
            FuncIdentifierNode funcIdentifierNode => CreateFuncIdentifierMessage(funcIdentifierNode, compilationUnit),
            FuncParameterNode funcParameterNode => CreateTypeNameMessage("Function parameter", funcParameterNode.Name, funcParameterNode.Type),
            VariableIdentifierNode variableIdentifierNode => CreateTypeNameMessage("Variable", variableIdentifierNode.Name, variableIdentifierNode.Type),
            VariableDeclarationNode variableDeclarationNode => CreateTypeNameMessage("Variable declaration", variableDeclarationNode.Name, variableDeclarationNode.Type),
            StructFieldAccessNode structFieldAccessNode => CreateTypeNameMessage("Struct field", $"{structFieldAccessNode.Target.Type}.{structFieldAccessNode.Field}", structFieldAccessNode.Type),
            CStringLiteralNode cStringLiteralNode => CreateLiteralMessage(cStringLiteralNode.Type, '"' + cStringLiteralNode.Value + '"'),
            StringLiteralNode stringLiteralNode => CreateLiteralMessage(stringLiteralNode.Type, '"' + stringLiteralNode.Value + '"'),
            BoolLiteralNode boolLiteralNode => CreateLiteralMessage(boolLiteralNode.Type, boolLiteralNode.Value.ToString()),
            Float32LiteralNode float32LiteralNode => CreateLiteralMessage(float32LiteralNode.Type, float32LiteralNode.Value.ToString(CultureInfo.InvariantCulture)),
            Float64LiteralNode float64LiteralNode => CreateLiteralMessage(float64LiteralNode.Type, float64LiteralNode.Value.ToString(CultureInfo.InvariantCulture)),
            I8LiteralNode i8LiteralNode => CreateLiteralMessage(i8LiteralNode.Type, i8LiteralNode.Value.ToString()),
            I16LiteralNode i16LiteralNode => CreateLiteralMessage(i16LiteralNode.Type, i16LiteralNode.Value.ToString()),
            I32LiteralNode i32LiteralNode => CreateLiteralMessage(i32LiteralNode.Type, i32LiteralNode.Value.ToString()),
            I64LiteralNode i64LiteralNode => CreateLiteralMessage(i64LiteralNode.Type, i64LiteralNode.Value.ToString()),
            U8LiteralNode u8LiteralNode => CreateLiteralMessage(u8LiteralNode.Type, u8LiteralNode.Value.ToString()),
            U16LiteralNode u16LiteralNode => CreateLiteralMessage(u16LiteralNode.Type, u16LiteralNode.Value.ToString()),
            U32LiteralNode u32LiteralNode => CreateLiteralMessage(u32LiteralNode.Type, u32LiteralNode.Value.ToString()),
            U64LiteralNode u64LiteralNode => CreateLiteralMessage(u64LiteralNode.Type, u64LiteralNode.Value.ToString()),
            // Expressions can have a generic fallback showing the resulting type
            ExpressionNode expressionNode => $"""
                                              **Expression** `{expressionNode.GetType().Name}`
                                              ```nub
                                              {expressionNode.Type}
                                              ```
                                              """,
            BlockNode => null,
            _ => hoveredNode.GetType().Name
        };
    }

    private static string CreateLiteralMessage(NubType type, string value)
    {
        return $"""
                **Literal** `{type}`
                ```nub
                {value}: {type}
                ```
                """;
    }

    private static string CreateTypeNameMessage(string description, string name, NubType type)
    {
        return $"""
                **{description}** `{name}`
                ```nub
                {name}: {type}
                ```
                """;
    }

    private static string CreateFuncIdentifierMessage(FuncIdentifierNode funcIdentifierNode, CompilationUnit compilationUnit)
    {
        var func = compilationUnit.ImportedFunctions.FirstOrDefault(x => x.Module == funcIdentifierNode.Module && x.Name == funcIdentifierNode.Name);
        if (func == null)
        {
            return $"""
                    **Function** `{funcIdentifierNode.Module}::{funcIdentifierNode.Name}`
                    ```nub
                    // Declaration not found
                    ```
                    """;
        }

        return CreateFuncPrototypeMessage(func);
    }

    private static string CreateFuncPrototypeMessage(FuncPrototypeNode funcPrototypeNode)
    {
        var parameterText = string.Join(", ", funcPrototypeNode.Parameters.Select(x => $"{x.Name}: {x.Type}"));
        var externText = funcPrototypeNode.ExternSymbol != null ? $"extern \"{funcPrototypeNode.ExternSymbol}\" " : "";

        return $"""
                **Function** `{funcPrototypeNode.Module}::{funcPrototypeNode.Name}`
                ```nub
                {externText}func {funcPrototypeNode.Module}::{funcPrototypeNode.Name}({parameterText}): {funcPrototypeNode.ReturnType}
                ```
                """;
    }
}