using System.Globalization;

namespace NexusShell.Services;

public enum NexusCalcOperator
{
    Add,
    Subtract,
    Multiply,
    Divide,
}

public sealed class NexusCalculatorEngine
{
    private decimal _accumulator;
    private NexusCalcOperator? _pendingOperator;
    private bool _isNewEntry = true;
    private string _entryText = "0";
    private string _expressionLine = string.Empty;
    private bool _hasError;

    public const string StandardModeLabel = "Standard Mode";
    public const string DivisionByZeroMessage = "Kan ikke dividere med nul";

    public string ExpressionLine => _hasError ? string.Empty : _expressionLine;

    public string Display => _hasError ? DivisionByZeroMessage : FormatNumber(ParseEntry());

    public bool HasError => _hasError;

    public void PressButton(string id)
    {
        if (string.IsNullOrWhiteSpace(id))
        {
            return;
        }

        if (_hasError && id is not ("C" or "CE"))
        {
            ClearAll();
        }

        switch (id)
        {
            case "0":
            case "1":
            case "2":
            case "3":
            case "4":
            case "5":
            case "6":
            case "7":
            case "8":
            case "9":
                PressDigit(int.Parse(id, CultureInfo.InvariantCulture));
                break;
            case "+":
                PressOperator(NexusCalcOperator.Add);
                break;
            case "-":
                PressOperator(NexusCalcOperator.Subtract);
                break;
            case "*":
                PressOperator(NexusCalcOperator.Multiply);
                break;
            case "/":
                PressOperator(NexusCalcOperator.Divide);
                break;
            case "%":
                PressPercent();
                break;
            case ".":
                PressDecimal();
                break;
            case "=":
                PressEquals();
                break;
            case "C":
                ClearAll();
                break;
            case "CE":
                ClearEntry();
                break;
            case "Backspace":
                PressBackspace();
                break;
            case "+/-":
                ToggleSign();
                break;
        }
    }

    public void Reset() => ClearAll();

    private void PressDigit(int digit)
    {
        if (_isNewEntry)
        {
            _entryText = digit.ToString(CultureInfo.InvariantCulture);
            _isNewEntry = false;
            return;
        }

        if (_entryText == "0")
        {
            _entryText = digit.ToString(CultureInfo.InvariantCulture);
            return;
        }

        _entryText += digit.ToString(CultureInfo.InvariantCulture);
    }

    private void PressDecimal()
    {
        if (_isNewEntry)
        {
            _entryText = "0.";
            _isNewEntry = false;
            return;
        }

        if (!_entryText.Contains('.', StringComparison.Ordinal))
        {
            _entryText += ".";
        }
    }

    private void PressOperator(NexusCalcOperator op)
    {
        var current = ParseEntry();

        if (_pendingOperator is not null && !_isNewEntry)
        {
            if (!TryApplyPending(current, out var result))
            {
                return;
            }

            _accumulator = result;
        }
        else
        {
            _accumulator = current;
        }

        _pendingOperator = op;
        _expressionLine = $"{FormatNumber(_accumulator)} {OperatorSymbol(op)}";
        _isNewEntry = true;
    }

    private void PressEquals()
    {
        if (_pendingOperator is null)
        {
            return;
        }

        var current = ParseEntry();
        if (!TryApplyPending(current, out var result))
        {
            return;
        }

        _expressionLine = $"{FormatNumber(_accumulator)} {OperatorSymbol(_pendingOperator.Value)} {FormatNumber(current)} =";
        _entryText = FormatNumber(result);
        _pendingOperator = null;
        _isNewEntry = true;
    }

    private void PressPercent()
    {
        var current = ParseEntry();

        if (_pendingOperator is not null)
        {
            current = _accumulator * current / 100m;
        }
        else
        {
            current /= 100m;
        }

        _entryText = FormatNumber(current);
        _isNewEntry = false;
    }

    private void PressBackspace()
    {
        if (_isNewEntry || _entryText.Length <= 1)
        {
            _entryText = "0";
            _isNewEntry = false;
            return;
        }

        _entryText = _entryText[..^1];
        if (_entryText is "" or "-")
        {
            _entryText = "0";
        }
    }

    private void ToggleSign()
    {
        if (ParseEntry() == 0m)
        {
            return;
        }

        if (_entryText.StartsWith("-", StringComparison.Ordinal))
        {
            _entryText = _entryText[1..];
        }
        else
        {
            _entryText = $"-{_entryText}";
        }

        _isNewEntry = false;
    }

    private void ClearEntry()
    {
        _entryText = "0";
        _isNewEntry = true;
    }

    private void ClearAll()
    {
        _accumulator = 0m;
        _pendingOperator = null;
        _isNewEntry = true;
        _entryText = "0";
        _expressionLine = string.Empty;
        _hasError = false;
    }

    private bool TryApplyPending(decimal right, out decimal result)
    {
        result = 0m;
        if (_pendingOperator is null)
        {
            return false;
        }

        try
        {
            result = Apply(_pendingOperator.Value, _accumulator, right);
            return true;
        }
        catch (DivideByZeroException)
        {
            _hasError = true;
            _expressionLine = string.Empty;
            return false;
        }
    }

    private static decimal Apply(NexusCalcOperator op, decimal left, decimal right) => op switch
    {
        NexusCalcOperator.Add => left + right,
        NexusCalcOperator.Subtract => left - right,
        NexusCalcOperator.Multiply => left * right,
        NexusCalcOperator.Divide => right == 0m
            ? throw new DivideByZeroException()
            : left / right,
        _ => left,
    };

    private decimal ParseEntry() =>
        decimal.TryParse(_entryText, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)
            ? value
            : 0m;

    private static string FormatNumber(decimal value)
    {
        if (value == decimal.Truncate(value))
        {
            return value.ToString("0", CultureInfo.InvariantCulture);
        }

        var text = value.ToString("G15", CultureInfo.InvariantCulture);
        if (text.Contains('E', StringComparison.OrdinalIgnoreCase))
        {
            return text;
        }

        return text.TrimEnd('0').TrimEnd('.');
    }

    private static string OperatorSymbol(NexusCalcOperator op) => op switch
    {
        NexusCalcOperator.Add => "+",
        NexusCalcOperator.Subtract => "−",
        NexusCalcOperator.Multiply => "×",
        NexusCalcOperator.Divide => "÷",
        _ => "?",
    };
}
