using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Questionable.Model.V1;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Questionable.QuestPathGenerator;

public static class RoslynShortcuts
{
    public static IEnumerable<SyntaxNodeOrToken> SyntaxNodeList(params SyntaxNodeOrToken?[] nodes)
    {
        nodes = nodes.Where(x => x != null).ToArray();
        if (nodes.Length == 0)
            return [];

        List<SyntaxNodeOrToken> list = new();
        for (int i = 0; i < nodes.Length; ++i)
        {
            if (i > 0)
                list.Add(Token(SyntaxKind.CommaToken));
            list.Add(nodes[i].GetValueOrDefault());
        }

        return list;
    }

    public static ExpressionSyntax LiteralValue<T>(T? value)
    {
        if (value is string s)
            return LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(s));
        else if (value is bool b)
            return LiteralExpression(b ? SyntaxKind.TrueLiteralExpression : SyntaxKind.FalseLiteralExpression);
        else if (value is short i16)
            return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(i16));
        else if (value is int i32)
            return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(i32));
        else if (value is ushort u16)
            return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(u16));
        else if (value is uint u32)
            return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(u32));
        else if (value is float f)
            return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(f));
        else if (value != null && value.GetType().IsEnum)
            return MemberAccessExpression(
                SyntaxKind.SimpleMemberAccessExpression,
                IdentifierName(value.GetType().Name),
                IdentifierName(value.GetType().GetEnumName(value)!));
        else if (value is Vector3 vector)
        {
            return ObjectCreationExpression(
                    IdentifierName(nameof(Vector3)))
                .WithArgumentList(
                    ArgumentList(
                        SeparatedList<ArgumentSyntax>(
                            new SyntaxNodeOrToken[]
                            {
                                Argument(LiteralValue(vector.X)),
                                Token(SyntaxKind.CommaToken),
                                Argument(LiteralValue(vector.Y)),
                                Token(SyntaxKind.CommaToken),
                                Argument(LiteralValue(vector.Z))
                            })));
        }
        else if (value is AethernetShortcut aethernetShortcut)
        {
            return ObjectCreationExpression(
                    IdentifierName(nameof(AethernetShortcut)))
                .WithInitializer(
                    InitializerExpression(
                        SyntaxKind.ObjectInitializerExpression,
                        SeparatedList<ExpressionSyntax>(
                            SyntaxNodeList(
                                Assignment<EAetheryteLocation?>(nameof(AethernetShortcut.From), aethernetShortcut.From,
                                        null)
                                    .AsSyntaxNodeOrToken(),
                                Assignment<EAetheryteLocation?>(nameof(AethernetShortcut.To), aethernetShortcut.To,
                                        null)
                                    .AsSyntaxNodeOrToken()))));
        }
        else if (value is ChatMessage chatMessage)
        {
            ChatMessage emptyMessage = new();
            return ObjectCreationExpression(
                    IdentifierName(nameof(ChatMessage)))
                .WithInitializer(
                    InitializerExpression(
                        SyntaxKind.ObjectInitializerExpression,
                        SeparatedList<ExpressionSyntax>(
                            SyntaxNodeList(
                                Assignment(nameof(ChatMessage.ExcelSheet), chatMessage.ExcelSheet,
                                        emptyMessage.ExcelSheet)
                                    .AsSyntaxNodeOrToken(),
                                Assignment(nameof(ChatMessage.Key), chatMessage.Key,
                                        emptyMessage.Key)
                                    .AsSyntaxNodeOrToken()))));
        }
        else if (value is DialogueChoice dialogueChoice)
        {
            DialogueChoice emptyChoice = new();
            return ObjectCreationExpression(
                    IdentifierName(nameof(DialogueChoice)))
                .WithInitializer(
                    InitializerExpression(
                        SyntaxKind.ObjectInitializerExpression,
                        SeparatedList<ExpressionSyntax>(
                            SyntaxNodeList(
                                Assignment<EDialogChoiceType?>(nameof(DialogueChoice.Type), dialogueChoice.Type, null)
                                    .AsSyntaxNodeOrToken(),
                                Assignment(nameof(DialogueChoice.ExcelSheet), dialogueChoice.ExcelSheet,
                                        emptyChoice.ExcelSheet)
                                    .AsSyntaxNodeOrToken(),
                                Assignment(nameof(DialogueChoice.Prompt), dialogueChoice.Prompt, emptyChoice.Prompt)
                                    .AsSyntaxNodeOrToken(),
                                Assignment(nameof(DialogueChoice.Yes), dialogueChoice.Yes, emptyChoice.Yes)
                                    .AsSyntaxNodeOrToken(),
                                Assignment(nameof(DialogueChoice.Answer), dialogueChoice.Answer, emptyChoice.Answer)
                                    .AsSyntaxNodeOrToken(),
                                Assignment(nameof(DialogueChoice.DataId), dialogueChoice.DataId, emptyChoice.DataId)
                                    .AsSyntaxNodeOrToken()))));
        }
        else if (value is JumpDestination jumpDestination)
        {
            return ObjectCreationExpression(
                    IdentifierName(nameof(JumpDestination)))
                .WithInitializer(
                    InitializerExpression(
                        SyntaxKind.ObjectInitializerExpression,
                        SeparatedList<ExpressionSyntax>(
                            SyntaxNodeList(
                                Assignment<Vector3?>(nameof(JumpDestination.Position), jumpDestination.Position, null)
                                    .AsSyntaxNodeOrToken(),
                                Assignment(nameof(JumpDestination.StopDistance), jumpDestination.StopDistance, null)
                                    .AsSyntaxNodeOrToken(),
                                Assignment(nameof(JumpDestination.DelaySeconds), jumpDestination.DelaySeconds, null)
                                    .AsSyntaxNodeOrToken()))));
        }
        else if (value is ExcelRef excelRef)
        {
            if (excelRef.Type == ExcelRef.EType.Key)
            {
                return ObjectCreationExpression(
                        IdentifierName(nameof(ExcelRef)))
                    .WithArgumentList(
                        ArgumentList(
                            SingletonSeparatedList(
                                Argument(LiteralValue(excelRef.AsKey())))));
            }
            else if (excelRef.Type == ExcelRef.EType.RowId)
            {
                return ObjectCreationExpression(
                        IdentifierName(nameof(ExcelRef)))
                    .WithArgumentList(
                        ArgumentList(
                            SingletonSeparatedList(
                                Argument(LiteralValue(excelRef.AsRowId())))));
            }
            else
                throw new Exception($"Unsupported ExcelRef type {excelRef.Type}");
        }
        else if (value is null)
            return LiteralExpression(SyntaxKind.NullLiteralExpression);
        else
            throw new Exception($"Unsupported data type {value.GetType()} = {value}");
    }

    public static AssignmentExpressionSyntax? Assignment<T>(string name, T? value, T? defaultValue)
    {
        if (value == null && defaultValue == null)
            return null;

        if (value != null && defaultValue != null && value.Equals(defaultValue))
            return null;

        return AssignmentExpression(
            SyntaxKind.SimpleAssignmentExpression,
            IdentifierName(name),
            LiteralValue(value));
    }

    public static AssignmentExpressionSyntax? AssignmentList<T>(string name, IEnumerable<T> value)
    {
        IEnumerable<T> list = value.ToList();
        if (!list.Any())
            return null;

        return AssignmentExpression(
            SyntaxKind.SimpleAssignmentExpression,
            IdentifierName(name),
            CollectionExpression(
                SeparatedList<CollectionElementSyntax>(
                    SyntaxNodeList(list.Select(x => ExpressionElement(
                        LiteralValue(x)).AsSyntaxNodeOrToken()).ToArray())
                )));
    }

    public static SyntaxNodeOrToken? AsSyntaxNodeOrToken(this SyntaxNode? node)
    {
        if (node == null)
            return null;

        return node;
    }
}