using System.Globalization;
using System.Text;
using System.Text.Json;
using Inspectron.Epson.PrintServer.Printers.Utils;
using Inspectron.Epson.Templates.Configuration;
using Inspectron.Epson.Templates.Language;
using Inspectron.Epson.Templates.Language.Nodes;

namespace Inspectron.Epson.Templates.Interpreter;

public class TemplateInterpreter
{
    private readonly PrinterProfile _profile;
    private readonly ExpressionEvaluator _evaluator;
    private readonly FormatResolver _formatter;

    public TemplateInterpreter(PrinterProfile profile, CultureInfo? culture = null)
    {
        _profile = profile ?? throw new ArgumentNullException(nameof(profile));
        _evaluator = new ExpressionEvaluator();
        _formatter = new FormatResolver(culture);
    }

    public List<PrintCommand> Interpret(TemplateNode template, string jsonData)
    {
        var document = JsonDocument.Parse(jsonData);
        var context = new DataContext(document.RootElement);
        return Interpret(template, context);
    }

    public List<PrintCommand> Interpret(TemplateNode template, DataContext context)
    {
        var commands = new List<PrintCommand>();
        InterpretNodes(template.ChildNodes, context, commands, new StyleContext());
        return commands;
    }

    private void InterpretNodes(
        IReadOnlyList<ITemplateNode> nodes,
        DataContext context,
        List<PrintCommand> commands,
        StyleContext style)
    {
        var lineBuffer = new StringBuilder();
        var hasInlineContent = false;

        void FlushLineBuffer()
        {
            if (hasInlineContent)
            {
                var text = ApplyAlignment(lineBuffer.ToString(), style);
                commands.Add(CreateCommand(text, style));
                lineBuffer.Clear();
                hasInlineContent = false;
            }
        }

        foreach (var node in nodes)
        {
            switch (node)
            {
                // Inline nodes - accumulate into line buffer
                case TextNode textNode:
                    lineBuffer.Append(textNode.Text);
                    hasInlineContent = true;
                    break;

                case BindingNode bindingNode:
                    var element = context.Resolve(bindingNode.Path);
                    lineBuffer.Append(_formatter.Format(element, bindingNode.Format));
                    hasInlineContent = true;
                    break;

                // Styled text - flush buffer, emit styled content, continue accumulating
                case StyledTextNode styledNode:
                    
                    InterpretStyledText(styledNode, context, commands, style);
                    break;

                // Block nodes - flush buffer first, then process
                case SeparatorNode separatorNode:
                    
                    InterpretSeparator(separatorNode, commands, style);
                    break;

                case NewLineNode:
                    FlushLineBuffer();
                    commands.Add(CreateCommand(string.Empty, style));
                    break;

                case IfNode ifNode:
                    
                    InterpretIf(ifNode, context, commands, style);
                    break;

                case ForeachNode foreachNode:
                    
                    InterpretForeach(foreachNode, context, commands, style);
                    break;

                case RowNode rowNode:
                    
                    InterpretRow(rowNode, context, commands, style);
                    break;

                case ColumnNode columnNode:
                    
                    InterpretColumn(columnNode, context, commands, style);
                    break;
            }
        }

        // Flush any remaining content
        FlushLineBuffer();
    }

    private void InterpretStyledText(
        StyledTextNode node,
        DataContext context,
        List<PrintCommand> commands,
        StyleContext style)
    {
        // Create new style context with inherited and new styles
        var newStyle = style.Clone();
        ApplyStyles(node.Styles, newStyle);

        // Build content string from child nodes
        var contentBuilder = new StringBuilder();
        foreach (var child in node.ContentNodes)
        {
            switch (child)
            {
                case TextNode textNode:
                    contentBuilder.Append(textNode.Text);
                    break;
                case BindingNode bindingNode:
                    var element = context.Resolve(bindingNode.Path);
                    contentBuilder.Append(_formatter.Format(element, bindingNode.Format));
                    break;
            }
        }

        var text = contentBuilder.ToString();
        text = ApplyAlignment(text, newStyle);
        commands.Add(CreateCommand(text, newStyle));
    }

    private void InterpretSeparator(
        SeparatorNode node,
        List<PrintCommand> commands,
        StyleContext style)
    {
        var width = style.IsBig ? _profile.BigLineWidth : _profile.LineWidth;
        var ch = node.Style switch
        {
            SeparatorStyle.Dash => '-',
            SeparatorStyle.Equals => '=',
            SeparatorStyle.Star => '*',
            SeparatorStyle.Tilde => '~',
            _ => '-'
        };
        var line = new string(ch, width);
        commands.Add(CreateCommand(line, style));
    }

    private void InterpretIf(
        IfNode node,
        DataContext context,
        List<PrintCommand> commands,
        StyleContext style)
    {
        // Evaluate @if condition
        if (_evaluator.Evaluate(node.IfBranch.Condition, context))
        {
            InterpretNodes(node.IfBranch.Body, context, commands, style);
            return;
        }

        // Evaluate @elseif conditions
        foreach (var branch in node.ElseIfBranches)
        {
            if (_evaluator.Evaluate(branch.Condition, context))
            {
                InterpretNodes(branch.Body, context, commands, style);
                return;
            }
        }

        // Execute @else branch if present
        if (node.ElseBranch != null)
        {
            InterpretNodes(node.ElseBranch, context, commands, style);
        }
    }

    private void InterpretForeach(
        ForeachNode node,
        DataContext context,
        List<PrintCommand> commands,
        StyleContext style)
    {
        var collection = context.ResolveCollection(node.CollectionPath).ToList();
        var count = collection.Count;

        for (int i = 0; i < count; i++)
        {
            var item = collection[i];
            var childContext = context.CreateChildContext(node.ItemVariable, item, i, count);
            InterpretNodes(node.Body, childContext, commands, style);
        }
    }

    private void InterpretRow(
        RowNode node,
        DataContext context,
        List<PrintCommand> commands,
        StyleContext style)
    {
        // Apply row-level styles
        var rowStyle = style.Clone();
        ApplyStyles(node.Styles, rowStyle);

        var lineWidth = rowStyle.IsBig ? _profile.BigLineWidth : _profile.LineWidth;
        var rowBuilder = new StringBuilder();

        foreach (var column in node.Columns)
        {
            // Build column content
            var columnContent = new StringBuilder();
            foreach (var child in column.ContentNodes)
            {
                switch (child)
                {
                    case TextNode textNode:
                        columnContent.Append(textNode.Text);
                        break;
                    case BindingNode bindingNode:
                        var element = context.Resolve(bindingNode.Path);
                        columnContent.Append(_formatter.Format(element, bindingNode.Format));
                        break;
                    case StyledTextNode styledNode:
                        foreach (var styledChild in styledNode.ContentNodes)
                        {
                            if (styledChild is TextNode st)
                                columnContent.Append(st.Text);
                            else if (styledChild is BindingNode sb)
                            {
                                var elem = context.Resolve(sb.Path);
                                columnContent.Append(_formatter.Format(elem, sb.Format));
                            }
                        }
                        break;
                }
            }

            var content = columnContent.ToString();
            var width = column.Width > 0 ? column.Width : content.Length;

            // Apply column alignment
            var aligned = column.Alignment switch
            {
                ColumnAlignment.Right => content.PadLeft(width),
                ColumnAlignment.Center => CenterText(content, width),
                _ => content.PadRight(width)
            };

            // Truncate if too long
            if (aligned.Length > width && width > 0)
            {
                aligned = aligned[..width];
            }

            rowBuilder.Append(aligned);
        }

        commands.Add(CreateCommand(rowBuilder.ToString(), rowStyle));
    }

    private void InterpretColumn(
        ColumnNode node,
        DataContext context,
        List<PrintCommand> commands,
        StyleContext style)
    {
        // Standalone column - just output content with width constraints
        var contentBuilder = new StringBuilder();
        foreach (var child in node.ContentNodes)
        {
            switch (child)
            {
                case TextNode textNode:
                    contentBuilder.Append(textNode.Text);
                    break;
                case BindingNode bindingNode:
                    var element = context.Resolve(bindingNode.Path);
                    contentBuilder.Append(_formatter.Format(element, bindingNode.Format));
                    break;
            }
        }

        var content = contentBuilder.ToString();
        var width = node.Width > 0 ? node.Width : content.Length;

        var aligned = node.Alignment switch
        {
            ColumnAlignment.Right => content.PadLeft(width),
            ColumnAlignment.Center => CenterText(content, width),
            _ => content.PadRight(width)
        };

        if (aligned.Length > width && width > 0)
        {
            aligned = aligned[..width];
        }

        commands.Add(CreateCommand(aligned, style));
    }

    private void ApplyStyles(IReadOnlyList<string> styles, StyleContext styleContext)
    {
        foreach (var style in styles)
        {
            var lower = style.ToLowerInvariant();

            if (lower.StartsWith("spacing:"))
            {
                if (int.TryParse(lower[8..], out var spacing))
                {
                    styleContext.LineSpacing = spacing;
                }
                continue;
            }

            switch (lower)
            {
                case "bold":
                    styleContext.IsBold = true;
                    break;
                case "big":
                    styleContext.IsBig = true;
                    break;
                case "tall":
                    styleContext.IsTall = true;
                    break;
                case "red":
                    styleContext.IsRed = true;
                    break;
                case "center":
                    styleContext.Alignment = TextAlignment.Center;
                    break;
                case "right":
                    styleContext.Alignment = TextAlignment.Right;
                    break;
                case "left":
                    styleContext.Alignment = TextAlignment.Left;
                    break;
            }
        }
    }

    private string ApplyAlignment(string text, StyleContext style)
    {
        if (style.Alignment == TextAlignment.Left)
        {
            return text;
        }

        var width = style.IsBig ? _profile.BigLineWidth : _profile.LineWidth;

        return style.Alignment switch
        {
            TextAlignment.Center => CenterText(text, width),
            TextAlignment.Right => text.PadLeft(width),
            _ => text
        };
    }

    private string CenterText(string text, int width)
    {
        if (string.IsNullOrEmpty(text) || text.Length >= width)
        {
            return text;
        }

        var totalPadding = width - text.Length;
        var leftPadding = totalPadding / 2;
        var rightPadding = totalPadding - leftPadding;
        return new string(' ', leftPadding) + text + new string(' ', rightPadding);
    }

    private PrintCommand CreateCommand(string text, StyleContext style)
    {
        var command = new PrintCommand(text, style.IsBig, style.IsBold)
        {
            IsTall = style.IsTall,
            IsRed = style.IsRed && _profile.SupportsRed,
            SetLineSpacing = style.LineSpacing
        };
        return command;
    }
}

internal enum TextAlignment
{
    Left,
    Center,
    Right
}

internal class StyleContext
{
    public bool IsBold { get; set; }
    public bool IsBig { get; set; }
    public bool IsTall { get; set; }
    public bool IsRed { get; set; }
    public TextAlignment Alignment { get; set; } = TextAlignment.Left;
    public int? LineSpacing { get; set; }

    public StyleContext Clone()
    {
        return new StyleContext
        {
            IsBold = IsBold,
            IsBig = IsBig,
            IsTall = IsTall,
            IsRed = IsRed,
            Alignment = Alignment,
            LineSpacing = LineSpacing
        };
    }
}
