using System.Text.Json;

namespace Inspectron.Epson.Templates.Interpreter;

public class DataContext
{
    private readonly JsonElement _root;
    private readonly DataContext? _parent;
    private readonly Dictionary<string, JsonElement> _localVariables = new();
    private readonly Dictionary<string, object> _loopMetadata = new();

    public DataContext(JsonElement root)
    {
        _root = root;
        _parent = null;
    }

    private DataContext(JsonElement root, DataContext parent)
    {
        _root = root;
        _parent = parent;
    }

    public DataContext CreateChildContext(string variableName, JsonElement value, int index, int count)
    {
        var child = new DataContext(_root, this);
        child._localVariables[variableName] = value;
        child._loopMetadata["_index"] = index;
        child._loopMetadata["_number"] = index + 1;
        child._loopMetadata["_first"] = index == 0;
        child._loopMetadata["_last"] = index == count - 1;
        child._loopMetadata["_count"] = count;
        return child;
    }

    public JsonElement? Resolve(string path)
    {
        if (string.IsNullOrEmpty(path))
        {
            return _root;
        }

        // Check loop metadata first
        if (_loopMetadata.TryGetValue(path, out var metadata))
        {
            return JsonSerializer.SerializeToElement(metadata);
        }

        var segments = path.Split('.');
        var firstSegment = segments[0];

        // Check local variables (loop variables)
        if (_localVariables.TryGetValue(firstSegment, out var localValue))
        {
            return NavigatePath(localValue, segments.Skip(1).ToArray());
        }

        // Check parent context for local variables
        if (_parent != null)
        {
            // First check if parent has this as a local variable
            var parentResult = _parent.ResolveLocalOnly(firstSegment);
            if (parentResult.HasValue)
            {
                return NavigatePath(parentResult.Value, segments.Skip(1).ToArray());
            }

            // Also check parent's loop metadata
            if (_parent._loopMetadata.TryGetValue(path, out var parentMetadata))
            {
                return JsonSerializer.SerializeToElement(parentMetadata);
            }
        }

        // Navigate from root
        return NavigatePath(_root, segments);
    }

    private JsonElement? ResolveLocalOnly(string name)
    {
        if (_localVariables.TryGetValue(name, out var value))
        {
            return value;
        }
        return _parent?.ResolveLocalOnly(name);
    }

    private JsonElement? NavigatePath(JsonElement element, string[] segments)
    {
        var current = element;

        foreach (var segment in segments)
        {
            if (current.ValueKind == JsonValueKind.Null || current.ValueKind == JsonValueKind.Undefined)
            {
                return null;
            }

            // Handle array access: collection.count or collection.length
            if (segment.Equals("count", StringComparison.OrdinalIgnoreCase) ||
                segment.Equals("length", StringComparison.OrdinalIgnoreCase))
            {
                if (current.ValueKind == JsonValueKind.Array)
                {
                    return JsonSerializer.SerializeToElement(current.GetArrayLength());
                }
            }

            // Handle array index: collection.0, collection.1
            if (int.TryParse(segment, out var index))
            {
                if (current.ValueKind == JsonValueKind.Array && index >= 0 && index < current.GetArrayLength())
                {
                    current = current[index];
                    continue;
                }
                return null;
            }

            // Handle object property
            if (current.ValueKind == JsonValueKind.Object)
            {
                if (current.TryGetProperty(segment, out var property))
                {
                    current = property;
                }
                else
                {
                    // Try case-insensitive match
                    var found = false;
                    foreach (var prop in current.EnumerateObject())
                    {
                        if (prop.Name.Equals(segment, StringComparison.OrdinalIgnoreCase))
                        {
                            current = prop.Value;
                            found = true;
                            break;
                        }
                    }
                    if (!found) return null;
                }
            }
            else
            {
                return null;
            }
        }

        return current;
    }

    public IEnumerable<JsonElement> ResolveCollection(string path)
    {
        var element = Resolve(path);
        if (element.HasValue && element.Value.ValueKind == JsonValueKind.Array)
        {
            foreach (var item in element.Value.EnumerateArray())
            {
                yield return item;
            }
        }
    }

    public string? GetString(string path)
    {
        var element = Resolve(path);
        if (!element.HasValue) return null;

        return element.Value.ValueKind switch
        {
            JsonValueKind.String => element.Value.GetString(),
            JsonValueKind.Number => element.Value.GetRawText(),
            JsonValueKind.True => "true",
            JsonValueKind.False => "false",
            JsonValueKind.Null => null,
            _ => element.Value.GetRawText()
        };
    }

    public bool IsTruthy(string path)
    {
        var element = Resolve(path);
        if (!element.HasValue) return false;

        return element.Value.ValueKind switch
        {
            JsonValueKind.Null or JsonValueKind.Undefined => false,
            JsonValueKind.False => false,
            JsonValueKind.True => true,
            JsonValueKind.String => !string.IsNullOrEmpty(element.Value.GetString()),
            JsonValueKind.Number => element.Value.GetDouble() != 0,
            JsonValueKind.Array => element.Value.GetArrayLength() > 0,
            JsonValueKind.Object => true,
            _ => false
        };
    }

    public object? GetValue(string path)
    {
        var element = Resolve(path);
        if (!element.HasValue) return null;

        return element.Value.ValueKind switch
        {
            JsonValueKind.String => element.Value.GetString(),
            JsonValueKind.Number => element.Value.TryGetInt64(out var l) ? l : element.Value.GetDouble(),
            JsonValueKind.True => true,
            JsonValueKind.False => false,
            JsonValueKind.Null => null,
            _ => element.Value.GetRawText()
        };
    }
}
