// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System;
using System.IO;

namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;

internal class SyntaxToken : RazorSyntaxNode
{
    private readonly GreenNode _leadingTrivia;
    private readonly GreenNode _trailingTrivia;

    internal SyntaxToken(SyntaxKind kind, string content, RazorDiagnostic[] diagnostics)
        : base(kind, content.Length, diagnostics, annotations: null)
    {
        Content = content;
    }

    internal SyntaxToken(SyntaxKind kind, string content, GreenNode leadingTrivia, GreenNode trailingTrivia)
        : base(kind, content.Length)
    {
        Content = content;
        _leadingTrivia = leadingTrivia;
        AdjustFlagsAndWidth(leadingTrivia);
        _trailingTrivia = trailingTrivia;
        AdjustFlagsAndWidth(trailingTrivia);
    }

    internal SyntaxToken(SyntaxKind kind, string content, GreenNode leadingTrivia, GreenNode trailingTrivia, RazorDiagnostic[] diagnostics, SyntaxAnnotation[] annotations)
        : base(kind, content.Length, diagnostics, annotations)
    {
        Content = content;
        _leadingTrivia = leadingTrivia;
        AdjustFlagsAndWidth(leadingTrivia);
        _trailingTrivia = trailingTrivia;
        AdjustFlagsAndWidth(trailingTrivia);
    }

    public string Content { get; }

    public SyntaxList<GreenNode> LeadingTrivia
    {
        get { return new SyntaxList<GreenNode>(GetLeadingTrivia()); }
    }

    public SyntaxList<GreenNode> TrailingTrivia
    {
        get { return new SyntaxList<GreenNode>(GetTrailingTrivia()); }
    }

    internal override bool IsToken => true;

    public override int Width => Content.Length;

    internal override SyntaxNode CreateRed(SyntaxNode parent, int position)
    {
        return new Syntax.SyntaxToken(this, parent, position);
    }

    protected override void WriteTokenTo(TextWriter writer, bool leading, bool trailing)
    {
        if (leading)
        {
            var trivia = GetLeadingTrivia();
            if (trivia != null)
            {
                trivia.WriteTo(writer, true, true);
            }
        }

        writer.Write(Content);

        if (trailing)
        {
            var trivia = GetTrailingTrivia();
            if (trivia != null)
            {
                trivia.WriteTo(writer, true, true);
            }
        }
    }

    public sealed override GreenNode GetLeadingTrivia()
    {
        return _leadingTrivia;
    }

    public override int GetLeadingTriviaWidth()
    {
        return _leadingTrivia == null ? 0 : _leadingTrivia.FullWidth;
    }

    public sealed override GreenNode GetTrailingTrivia()
    {
        return _trailingTrivia;
    }

    public override int GetTrailingTriviaWidth()
    {
        return _trailingTrivia == null ? 0 : _trailingTrivia.FullWidth;
    }

    public sealed override GreenNode WithLeadingTrivia(GreenNode trivia)
    {
        return TokenWithLeadingTrivia(trivia);
    }

    public virtual SyntaxToken TokenWithLeadingTrivia(GreenNode trivia)
    {
        return new SyntaxToken(Kind, Content, trivia, _trailingTrivia, GetDiagnostics(), GetAnnotations());
    }

    public sealed override GreenNode WithTrailingTrivia(GreenNode trivia)
    {
        return TokenWithTrailingTrivia(trivia);
    }

    public virtual SyntaxToken TokenWithTrailingTrivia(GreenNode trivia)
    {
        return new SyntaxToken(Kind, Content, _leadingTrivia, trivia, GetDiagnostics(), GetAnnotations());
    }

    internal override GreenNode SetDiagnostics(RazorDiagnostic[] diagnostics)
    {
        return new SyntaxToken(Kind, Content, _leadingTrivia, _trailingTrivia, diagnostics, GetAnnotations());
    }

    internal override GreenNode SetAnnotations(SyntaxAnnotation[] annotations)
    {
        return new SyntaxToken(Kind, Content, _leadingTrivia, _trailingTrivia, GetDiagnostics(), annotations);
    }

    protected sealed override int GetSlotCount()
    {
        return 0;
    }

    internal sealed override GreenNode GetSlot(int index)
    {
        throw new InvalidOperationException("Tokens don't have slots.");
    }

    public override TResult Accept<TResult>(SyntaxVisitor<TResult> visitor)
    {
        return visitor.VisitToken(this);
    }

    public override void Accept(SyntaxVisitor visitor)
    {
        visitor.VisitToken(this);
    }

    public override bool IsEquivalentTo(GreenNode other)
    {
        if (!base.IsEquivalentTo(other))
        {
            return false;
        }

        var otherToken = (SyntaxToken)other;

        if (Content != otherToken.Content)
        {
            return false;
        }

        var thisLeading = GetLeadingTrivia();
        var otherLeading = otherToken.GetLeadingTrivia();
        if (thisLeading != otherLeading)
        {
            if (thisLeading == null || otherLeading == null)
            {
                return false;
            }

            if (!thisLeading.IsEquivalentTo(otherLeading))
            {
                return false;
            }
        }

        var thisTrailing = GetTrailingTrivia();
        var otherTrailing = otherToken.GetTrailingTrivia();
        if (thisTrailing != otherTrailing)
        {
            if (thisTrailing == null || otherTrailing == null)
            {
                return false;
            }

            if (!thisTrailing.IsEquivalentTo(otherTrailing))
            {
                return false;
            }
        }

        return true;
    }

    public override string ToString()
    {
        return Content;
    }

    internal static SyntaxToken CreateMissing(SyntaxKind kind, params RazorDiagnostic[] diagnostics)
    {
        return new MissingToken(kind, diagnostics);
    }

    private class MissingToken : SyntaxToken
    {
        internal MissingToken(SyntaxKind kind, RazorDiagnostic[] diagnostics)
            : base(kind, string.Empty, diagnostics)
        {
            Flags |= NodeFlags.IsMissing;
        }

        internal MissingToken(SyntaxKind kind, GreenNode leading, GreenNode trailing, RazorDiagnostic[] diagnostics, SyntaxAnnotation[] annotations)
            : base(kind, string.Empty, leading, trailing, diagnostics, annotations)
        {
            Flags |= NodeFlags.IsMissing;
        }
    }
}
