using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EonaCat.LogStack.Templates;

// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.

/// <summary>
/// Legacy adapter to support existing MessageTemplateEngine interface
/// while maintaining compatibility with the enhanced MessageTemplate class.
/// 
/// For new code, use MessageTemplate directly for better performance.
/// </summary>
public abstract class TemplateToken
{
    public abstract void Render(StringBuilder sb, Dictionary<string, object?> properties);
}

/// <summary>
/// Literal text token
/// </summary>
public sealed class LiteralToken : TemplateToken
{
    private readonly string _text;

    public LiteralToken(string text)
    {
        _text = text;
    }

    public override void Render(StringBuilder sb, Dictionary<string, object?> properties)
    {
        sb.Append(_text);
    }
}

/// <summary>
/// Conditional token for if/then/else constructs
/// Syntax: {@if:condition:trueOutput|falseOutput}
/// Example: {@if:IsActive:Active|Inactive}
/// </summary>
public sealed class ConditionalToken : TemplateToken
{
    private readonly string _condition;
    private readonly string _trueOutput;
    private readonly string _falseOutput;

    public ConditionalToken(string condition, string trueOutput, string falseOutput)
    {
        _condition = condition;
        _trueOutput = trueOutput;
        _falseOutput = falseOutput;
    }

    public override void Render(StringBuilder sb, Dictionary<string, object?> properties)
    {
        object? value = ResolveCondition(_condition, properties);
        bool result = IsTruthy(value);
        sb.Append(result ? _trueOutput : _falseOutput);
    }

    private static object? ResolveCondition(string condition, Dictionary<string, object?> properties)
    {
        // Support simple property names or comparisons like "Count>0" or "Status==Active"
        condition = condition.Trim();

        // Check for comparison operators
        string[] operators = new[] { ">=", "<=", "==", "!=", ">", "<" };
        foreach (var op in operators)
        {
            if (condition.Contains(op, StringComparison.Ordinal))
            {
                int opIdx = condition.IndexOf(op, StringComparison.Ordinal);
                string left = condition.Substring(0, opIdx).Trim();
                string right = condition.Substring(opIdx + op.Length).Trim();

                object? leftVal = GetPropertyValue(left, properties);
                object? rightVal = GetPropertyValue(right, properties);

                return CompareValues(leftVal, rightVal, op);
            }
        }

        // Simple property name
        return properties.TryGetValue(condition, out var val) ? val : null;
    }

    private static object? GetPropertyValue(string key, Dictionary<string, object?> properties)
    {
        key = key.Trim();

        // Check if it's a property reference
        if (properties.TryGetValue(key, out var val))
        {
            return val;
        }

        // Try to parse as number
        if (int.TryParse(key, out int intVal))
        {
            return intVal;
        }

        if (double.TryParse(key, out double dblVal))
        {
            return dblVal;
        }

        // Treat as string literal
        if ((key.StartsWith("'") && key.EndsWith("'")) || (key.StartsWith("\"") && key.EndsWith("\"")))
        {
            return key.Substring(1, key.Length - 2);
        }

        return key;
    }

    private static bool CompareValues(object? left, object? right, string op)
    {
        return op switch
        {
            "==" => ValuesEqual(left, right),
            "!=" => !ValuesEqual(left, right),
            ">" => CompareNumeric(left, right) > 0,
            "<" => CompareNumeric(left, right) < 0,
            ">=" => CompareNumeric(left, right) >= 0,
            "<=" => CompareNumeric(left, right) <= 0,
            _ => false
        };
    }

    private static bool ValuesEqual(object? left, object? right)
    {
        if (left == null && right == null)
        {
            return true;
        }

        if (left == null || right == null)
        {
            return false;
        }

        return left.ToString().Equals(right.ToString(), StringComparison.OrdinalIgnoreCase);
    }

    private static int CompareNumeric(object? left, object? right)
    {
        double leftNum = ToDouble(left);
        double rightNum = ToDouble(right);
        return leftNum.CompareTo(rightNum);
    }

    private static double ToDouble(object? value)
    {
        return value switch
        {
            null => 0,
            double d => d,
            int i => i,
            long l => l,
            float f => f,
            decimal dec => (double)dec,
            string s => double.TryParse(s, out double d) ? d : 0,
            _ => 0
        };
    }

    private static bool IsTruthy(object? value)
    {
        if (value == null)
        {
            return false;
        }

        if (value is bool b)
        {
            return b;
        }

        if (value is int i)
        {
            return i != 0;
        }

        if (value is long l)
        {
            return l != 0;
        }

        if (value is double d)
        {
            return d != 0.0;
        }

        if (value is string s)
        {
            return !string.IsNullOrEmpty(s);
        }

        return true;
    }
}

/// <summary>
/// Loop token for iterating over arrays/lists
/// Syntax: {@loop:PropertyName:itemTemplate}
/// Example: {@loop:Items:Item={Item}}
/// </summary>
public sealed class LoopToken : TemplateToken
{
    private readonly string _collectionName;
    private readonly string _itemTemplate;
    private readonly string _separator;

    public LoopToken(string collectionName, string itemTemplate, string separator = ", ")
    {
        _collectionName = collectionName;
        _itemTemplate = itemTemplate;
        _separator = separator;
    }

    public override void Render(StringBuilder sb, Dictionary<string, object?> properties)
    {
        if (!properties.TryGetValue(_collectionName, out var collection))
        {
            return;
        }

        var items = collection as System.Collections.IEnumerable;
        if (items == null)
        {
            return;
        }

        bool first = true;
        foreach (var item in items)
        {
            if (!first)
            {
                sb.Append(_separator);
            }

            // Create a local scope with the item
            var itemProps = new Dictionary<string, object?>(properties)
            {
                { "Item", item }
            };

            // Render the item template
            var renderer = new MessageTemplateRenderer(_itemTemplate);
            renderer.Render(sb, itemProps);

            first = false;
        }
    }
}

/// <summary>
/// Property reference token (e.g., {PropertyName})
/// Supports nested properties, formatting, filters, conditionals, and fallback values
/// </summary>
public sealed class PropertyToken : TemplateToken
{
    private readonly string _propertyName;
    private readonly string? _format;
    private readonly bool _destructure;
    private readonly int _alignment;
    private readonly string[]? _filters;
    private readonly string? _fallback;
    private readonly string? _conditionalTrueValue;
    private readonly string? _conditionalFalseValue;

    public string PropertyName => _propertyName;
    public string? Format => _format;
    public bool Destructure => _destructure;
    public int Alignment => _alignment;
    public string[]? Filters => _filters;
    public string? Fallback => _fallback;
    public string? ConditionalTrueValue => _conditionalTrueValue;
    public string? ConditionalFalseValue => _conditionalFalseValue;
    public bool IsConditional => _conditionalTrueValue != null || _conditionalFalseValue != null;

    public PropertyToken(string propertyName, string? format = null, bool destructure = false, int alignment = 0, string[]? filters = null, string? fallback = null, string? conditionalTrueValue = null, string? conditionalFalseValue = null)
    {
        _propertyName = propertyName;
        _format = format;
        _destructure = destructure;
        _alignment = alignment;
        _filters = filters;
        _fallback = fallback;
        _conditionalTrueValue = conditionalTrueValue;
        _conditionalFalseValue = conditionalFalseValue;
    }

    public override void Render(StringBuilder sb, Dictionary<string, object?> properties)
    {
        object? value = null;

        // Support nested property access (Object.Property.SubProperty)
        if (_propertyName.Contains("."))
        {
            value = ResolveNestedProperty(_propertyName, properties);
        }
        else if (!properties.TryGetValue(_propertyName, out value))
        {
            value = null;
        }

        // Handle conditionals: {?PropertyName:TrueValue|FalseValue}
        if (IsConditional)
        {
            bool condition = IsTruthy(value);
            string result = condition ? _conditionalTrueValue ?? "" : _conditionalFalseValue ?? "";
            sb.Append(result);
            return;
        }

        if (value == null)
        {
            sb.Append(_fallback ?? "null");
            return;
        }

        // Apply filters
        if (_filters != null && _filters.Length > 0)
        {
            value = ApplyFilters(value, _filters);
        }

        string formatted = FormatValue(value, _destructure, _format);

        // Apply alignment
        if (_alignment != 0)
        {
            formatted = _alignment > 0
                ? formatted.PadLeft(_alignment)
                : formatted.PadRight(-_alignment);
        }

        sb.Append(formatted);
    }

    private static bool IsTruthy(object? value)
    {
        if (value == null)
        {
            return false;
        }

        if (value is bool b)
        {
            return b;
        }

        if (value is int i)
        {
            return i != 0;
        }

        if (value is long l)
        {
            return l != 0;
        }

        if (value is double d)
        {
            return d != 0.0;
        }

        if (value is string s)
        {
            return !string.IsNullOrEmpty(s);
        }

        return true;
    }

    private static object? ResolveNestedProperty(string propertyPath, Dictionary<string, object?> properties)
    {
        if (propertyPath == null)
        {
            return null;
        }

        string[] parts = propertyPath.Split('.');
        object? current = null;

        if (!properties.TryGetValue(parts[0], out current))
        {
            return null;
        }

        for (int i = 1; i < parts.Length && current != null; i++)
        {
            current = GetPropertyValue(current, parts[i]);
        }

        return current;
    }

    private static object? GetPropertyValue(object? obj, string propertyName)
    {
        if (obj == null)
        {
            return null;
        }

        var propInfo = obj.GetType().GetProperty(propertyName, System.Reflection.BindingFlags.IgnoreCase | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
        return propInfo?.CanRead == true ? propInfo.GetValue(obj) : null;
    }

    private static object? ApplyFilters(object? value, string[] filters)
    {
        if (value == null)
        {
            return null;
        }

        foreach (var filter in filters)
        {
            string filterName = filter;
            string? filterParam = null;

            if (filter.Contains(":", StringComparison.Ordinal))
            {
                int colonIdx = filter.IndexOf(':');
                filterName = filter.Substring(0, colonIdx).Trim();
                filterParam = filter.Substring(colonIdx + 1).Trim();
            }

            value = ApplyFilter(value, filterName, filterParam);
        }

        return value;
    }

    private static object? ApplyFilter(object? value, string filterName, string? param)
    {
        if (value == null)
        {
            return null;
        }

        string str = value.ToString() ?? "";

        if (filterName.Equals("reverse", StringComparison.OrdinalIgnoreCase))
        {
            var chars = str.ToCharArray();
            System.Array.Reverse(chars);
            return new string(chars);
        }

        return filterName.ToLowerInvariant() switch
        {
            // String filters
            "uppercase" or "upper" => str.ToUpperInvariant(),
            "lowercase" or "lower" => str.ToLowerInvariant(),
            "trim" => str.Trim(),
            "trimstart" => str.TrimStart(),
            "trimend" => str.TrimEnd(),
            "truncate" => param != null && int.TryParse(param, out int len) ? (str.Length > len ? str.Substring(0, len) + "…" : str) : str,
            "substr" or "substring" => param != null && int.TryParse(param, out int pos) && pos < str.Length ? str.Substring(pos) : str,
            "replace" => ApplyReplaceFilter(str, param),
            "pad" => ApplyPadFilter(str, param),
            "repeat" => param != null && int.TryParse(param, out int count) ? string.Concat(System.Linq.Enumerable.Repeat(str, count)) : str,
            "startswith" => param != null ? (str.StartsWith(param, StringComparison.OrdinalIgnoreCase) ? "true" : "false") : str,
            "endswith" => param != null ? (str.EndsWith(param, StringComparison.OrdinalIgnoreCase) ? "true" : "false") : str,
            "contains" => param != null ? (str.Contains(param, StringComparison.OrdinalIgnoreCase) ? "true" : "false") : str,
            "split" => ApplySplitFilter(str, param),

            // Math filters
            "add" => ApplyMathFilter(value, param, (a, b) => a + b),
            "subtract" => ApplyMathFilter(value, param, (a, b) => a - b),
            "multiply" => ApplyMathFilter(value, param, (a, b) => a * b),
            "divide" => ApplyMathFilter(value, param, (a, b) => b != 0 ? a / b : double.NaN),
            "modulo" => ApplyMathFilter(value, param, (a, b) => b != 0 ? a % b : double.NaN),
            "abs" => ApplyMathFilter(value, param, (a, _) => Math.Abs(a)),
            "floor" => ApplyMathFilter(value, param, (a, _) => Math.Floor(a)),
            "ceil" => ApplyMathFilter(value, param, (a, _) => Math.Ceiling(a)),
            "round" => param != null && int.TryParse(param, out int decimals) ? Math.Round(GetDoubleValue(value), decimals).ToString() : Math.Round(GetDoubleValue(value)).ToString(),
            "min" => ApplyMathFilter(value, param, (a, b) => Math.Min(a, b)),
            "max" => ApplyMathFilter(value, param, (a, b) => Math.Max(a, b)),

            // Comparison filters
            "equals" => param != null ? (str.Equals(param, StringComparison.OrdinalIgnoreCase) ? "true" : "false") : str,
            "lessthan" or "lt" => param != null && double.TryParse(param, out double paramVal) && double.TryParse(str, out double strVal) ? (strVal < paramVal ? "true" : "false") : str,
            "greaterthan" or "gt" => param != null && double.TryParse(param, out double paramVal) && double.TryParse(str, out double strVal) ? (strVal > paramVal ? "true" : "false") : str,
            "lessthaneq" or "lte" => param != null && double.TryParse(param, out double paramVal) && double.TryParse(str, out double strVal) ? (strVal <= paramVal ? "true" : "false") : str,
            "greaterthaneq" or "gte" => param != null && double.TryParse(param, out double paramVal) && double.TryParse(str, out double strVal) ? (strVal >= paramVal ? "true" : "false") : str,

            // Date/Time filters
            "date" => ApplyDateFilter(value, param),
            "timespan" => ApplyTimeSpanFilter(value, param),

            // Default
            _ => str
        };
    }

    private static string ApplyReplaceFilter(string str, string? param)
    {
        if (string.IsNullOrEmpty(param))
        {
            return str;
        }

        var parts = param.Split(':');
        if (parts.Length >= 2)
        {
            return str.Replace(parts[0], parts[1]);
        }
        return str;
    }

    private static string ApplyPadFilter(string str, string? param)
    {
        if (!int.TryParse(param, out int width))
        {
            return str;
        }

        return str.Length < width ? str.PadRight(width) : str;
    }

    private static string ApplySplitFilter(string str, string? param)
    {
        if (string.IsNullOrEmpty(param))
        {
            return str;
        }

        var parts = str.Split(new[] { param }, StringSplitOptions.None);
        return string.Join(", ", parts);
    }

    private static string ApplyMathFilter(object? value, string? param, Func<double, double, double> operation)
    {
        double baseVal = GetDoubleValue(value);
        if (!double.TryParse(param, out double paramVal))
        {
            return baseVal.ToString();
        }

        double result = operation(baseVal, paramVal);
        return result.ToString();
    }

    private static double GetDoubleValue(object? value)
    {
        return value switch
        {
            double d => d,
            float f => f,
            int i => i,
            long l => l,
            decimal dec => (double)dec,
            string s => double.TryParse(s, out double d) ? d : 0,
            _ => 0
        };
    }

    private static string ApplyDateFilter(object? value, string? param)
    {
        if (value == null)
        {
            return "";
        }

        DateTime dt = value switch
        {
            DateTime d => d,
            DateTimeOffset dto => dto.DateTime,
            string s => DateTime.TryParse(s, out DateTime parsed) ? parsed : DateTime.Now,
            _ => DateTime.Now
        };

        string format = param ?? "O";
        try
        {
            return dt.ToString(format, System.Globalization.CultureInfo.InvariantCulture);
        }
        catch
        {
            return dt.ToString();
        }
    }

    private static string ApplyTimeSpanFilter(object? value, string? param)
    {
        if (value == null)
        {
            return "";
        }

        TimeSpan ts = value switch
        {
            TimeSpan t => t,
            string s => TimeSpan.TryParse(s, out TimeSpan parsed) ? parsed : TimeSpan.Zero,
            _ => TimeSpan.Zero
        };

        return param?.ToLowerInvariant() switch
        {
            "totalseconds" => ts.TotalSeconds.ToString(),
            "totalminutes" => ts.TotalMinutes.ToString(),
            "totalhours" => ts.TotalHours.ToString(),
            "totaldays" => ts.TotalDays.ToString(),
            "seconds" => ts.Seconds.ToString(),
            "minutes" => ts.Minutes.ToString(),
            "hours" => ts.Hours.ToString(),
            "days" => ts.Days.ToString(),
            _ => ts.ToString()
        };
    }

    private static string FormatValue(object value, bool destructure, string? format)
    {
        if (destructure)
        {
            return DestructureObject(value);
        }

        if (value is IFormattable formattable && format != null)
        {
            try
            {
                return formattable.ToString(format, System.Globalization.CultureInfo.InvariantCulture);
            }
            catch
            {
                return value.ToString() ?? "";
            }
        }

        return value.ToString() ?? "";
    }

    private static string DestructureObject(object obj)
    {
        if (obj == null)
        {
            return "null";
        }

        var t = obj.GetType();
        if (t.IsPrimitive || obj is string || obj is decimal || obj is DateTime || obj is DateTimeOffset || obj is Guid)
        {
            return obj.ToString()!;
        }

        var sb = new StringBuilder("{");
        bool first = true;
        foreach (var prop in t.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
        {
            try
            {
                if (!first)
                {
                    sb.Append(", ");
                }

                var val = prop.GetValue(obj);
                sb.Append(prop.Name).Append(": ");
                if (val == null)
                {
                    sb.Append("null");
                }
                else if (val.GetType().IsPrimitive || val is string || val is decimal || val is DateTime || val is DateTimeOffset)
                {
                    sb.Append(val);
                }
                else
                {
                    sb.Append(DestructureObject(val));
                }

                first = false;
            }
            catch { }
        }
        sb.Append('}');
        return sb.ToString();
    }
}

/// <summary>
/// Parses message templates (legacy interface for compatibility)
/// For new code, use MessageTemplate.Parse() directly
/// </summary>
public sealed class MessageTemplateParser
{
    private readonly string _template;
    private int _position;

    public MessageTemplateParser(string template)
    {
        _template = template ?? "";
    }

    public List<TemplateToken> Parse()
    {
        var tokens = new List<TemplateToken>();
        var sb = new StringBuilder();

        while (_position < _template.Length)
        {
            char c = _template[_position];

            if (c == '{')
            {
                // Flush any accumulated literal text
                if (sb.Length > 0)
                {
                    tokens.Add(new LiteralToken(sb.ToString()));
                    sb.Clear();
                }

                // Try to parse special tokens first (@if, @loop)
                TemplateToken? token = TryParseSpecialToken();
                if (token != null)
                {
                    tokens.Add(token);
                }
                else
                {
                    // Fall back to property token
                    _position--; // Back up to re-parse the opening brace
                    token = ParsePropertyToken();
                    if (token != null)
                    {
                        tokens.Add(token);
                    }
                }
            }
            else if (c == '}')
            {
                // Escaped closing brace
                if (_position + 1 < _template.Length && _template[_position + 1] == '}')
                {
                    sb.Append('}');
                    _position += 2;
                }
                else
                {
                    sb.Append(c);
                    _position++;
                }
            }
            else
            {
                sb.Append(c);
                _position++;
            }
        }

        // Flush remaining literal text
        if (sb.Length > 0)
        {
            tokens.Add(new LiteralToken(sb.ToString()));
        }

        return tokens;
    }

    private TemplateToken? TryParseSpecialToken()
    {
        if (_template[_position] != '{')
        {
            return null;
        }

        _position++; // Skip {

        // Look ahead for @ symbol
        if (_position >= _template.Length || _template[_position] != '@')
        {
            _position--; // Back up if not @
            return null;
        }

        _position++; // Skip @

        // Read the keyword (if, loop, etc.)
        var keywordSb = new StringBuilder();
        while (_position < _template.Length && char.IsLetter(_template[_position]))
        {
            keywordSb.Append(_template[_position]);
            _position++;
        }

        string keyword = keywordSb.ToString().ToLowerInvariant();

        // Read the rest of the token content until }
        if (_position >= _template.Length || _template[_position] != ':')
        {
            _position -= keyword.Length + 2; // Back up
            return null;
        }

        _position++; // Skip :

        var contentSb = new StringBuilder();
        int braceCount = 1;
        while (_position < _template.Length && braceCount > 0)
        {
            char c = _template[_position];
            if (c == '{')
            {
                braceCount++;
            }
            else if (c == '}')
            {
                braceCount--;
            }

            if (braceCount > 0)
            {
                contentSb.Append(c);
            }

            _position++;
        }

        string content = contentSb.ToString();

        return keyword switch
        {
            "if" => ParseConditionalToken(content),
            "loop" => ParseLoopToken(content),
            _ => null
        };
    }

    private TemplateToken? ParseConditionalToken(string content)
    {
        // Format: condition:trueOutput|falseOutput
        int colonIdx = content.IndexOf(':');
        if (colonIdx == -1)
        {
            return null;
        }

        string condition = content.Substring(0, colonIdx).Trim();
        string rest = content.Substring(colonIdx + 1).Trim();

        int pipeIdx = rest.IndexOf('|');
        if (pipeIdx == -1)
        {
            return new ConditionalToken(condition, rest, "");
        }

        string trueOutput = rest.Substring(0, pipeIdx).Trim();
        string falseOutput = rest.Substring(pipeIdx + 1).Trim();

        return new ConditionalToken(condition, trueOutput, falseOutput);
    }

    private TemplateToken? ParseLoopToken(string content)
    {
        // Format: collectionName:itemTemplate or collectionName:itemTemplate:separator
        var parts = content.Split(':');
        if (parts.Length < 2)
        {
            return null;
        }

        string collectionName = parts[0].Trim();
        string itemTemplate = parts[1].Trim();
        string separator = parts.Length > 2 ? parts[2].Trim() : ", ";

        return new LoopToken(collectionName, itemTemplate, separator);
    }

    private PropertyToken? ParsePropertyToken()
    {
        _position++; // Skip opening {

        if (_position >= _template.Length)
        {
            return null;
        }

        // Check for conditional prefix ?
        bool isConditional = false;
        if (_template[_position] == '?')
        {
            isConditional = true;
            _position++;
        }

        // Check for destructuring prefix @
        bool destructure = false;
        if (_position < _template.Length && _template[_position] == '@')
        {
            destructure = true;
            _position++;
        }

        // Read property name
        var nameSb = new StringBuilder();
        while (_position < _template.Length)
        {
            char c = _template[_position];
            if (c == ':' || c == '}' || c == ',' || c == '|')
            {
                break;
            }

            nameSb.Append(c);
            _position++;
        }

        string propertyName = nameSb.ToString().Trim();
        if (propertyName.Length == 0)
        {
            return null;
        }

        // Read optional alignment (,10 or ,-10)
        int alignment = 0;
        if (_position < _template.Length && _template[_position] == ',')
        {
            _position++; // Skip comma
            var alignSb = new StringBuilder();
            while (_position < _template.Length && (char.IsDigit(_template[_position]) || (_template[_position] == '-' && alignSb.Length == 0)))
            {
                alignSb.Append(_template[_position]);
                _position++;
            }
            if (int.TryParse(alignSb.ToString(), out int align))
            {
                alignment = align;
            }
        }

        // Read optional filters (|uppercase, |truncate:10)
        string[]? filters = null;
        if (_position < _template.Length && _template[_position] == '|')
        {
            _position++; // Skip pipe
            var filterSb = new StringBuilder();
            while (_position < _template.Length && _template[_position] != ':' && _template[_position] != '}')
            {
                filterSb.Append(_template[_position]);
                _position++;
            }
            string filterStr = filterSb.ToString().Trim();
            if (filterStr.Length > 0)
            {
                filters = filterStr.Split('|');
            }
        }

        // Handle conditional values {?Property:TrueValue|FalseValue}
        string? conditionalTrueValue = null;
        string? conditionalFalseValue = null;
        if (isConditional && _position < _template.Length && _template[_position] == ':')
        {
            _position++; // Skip :
            var trueSb = new StringBuilder();
            while (_position < _template.Length && _template[_position] != '|' && _template[_position] != '}')
            {
                trueSb.Append(_template[_position]);
                _position++;
            }
            conditionalTrueValue = trueSb.ToString().Trim();

            if (_position < _template.Length && _template[_position] == '|')
            {
                _position++; // Skip |
                var falseSb = new StringBuilder();
                while (_position < _template.Length && _template[_position] != '}')
                {
                    falseSb.Append(_template[_position]);
                    _position++;
                }
                conditionalFalseValue = falseSb.ToString().Trim();
            }
        }
        else if (!isConditional)
        {
            // Read optional format specifier (non-conditional path)
            string? format = null;
            if (_position < _template.Length && _template[_position] == ':')
            {
                _position++; // Skip :
                var formatSb = new StringBuilder();
                while (_position < _template.Length && _template[_position] != '}')
                {
                    formatSb.Append(_template[_position]);
                    _position++;
                }
                format = formatSb.ToString().Trim();
            }

            // Skip closing }
            if (_position < _template.Length && _template[_position] == '}')
            {
                _position++;
            }

            return new PropertyToken(propertyName, format, destructure, alignment, filters);
        }

        // Skip closing }
        if (_position < _template.Length && _template[_position] == '}')
        {
            _position++;
        }

        return new PropertyToken(propertyName, null, destructure, alignment, filters, null, conditionalTrueValue, conditionalFalseValue);
    }
}

/// <summary>
/// Renders a parsed message template with properties (legacy compatibility layer)
/// For new code, use MessageTemplate.Render() directly
/// </summary>
public sealed class MessageTemplateRenderer
{
    private readonly List<TemplateToken> _tokens;

    public MessageTemplateRenderer(string template)
    {
        var parser = new MessageTemplateParser(template);
        _tokens = parser.Parse();
    }

    public MessageTemplateRenderer(List<TemplateToken> tokens)
    {
        _tokens = tokens;
    }

    public string Render(Dictionary<string, object?> properties)
    {
        var sb = new StringBuilder();
        foreach (var token in _tokens)
        {
            token.Render(sb, properties);
        }
        return sb.ToString();
    }

    public void Render(StringBuilder sb, Dictionary<string, object?> properties)
    {
        foreach (var token in _tokens)
        {
            token.Render(sb, properties);
        }
    }

    /// <summary>
    /// Gets all property names referenced in the template
    /// </summary>
    public IEnumerable<string> GetPropertyNames()
    {
        return _tokens
            .OfType<PropertyToken>()
            .Select(t => t.PropertyName)
            .Distinct();
    }
}

/// <summary>
/// Template cache for frequently-used templates (legacy compatibility)
/// </summary>
public sealed class MessageTemplateCache
{
    private readonly Dictionary<string, MessageTemplateRenderer> _cache = new();
    private readonly int _maxSize;
    private readonly object _lock = new object();

    public MessageTemplateCache(int maxSize = 1000)
    {
        _maxSize = maxSize;
    }

    public MessageTemplateRenderer GetOrParse(string template)
    {
        lock (_lock)
        {
            if (_cache.TryGetValue(template, out var cached))
            {
                return cached;
            }

            if (_cache.Count >= _maxSize)
            {
                // Remove oldest entry (simple FIFO)
                var oldestKey = _cache.Keys.First();
                _cache.Remove(oldestKey);
            }

            var renderer = new MessageTemplateRenderer(template);
            _cache[template] = renderer;
            return renderer;
        }
    }

    public void Clear()
    {
        lock (_lock)
        {
            _cache.Clear();
        }
    }

    public int Count
    {
        get
        {
            lock (_lock)
            {
                return _cache.Count;
            }
        }
    }
}
