using Inspectron.Epson.Templates.Language.Nodes;

namespace Inspectron.Epson.Templates.Language;

public class Parser
{
    private readonly List<Token> _tokens;
    private int _position;
    private readonly List<ParseError> _errors = new();

    public Parser(List<Token> tokens)
    {
        _tokens = tokens ?? throw new ArgumentNullException(nameof(tokens));
    }

    public ParseResult Parse()
    {
        _position = 0;
        _errors.Clear();

        var nodes = ParseNodeList(TokenType.EndOfFile);
        return new ParseResult(new TemplateNode(nodes), _errors.ToList());
    }

    private List<ITemplateNode> ParseNodeList(params TokenType[] terminators)
    {
        var nodes = new List<ITemplateNode>();

        while (!IsAtEnd() && !terminators.Contains(Current.Type))
        {
            var node = ParseNode();
            if (node != null)
            {
                nodes.Add(node);
            }
        }

        return nodes;
    }

    private ITemplateNode? ParseNode()
    {
        var token = Current;

        switch (token.Type)
        {
            case TokenType.NewLine:
                Advance();
                if(token.Position.Column==1)
                    return new NewLineNode(token.Position);
                else
                    return null;

            case TokenType.Text:
                return ParseText();

            case TokenType.StyleStart:
                return ParseStyledText();

            case TokenType.Binding:
                return ParseBinding();

            case TokenType.DashSeparator:
                Advance();
                return new SeparatorNode(SeparatorStyle.Dash, token.Position);

            case TokenType.EqualsSeparator:
                Advance();
                return new SeparatorNode(SeparatorStyle.Equals, token.Position);

            case TokenType.StarSeparator:
                Advance();
                return new SeparatorNode(SeparatorStyle.Star, token.Position);

            case TokenType.TildeSeparator:
                Advance();
                return new SeparatorNode(SeparatorStyle.Tilde, token.Position);

            case TokenType.If:
                return ParseIf();

            case TokenType.Foreach:
                return ParseForeach();

            case TokenType.Row:
                return ParseRow();

            case TokenType.Column:
                return ParseColumn();

            case TokenType.ElseIf:
            case TokenType.Else:
            case TokenType.End:
            case TokenType.EndRow:
                // These are handled by parent parsers; don't consume here
                return null;

            case TokenType.StyleEnd:
                // Orphan style end - skip with error
                AddError("Unexpected style end marker '#' without matching style start", token.Position);
                Advance();
                return null;

            case TokenType.EndOfFile:
                return null;

            default:
                AddError($"Unexpected token type: {token.Type}", token.Position);
                Advance();
                return null;
        }
    }

    private TextNode ParseText()
    {
        var token = Current;
        Advance();
        return new TextNode(token.Value, token.Position);
    }

    private BindingNode ParseBinding()
    {
        var token = Current;
        Advance();
        return BindingNode.Parse(token.Value, token.Position);
    }

    private StyledTextNode ParseStyledText()
    {
        var startToken = Current;
        var styles = startToken.Value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
            .ToList();
        Advance(); // consume StyleStart

        var contentNodes = new List<ITemplateNode>();

        // Parse content until StyleEnd or NewLine
        while (!IsAtEnd() && Current.Type != TokenType.StyleEnd && Current.Type != TokenType.NewLine)
        {
            if (Current.Type == TokenType.Text)
            {
                contentNodes.Add(ParseText());
            }
            else if (Current.Type == TokenType.Binding)
            {
                contentNodes.Add(ParseBinding());
            }
            else
            {
                // Unexpected token inside styled text
                AddError($"Unexpected token inside styled text: {Current.Type}", Current.Position);
                Advance();
            }
        }

        // Consume StyleEnd if present
        if (Current.Type == TokenType.StyleEnd)
        {
            Advance();
        }

        return new StyledTextNode(styles, contentNodes, startToken.Position);
    }

    private IfNode ParseIf()
    {
        var ifToken = Current;
        var ifCondition = ifToken.Value;
        Advance(); // consume @if

        // Skip newline after @if
        SkipNewlines();

        var ifBody = ParseNodeList(TokenType.ElseIf, TokenType.Else, TokenType.End, TokenType.EndOfFile);

        var elseIfBranches = new List<ConditionBranch>();
        List<ITemplateNode>? elseBranch = null;

        // Parse @elseif branches
        while (Current.Type == TokenType.ElseIf)
        {
            var elseIfToken = Current;
            var elseIfCondition = elseIfToken.Value;
            Advance(); // consume @elseif
            SkipNewlines();

            var elseIfBody = ParseNodeList(TokenType.ElseIf, TokenType.Else, TokenType.End, TokenType.EndOfFile);
            elseIfBranches.Add(new ConditionBranch(elseIfCondition, elseIfBody, elseIfToken.Position));
        }

        // Parse @else branch
        if (Current.Type == TokenType.Else)
        {
            Advance(); // consume @else
            SkipNewlines();

            elseBranch = ParseNodeList(TokenType.End, TokenType.EndOfFile);
        }

        // Expect @end
        if (Current.Type == TokenType.End)
        {
            Advance(); // consume @end
        }
        else
        {
            AddError("Expected @end to close @if block", Current.Position);
        }

        return new IfNode(
            new ConditionBranch(ifCondition, ifBody, ifToken.Position),
            elseIfBranches,
            elseBranch,
            ifToken.Position);
    }

    private ForeachNode ParseForeach()
    {
        var foreachToken = Current;
        Advance(); // consume @foreach

        string itemVariable;
        string collectionPath;

        try
        {
            (itemVariable, collectionPath) = ForeachNode.ParseForeach(foreachToken.Value);
        }
        catch (FormatException ex)
        {
            AddError(ex.Message, foreachToken.Position);
            itemVariable = "item";
            collectionPath = "items";
        }

        SkipNewlines();

        var body = ParseNodeList(TokenType.End, TokenType.EndOfFile);

        if (Current.Type == TokenType.End)
        {
            Advance(); // consume @end
        }
        else
        {
            AddError("Expected @end to close @foreach block", Current.Position);
        }

        return new ForeachNode(itemVariable, collectionPath, body, foreachToken.Position);
    }

    private RowNode ParseRow()
    {
        var rowToken = Current;
        var styles = string.IsNullOrWhiteSpace(rowToken.Value)
            ? Array.Empty<string>()
            : rowToken.Value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        Advance(); // consume @row
        SkipNewlines();

        var columns = new List<ColumnNode>();

        // Parse columns until @endrow
        while (!IsAtEnd() && Current.Type != TokenType.EndRow && Current.Type != TokenType.EndOfFile)
        {
            if (Current.Type == TokenType.Column)
            {
                columns.Add(ParseColumnDef());
            }
            else if (Current.Type == TokenType.NewLine)
            {
                Advance();
            }
            else
            {
                // Content outside of column definition - create implicit column
                var implicitContent = new List<ITemplateNode>();
                while (!IsAtEnd() &&
                       Current.Type != TokenType.Column &&
                       Current.Type != TokenType.EndRow &&
                       Current.Type != TokenType.NewLine &&
                       Current.Type != TokenType.EndOfFile)
                {
                    var node = ParseNode();
                    if (node != null) implicitContent.Add(node);
                }

                if (implicitContent.Count > 0)
                {
                    columns.Add(new ColumnNode(0, ColumnAlignment.Left, implicitContent, Current.Position));
                }
            }
        }

        if (Current.Type == TokenType.EndRow)
        {
            Advance(); // consume @endrow
        }
        else
        {
            AddError("Expected @endrow to close @row block", Current.Position);
        }

        return new RowNode(columns, styles, rowToken.Position);
    }

    private ColumnNode ParseColumnDef()
    {
        var columnToken = Current;
        var (width, alignment) = ColumnNode.ParseColumnSpec(columnToken.Value);
        Advance(); // consume column token

        var content = new List<ITemplateNode>();

        // Parse content until next column, newline, or endrow
        while (!IsAtEnd() &&
               Current.Type != TokenType.Column &&
               Current.Type != TokenType.EndRow &&
               Current.Type != TokenType.NewLine &&
               Current.Type != TokenType.EndOfFile)
        {
            var node = ParseNode();
            if (node != null) content.Add(node);
        }

        return new ColumnNode(width, alignment, content, columnToken.Position);
    }

    private ColumnNode? ParseColumn()
    {
        // Standalone column outside of @row
        var columnToken = Current;
        var (width, alignment) = ColumnNode.ParseColumnSpec(columnToken.Value);
        Advance();

        var content = new List<ITemplateNode>();

        while (!IsAtEnd() &&
               Current.Type != TokenType.Column &&
               Current.Type != TokenType.NewLine &&
               Current.Type != TokenType.EndOfFile)
        {
            var node = ParseNode();
            if (node != null) content.Add(node);
        }

        return new ColumnNode(width, alignment, content, columnToken.Position);
    }

    private void SkipNewlines()
    {
        while (Current.Type == TokenType.NewLine)
        {
            Advance();
        }
    }

    private Token Current => _position < _tokens.Count ? _tokens[_position] : Token.Eof(_tokens.Count);

    private bool IsAtEnd() => _position >= _tokens.Count || Current.Type == TokenType.EndOfFile;

    private void Advance()
    {
        if (!IsAtEnd()) _position++;
    }

    private void AddError(string message, SourcePosition position)
    {
        _errors.Add(new ParseError(message, position));
    }
}

public record ParseResult(TemplateNode Template, List<ParseError> Errors)
{
    public bool HasErrors => Errors.Count > 0;
}

public record ParseError(string Message, SourcePosition Position);
