using System;
using System.Collections.Generic;
using BepInEx.Configuration;
using UnityEngine;

namespace BanquetForCyka {
    public class ExpressionConfigEntry {
        private readonly ConfigEntry<string> _configEntry;
        private readonly ConfigEntry<bool> _debug;
        private Func<float, float> _compiledExpression;
        private string _lastValidExpression;
        private bool _isValid = true;
        private string _currentInput;

        public string Value {
            get => _configEntry.Value;
            set {
                if (value == _configEntry.Value)
                    return;

                _currentInput = value;
                // Try to compile the expression
                try {
                    LogDebug($"=== Starting compilation of new expression ===", _debug);
                    LogDebug($"Input expression: '{value}'", _debug);
                    var newExpr = ParseExpression(value, _debug);
                    _compiledExpression = newExpr;
                    _lastValidExpression = value;
                    _isValid = true;
                    LogDebug($"=== Expression compilation successful ===", _debug);
                } catch (Exception e) {
                    _isValid = false;
                    LogDebug($"=== Expression compilation failed ===", _debug);
                    LogDebug($"Error: {e.Message}", _debug);
                    LogDebug($"Stack trace: {e.StackTrace}", _debug);
                    LogDebug($"Expression is invalid but keeping input: '{value}'", _debug);
                }
            }
        }

        public bool IsValid => _isValid;
        public string LastValidExpression => _lastValidExpression;
        public string CurrentInput => _currentInput;

        public ExpressionConfigEntry(ConfigFile config, string section, string key, string defaultValue,
                                     string description, ConfigEntry<bool> debug = null) {
            _debug = debug;
            LogDebug($"=== Initializing ExpressionConfigEntry ===", _debug);
            LogDebug($"Section: {section}", _debug);
            LogDebug($"Key: {key}", _debug);
            LogDebug($"Default value: {defaultValue}", _debug);

            _configEntry = config.Bind(
                section, key, defaultValue,
                new ConfigDescription(description +
                                      "\nUse 'v' to represent the input value. Examples: 'v*4', 'v+13', '(v+5)*2'"));

            _currentInput = defaultValue;

            // Initial compilation
            try {
                LogDebug($"=== Starting initial compilation ===", _debug);
                LogDebug($"Initial expression: '{_configEntry.Value}'", _debug);
                _compiledExpression = ParseExpression(_configEntry.Value, _debug);
                _lastValidExpression = _configEntry.Value;
                _isValid = true;
                LogDebug($"=== Initial compilation successful ===", _debug);
            } catch (Exception e) {
                LogDebug($"=== Initial compilation failed ===", _debug);
                LogDebug($"Error: {e.Message}", _debug);
                LogDebug($"Stack trace: {e.StackTrace}", _debug);
                _isValid = false;
            }

            // Recompile when config changes
            _configEntry.SettingChanged += (sender, args) => {
                try {
                    LogDebug($"=== Config changed, starting recompilation ===", _debug);
                    LogDebug($"New expression: '{_configEntry.Value}'", _debug);
                    _compiledExpression = ParseExpression(_configEntry.Value, _debug);
                    _lastValidExpression = _configEntry.Value;
                    _isValid = true;
                    _currentInput = _configEntry.Value;
                    LogDebug($"=== Recompilation successful ===", _debug);
                } catch (Exception e) {
                    _isValid = false;
                    LogDebug($"=== Recompilation failed ===", _debug);
                    LogDebug($"Error: {e.Message}", _debug);
                    LogDebug($"Stack trace: {e.StackTrace}", _debug);
                    LogDebug($"Expression is invalid but keeping input: '{_configEntry.Value}'", _debug);
                    _currentInput = _configEntry.Value;
                }
            };
        }

        public float Evaluate(float input) {
            if (!_isValid) {
                LogDebug($"=== Evaluation with invalid expression ===", _debug);
                LogDebug($"Input value: {input}", _debug);
                LogDebug($"Using last valid expression: '{_lastValidExpression}'", _debug);
                return input;  // Return original value if expression is invalid
            }
            LogDebug($"=== Starting expression evaluation ===", _debug);
            LogDebug($"Expression: '{_configEntry.Value}'", _debug);
            LogDebug($"Input value: {input}", _debug);
            var result = _compiledExpression(input);
            LogDebug($"Output value: {result}", _debug);
            LogDebug($"=== Evaluation complete ===", _debug);
            return result;
        }

        private static void LogDebug(string message, ConfigEntry<bool> debug) {
            if (debug?.Value == true) {
                Console.WriteLine($"[ExpressionConfigEntry] {message}");
            }
        }

        // Copy of the expression parser from Main
        private static Func<float, float> ParseExpression(string expr, ConfigEntry<bool> debug) {
            try {
                LogDebug($"=== Starting expression parsing ===", debug);
                LogDebug($"Raw expression: '{expr}'", debug);

                // Remove all whitespace
                expr = expr.Replace(" ", "");
                LogDebug($"After whitespace removal: '{expr}'", debug);

                var tokens = new List<string>();
                var current = "";

                // Tokenize the expression
                LogDebug("=== Tokenizing expression ===", debug);
                for (int i = 0; i < expr.Length; i++) {
                    char c = expr[i];
                    if (c == 'v') {
                        if (current != "") {
                            LogDebug($"Found number token: '{current}'", debug);
                            tokens.Add(current);
                            current = "";
                        }
                        LogDebug("Found variable token: 'v'", debug);
                        tokens.Add("v");
                    } else if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')') {
                        if (current != "") {
                            LogDebug($"Found number token: '{current}'", debug);
                            tokens.Add(current);
                            current = "";
                        }
                        LogDebug($"Found operator token: '{c}'", debug);
                        tokens.Add(c.ToString());
                    } else {
                        current += c;
                    }
                }
                if (current != "") {
                    LogDebug($"Found final number token: '{current}'", debug);
                    tokens.Add(current);
                }
                LogDebug($"All tokens: {string.Join(", ", tokens)}", debug);

                // Convert to postfix notation using shunting yard
                LogDebug("=== Converting to postfix notation ===", debug);
                var output = new List<string>();
                var operators = new Stack<string>();

                foreach (var token in tokens) {
                    if (token == "v" || float.TryParse(token, out _)) {
                        LogDebug($"Pushing operand to output: '{token}'", debug);
                        output.Add(token);
                    } else if (token == "(") {
                        LogDebug("Pushing '(' to operator stack", debug);
                        operators.Push(token);
                    } else if (token == ")") {
                        LogDebug("Found ')', popping operators until '('", debug);
                        while (operators.Count > 0 && operators.Peek() != "(") {
                            var op = operators.Pop();
                            LogDebug($"Popped operator to output: '{op}'", debug);
                            output.Add(op);
                        }
                        if (operators.Count > 0) {
                            LogDebug("Popping '(' from stack", debug);
                            operators.Pop();  // Remove "("
                        }
                    } else {
                        LogDebug($"Processing operator: '{token}'", debug);
                        while (operators.Count > 0 && operators.Peek() != "(" &&
                               GetPrecedence(operators.Peek()) >= GetPrecedence(token)) {
                            var op = operators.Pop();
                            LogDebug($"Popped higher precedence operator to output: '{op}'", debug);
                            output.Add(op);
                        }
                        LogDebug($"Pushing operator to stack: '{token}'", debug);
                        operators.Push(token);
                    }
                }

                while (operators.Count > 0) {
                    var op = operators.Pop();
                    LogDebug($"Popping remaining operator to output: '{op}'", debug);
                    output.Add(op);
                }

                LogDebug($"Postfix expression: {string.Join(" ", output)}", debug);

                // Build the expression tree
                LogDebug("=== Building expression tree ===", debug);
                var stack = new Stack<Func<float, float>>();

                foreach (var token in output) {
                    if (token == "v") {
                        LogDebug("Pushing variable function to stack", debug);
                        stack.Push(v => v);
                    } else if (float.TryParse(token, out float num)) {
                        LogDebug($"Pushing constant function to stack: {num}", debug);
                        stack.Push(v => num);
                    } else {
                        var right = stack.Pop();
                        var left = stack.Pop();
                        LogDebug($"Popped two functions for operator: '{token}'", debug);

                        switch (token) {
                            case "+":
                                LogDebug("Creating addition function", debug);
                                stack.Push(v => left(v) + right(v));
                                break;
                            case "-":
                                LogDebug("Creating subtraction function", debug);
                                stack.Push(v => left(v) - right(v));
                                break;
                            case "*":
                                LogDebug("Creating multiplication function", debug);
                                stack.Push(v => left(v) * right(v));
                                break;
                            case "/":
                                LogDebug("Creating division function", debug);
                                stack.Push(v => {
                                    var r = right(v);
                                    return r != 0 ? left(v) / r : left(v);
                                });
                                break;
                        }
                    }
                }

                LogDebug("=== Expression parsing complete ===", debug);
                return stack.Pop();
            } catch (Exception e) {
                LogDebug($"=== Expression parsing failed ===", debug);
                LogDebug($"Error: {e.Message}", debug);
                throw new FormatException($"Failed to parse expression '{expr}': {e.Message}");
            }
        }

        private static int GetPrecedence(string op) {
            switch (op) {
                case "+":
                    return 1;
                case "-":
                    return 1;
                case "*":
                    return 2;
                case "/":
                    return 2;
                default:
                    return 0;
            }
        }
    }
}