using System.Text.Json;
using Inspectron.Epson.PrintServer.Printers.Utils;
using Inspectron.Epson.TemplateEngine.DataBinding;
using Inspectron.Epson.TemplateEngine.Parsing;

namespace Inspectron.Epson.TemplateEngine.Rendering;

public class TemplateRenderer
{
    private readonly int _lineWidth;
    private readonly int _bigFontLineWidth;
    private readonly LayoutEngine _layout = new();

    public TemplateRenderer(int lineWidth, int bigFontLineWidth)
    {
        _lineWidth = lineWidth;
        _bigFontLineWidth = bigFontLineWidth;
    }

    public List<PrintCommand> Render(ReceiptNode receipt, DataContext context)
    {
        var commands = new List<PrintCommand>();
        RenderChildren(receipt.Children, context, commands);
        return commands;
    }

    private void RenderChildren(List<TemplateNode> children, DataContext context, List<PrintCommand> commands)
    {
        for (int i = 0; i < children.Count; i++)
        {
            var node = children[i];

            if (node is IfNode ifNode)
            {
                var evaluator = new ExpressionEvaluator(context);
                bool condition = evaluator.EvaluateCondition(ifNode.Test);

                if (condition)
                {
                    RenderChildren(ifNode.Children, context, commands);
                    // Skip following else
                    if (i + 1 < children.Count && children[i + 1] is ElseNode)
                        i++;
                }
                else
                {
                    // Check for following else
                    if (i + 1 < children.Count && children[i + 1] is ElseNode elseNode)
                    {
                        RenderChildren(elseNode.Children, context, commands);
                        i++;
                    }
                }
            }
            else
            {
                RenderNode(node, context, commands);
            }
        }
    }

    private void RenderNode(TemplateNode node, DataContext context, List<PrintCommand> commands)
    {
        switch (node)
        {
            case LineNode line:
                RenderLine(line, context, commands);
                break;
            case ColumnsNode columns:
                RenderColumns(columns, context, commands);
                break;
            case RowNode row:
                RenderRow(row, context, commands);
                break;
            case SeparatorNode separator:
                RenderSeparator(separator, commands);
                break;
            case CutNode:
                commands.Add(new PrintCommand("") { IsCut = true });
                break;
            case FeedNode feed:
                RenderFeed(feed, commands);
                break;
            case ForeachNode foreachNode:
                RenderForeach(foreachNode, context, commands);
                break;
            case TableNode table:
                RenderTable(table, context, commands);
                break;
            case ElseNode:
                // Handled by IfNode processing in RenderChildren
                break;
        }
    }

    private void RenderLine(LineNode line, DataContext context, List<PrintCommand> commands)
    {
        var evaluator = new ExpressionEvaluator(context);
        string text = evaluator.Evaluate(line.Text ?? "");
        int effectiveWidth = line.Big ? _bigFontLineWidth : _lineWidth;

        if (line.Wrap && text.Length > effectiveWidth)
        {
            var wrappedLines = _layout.WrapText(text, effectiveWidth, line.WrapIndent);
            foreach (var wrappedLine in wrappedLines)
            {
                var cmd = CreateCommand(wrappedLine, line.Bold, line.Big, line.Tall, line.Red, line.LineSpacing);
                commands.Add(cmd);
            }
        }
        else
        {
            text = _layout.AlignText(text, line.Align, effectiveWidth);
            var cmd = CreateCommand(text, line.Bold, line.Big, line.Tall, line.Red, line.LineSpacing);
            commands.Add(cmd);
        }
    }

    private void RenderColumns(ColumnsNode columns, DataContext context, List<PrintCommand> commands)
    {
        var evaluator = new ExpressionEvaluator(context);
        string left = evaluator.Evaluate(columns.Left ?? "");
        string right = evaluator.Evaluate(columns.Right ?? "");
        int effectiveWidth = columns.Big ? _bigFontLineWidth : _lineWidth;

        if (columns.Wrap)
        {
            var lines = _layout.FormatTwoColumnsWithWrap(left, right, effectiveWidth);
            foreach (var line in lines)
            {
                var cmd = CreateCommand(line, columns.Bold, columns.Big, columns.Tall, columns.Red, columns.LineSpacing);
                commands.Add(cmd);
            }
        }
        else
        {
            string text = _layout.FormatTwoColumns(left, right, effectiveWidth);
            var cmd = CreateCommand(text, columns.Bold, columns.Big, columns.Tall, columns.Red, columns.LineSpacing);
            commands.Add(cmd);
        }
    }

    private void RenderRow(RowNode row, DataContext context, List<PrintCommand> commands)
    {
        var evaluator = new ExpressionEvaluator(context);
        int effectiveWidth = row.Big ? _bigFontLineWidth : _lineWidth;

        bool anyWrap = row.Columns.Any(c => c.Wrap);

        if (anyWrap)
        {
            var columnData = row.Columns.Select(c => (
                text: evaluator.Evaluate(c.Text ?? ""),
                width: ResolveColumnWidth(c, effectiveWidth),
                align: c.Align,
                wrap: c.Wrap
            )).ToList();

            var lines = _layout.FormatMultiColumnWithWrap(columnData, effectiveWidth);
            foreach (var line in lines)
            {
                var cmd = CreateCommand(line, row.Bold, row.Big, row.Tall, row.Red, row.LineSpacing);
                commands.Add(cmd);
            }
        }
        else
        {
            var columnData = row.Columns.Select(c => (
                text: evaluator.Evaluate(c.Text ?? ""),
                width: ResolveColumnWidth(c, effectiveWidth),
                align: c.Align
            )).ToList();

            string text = _layout.FormatMultiColumn(columnData, effectiveWidth);
            var cmd = CreateCommand(text, row.Bold, row.Big, row.Tall, row.Red, row.LineSpacing);
            commands.Add(cmd);
        }
    }

    private void RenderSeparator(SeparatorNode separator, List<PrintCommand> commands)
    {
        string text = _layout.CreateSeparator(separator.Character, _lineWidth);
        commands.Add(new PrintCommand(text));
    }

    private void RenderFeed(FeedNode feed, List<PrintCommand> commands)
    {
        for (int i = 0; i < feed.Lines; i++)
        {
            commands.Add(new PrintCommand(""));
        }
    }

    private void RenderForeach(ForeachNode foreachNode, DataContext context, List<PrintCommand> commands)
    {
        var evaluator = new ExpressionEvaluator(context);

        // Resolve the items collection
        var itemsElement = context.Resolve(foreachNode.Items);
        if (itemsElement == null || itemsElement.Value.ValueKind != JsonValueKind.Array)
            return;

        var array = itemsElement.Value;
        int count = array.GetArrayLength();

        for (int i = 0; i < count; i++)
        {
            var item = array[i];
            var childContext = context.CreateChildScope();
            childContext.SetVariable(foreachNode.Var, item);
            childContext.SetLoopVariable("$index", i);
            childContext.SetLoopVariable("$first", i == 0);
            childContext.SetLoopVariable("$last", i == count - 1);

            RenderChildren(foreachNode.Children, childContext, commands);
        }
    }

    private void RenderTable(TableNode table, DataContext context, List<PrintCommand> commands)
    {
        var evaluator = new ExpressionEvaluator(context);

        int tableContentWidth = _lineWidth - table.Columns.Count - 1;
        var columnDefs = table.Columns.Select(c => (
            text: evaluator.Evaluate(c.Text ?? ""),
            width: ResolveColumnWidth(c, tableContentWidth),
            align: c.Align
        )).ToList();

        // Header rows from headerItems or column text
        var headerRows = new List<List<string>>();
        if (!string.IsNullOrEmpty(table.HeaderItems))
        {
            var headerArray = context.Resolve(table.HeaderItems);
            if (headerArray != null && headerArray.Value.ValueKind == JsonValueKind.Array)
            {
                foreach (var headerItem in headerArray.Value.EnumerateArray())
                {
                    var row = new List<string>();
                    foreach (var col in table.Columns)
                    {
                        var childContext = context.CreateChildScope();
                        childContext.SetVariable(table.Var ?? "item", headerItem);
                        var childEval = new ExpressionEvaluator(childContext);
                        row.Add(childEval.Evaluate(col.Text ?? ""));
                    }
                    headerRows.Add(row);
                }
            }
        }
        else if (columnDefs.Any(c => !string.IsNullOrEmpty(c.text)))
        {
            headerRows.Add(columnDefs.Select(c => c.text).ToList());
        }

        // Data rows
        var dataRows = new List<List<string>>();
        if (!string.IsNullOrEmpty(table.Items))
        {
            var itemsArray = context.Resolve(table.Items);
            if (itemsArray != null && itemsArray.Value.ValueKind == JsonValueKind.Array)
            {
                foreach (var dataItem in itemsArray.Value.EnumerateArray())
                {
                    var childContext = context.CreateChildScope();
                    childContext.SetVariable(table.Var ?? "item", dataItem);
                    var childEval = new ExpressionEvaluator(childContext);

                    var row = new List<string>();
                    foreach (var child in table.Children)
                    {
                        if (child is LineNode lineChild)
                        {
                            row.Add(childEval.Evaluate(lineChild.Text ?? ""));
                        }
                    }

                    // If no line children, use column defs
                    if (row.Count == 0)
                    {
                        foreach (var col in table.Columns)
                        {
                            row.Add(childEval.Evaluate(col.Text ?? ""));
                        }
                    }

                    dataRows.Add(row);
                }
            }
        }

        var wrapFlags = table.Columns.Select(c => c.Wrap).ToList();
        var lines = _layout.FormatTable(columnDefs, headerRows, dataRows, _lineWidth, wrapFlags);
        foreach (var line in lines)
        {
            commands.Add(new PrintCommand(line));
        }
    }

    private static int ResolveColumnWidth(ColumnDef col, int availableWidth)
    {
        if (col.Width.HasValue) return col.Width.Value;
        if (col.WidthPercent.HasValue) return (int)(availableWidth * col.WidthPercent.Value / 100.0);
        return 0;
    }

    private static PrintCommand CreateCommand(string text, bool bold, bool big, bool tall, bool red, int? lineSpacing)
    {
        return new PrintCommand(text, isBig: big, isBold: bold)
        {
            IsTall = tall,
            IsRed = red,
            SetLineSpacing = lineSpacing
        };
    }
}
