using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using DansoriEQ.Core.Eq;

namespace DansoriEQ.App;

public partial class FilterEditWindow : Wpf.Ui.Controls.FluentWindow
{
    private EqState _editState;
    private int _focusIndex;
    private bool _sync;

    public EqState ResultState { get; private set; } = new();

    private static readonly FilterType[] AllTypes = Enum.GetValues<FilterType>();

    // Frequency: log slider 0..1000 ↔ 20 Hz..20 kHz
    private static readonly double LogMin = Math.Log10(20);
    private static readonly double LogMax = Math.Log10(20000);
    private static double SliderToHz(double v)  => Math.Pow(10, LogMin + v / 1000.0 * (LogMax - LogMin));
    private static double HzToSlider(double hz) => (Math.Log10(Math.Clamp(hz, 20, 20000)) - LogMin) / (LogMax - LogMin) * 1000.0;

    // Q: log slider 0..1000 ↔ 0.3..6.0
    private static double SliderToQ(double v) => 0.3 * Math.Pow(20.0, v / 1000.0);
    private static double QToSlider(double q) => Math.Log(Math.Clamp(q, 0.3, 6.0) / 0.3) / Math.Log(20.0) * 1000.0;

    public FilterEditWindow(EqState fullState, int filterIndex)
    {
        _sync       = true;
        _editState  = fullState.Clone();
        _focusIndex = _editState.Filters.Count == 0
            ? -1
            : Math.Clamp(filterIndex, 0, _editState.Filters.Count - 1);

        InitializeComponent();

        foreach (var t in AllTypes)
            CmbType.Items.Add(new ComboBoxItem { Content = t.DisplayName(), Tag = t });

        _sync = false;

        Graph.State         = _editState;
        Graph.SelectedIndex = _focusIndex;
        Graph.NodeSelected += (_, idx) => { _focusIndex = idx; SyncFromFilter(); };
        Graph.NodeDragging += (_, idx) => { if (idx == _focusIndex) SyncFromFilter(); };
        Graph.EqChanged    += (_, __)  => SyncFromFilter();

        Loaded += (_, __) => SyncFromFilter();
    }

    private Filter? Current =>
        _focusIndex >= 0 && _focusIndex < _editState.Filters.Count ? _editState.Filters[_focusIndex] : null;

    // ── Push current filter state into the controls ──
    private void SyncFromFilter()
    {
        var f = Current;
        if (f is null) return;

        _sync = true;

        CmbType.SelectedIndex = Array.IndexOf(AllTypes, f.Type);
        ChkEnabled.IsChecked  = f.Enabled;

        SldFc.Value = HzToSlider(f.Fc);
        TxtFc.Text  = FormatHz(f.Fc);

        bool usesGain = f.Type.UsesGain();
        GainRow.Visibility = usesGain ? Visibility.Visible : Visibility.Collapsed;
        if (usesGain)
        {
            SldGain.Value = f.GainDb;
            TxtGain.Text  = $"{f.GainDb:0.0} dB";
        }

        ConfigureThirdRow(f);

        Graph.SelectedIndex = _focusIndex;
        UpdateBandLabel();

        _sync = false;
    }

    // Configure the third slider + box (Q / Slope / Order) for the current filter type.
    private void ConfigureThirdRow(Filter f)
    {
        switch (f.Type.ThirdParam())
        {
            case FilterParam.Q:
                ThirdName.Text   = "Q";
                SldThird.Minimum = 0; SldThird.Maximum = 1000;
                SldThird.Value   = QToSlider(f.Q);
                TxtThird.Text    = $"{f.Q:0.00}";
                break;

            case FilterParam.Slope:
                if (f.Type is FilterType.LowShelfSlope or FilterType.HighShelfSlope)
                {
                    ThirdName.Text   = "기울기";
                    SldThird.Minimum = 3; SldThird.Maximum = 24;
                    SldThird.Value   = Math.Clamp(f.Slope, 3, 24);
                    TxtThird.Text    = $"{SldThird.Value:0} dB/oct";
                }
                else
                {
                    ThirdName.Text   = "기울기(S)";
                    SldThird.Minimum = 0.1; SldThird.Maximum = 2.0;
                    SldThird.Value   = Math.Clamp(f.Slope, 0.1, 2.0);
                    TxtThird.Text    = $"{SldThird.Value:0.00}";
                }
                break;

            case FilterParam.Order:
                ThirdName.Text   = "차수";
                SldThird.Minimum = 2; SldThird.Maximum = 8;
                SldThird.Value   = EvenOrder(f.Order);
                TxtThird.Text    = $"{EvenOrder(f.Order)}";
                break;
        }
    }

    private static int EvenOrder(int n) { n = Math.Clamp(n, 2, 8); return n % 2 == 0 ? n : n + 1; }

    // ── Type change ──
    private void CmbType_Changed(object sender, SelectionChangedEventArgs e)
    {
        if (_sync) return;
        var f = Current;
        if (f is null || CmbType.SelectedItem is not ComboBoxItem it || it.Tag is not FilterType t) return;
        f.Type = t;

        if (t.ThirdParam() == FilterParam.Order && f.Order < 2) f.Order = 4;
        if (t.ThirdParam() == FilterParam.Slope && f.Slope <= 0)
            f.Slope = t is FilterType.LowShelfSlope or FilterType.HighShelfSlope ? 12 : 1.0;

        SyncFromFilter();
        Graph.InvalidateVisual();
    }

    // ── Slider handlers ──
    private void SldFc_Changed(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        if (_sync) return;
        var f = Current; if (f is null) return;
        f.Fc = SliderToHz(SldFc.Value);
        _sync = true; TxtFc.Text = FormatHz(f.Fc); _sync = false;
        UpdateBandLabel();
        Graph.InvalidateVisual();
    }

    private void SldGain_Changed(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        if (_sync) return;
        var f = Current; if (f is null) return;
        f.GainDb = SldGain.Value;
        _sync = true; TxtGain.Text = $"{f.GainDb:0.0} dB"; _sync = false;
        UpdateBandLabel();
        Graph.InvalidateVisual();
    }

    private void SldThird_Changed(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        if (_sync) return;
        var f = Current; if (f is null) return;
        _sync = true;
        switch (f.Type.ThirdParam())
        {
            case FilterParam.Q:
                f.Q = SliderToQ(SldThird.Value);
                TxtThird.Text = $"{f.Q:0.00}";
                break;
            case FilterParam.Slope:
                f.Slope = SldThird.Value;
                TxtThird.Text = f.Type is FilterType.LowShelfSlope or FilterType.HighShelfSlope
                    ? $"{f.Slope:0} dB/oct" : $"{f.Slope:0.00}";
                break;
            case FilterParam.Order:
                f.Order = EvenOrder((int)Math.Round(SldThird.Value));
                SldThird.Value = f.Order;   // snap
                TxtThird.Text = $"{f.Order}";
                break;
        }
        _sync = false;
        UpdateBandLabel();
        Graph.InvalidateVisual();
    }

    // ── Exact numeric entry (Peace-style) ──
    private void Txt_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Enter && sender is TextBox tb)
        {
            e.Handled = true;
            switch (tb.Name)
            {
                case nameof(TxtFc):    TxtFc_Commit(tb, e);    break;
                case nameof(TxtGain):  TxtGain_Commit(tb, e);  break;
                case nameof(TxtThird): TxtThird_Commit(tb, e); break;
            }
        }
    }

    private void TxtFc_Commit(object sender, RoutedEventArgs e)
    {
        if (_sync) return;
        var f = Current; if (f is null) return;
        if (TryParseHz(TxtFc.Text, out double hz))
        {
            f.Fc = Math.Clamp(hz, 20, 20000);
            _sync = true; SldFc.Value = HzToSlider(f.Fc); _sync = false;
        }
        _sync = true; TxtFc.Text = FormatHz(f.Fc); _sync = false;
        UpdateBandLabel(); Graph.InvalidateVisual();
    }

    private void TxtGain_Commit(object sender, RoutedEventArgs e)
    {
        if (_sync) return;
        var f = Current; if (f is null) return;
        if (TryParseNum(TxtGain.Text, out double g))
        {
            f.GainDb = Math.Clamp(g, -12, 12);
            _sync = true; SldGain.Value = f.GainDb; _sync = false;
        }
        _sync = true; TxtGain.Text = $"{f.GainDb:0.0} dB"; _sync = false;
        UpdateBandLabel(); Graph.InvalidateVisual();
    }

    private void TxtThird_Commit(object sender, RoutedEventArgs e)
    {
        if (_sync) return;
        var f = Current; if (f is null) return;
        if (TryParseNum(TxtThird.Text, out double v))
        {
            switch (f.Type.ThirdParam())
            {
                case FilterParam.Q:
                    f.Q = Math.Clamp(v, 0.3, 6.0);
                    _sync = true; SldThird.Value = QToSlider(f.Q); TxtThird.Text = $"{f.Q:0.00}"; _sync = false;
                    break;
                case FilterParam.Slope:
                    if (f.Type is FilterType.LowShelfSlope or FilterType.HighShelfSlope)
                    {
                        f.Slope = Math.Clamp(v, 3, 24);
                        _sync = true; SldThird.Value = f.Slope; TxtThird.Text = $"{f.Slope:0} dB/oct"; _sync = false;
                    }
                    else
                    {
                        f.Slope = Math.Clamp(v, 0.1, 2.0);
                        _sync = true; SldThird.Value = f.Slope; TxtThird.Text = $"{f.Slope:0.00}"; _sync = false;
                    }
                    break;
                case FilterParam.Order:
                    f.Order = EvenOrder((int)Math.Round(v));
                    _sync = true; SldThird.Value = f.Order; TxtThird.Text = $"{f.Order}"; _sync = false;
                    break;
            }
        }
        UpdateBandLabel(); Graph.InvalidateVisual();
    }

    private void ChkEnabled_Click(object sender, RoutedEventArgs e)
    {
        var f = Current; if (f is null) return;
        f.Enabled = ChkEnabled.IsChecked == true;
        Graph.InvalidateVisual();
    }

    private void UpdateBandLabel()
    {
        var f = Current; if (f is null) return;
        string third = f.Type.ThirdParam() switch
        {
            FilterParam.Q     => $"Q {f.Q:0.00}",
            FilterParam.Slope => f.Type is FilterType.LowShelfSlope or FilterType.HighShelfSlope
                                    ? $"{f.Slope:0} dB/oct" : $"S {f.Slope:0.00}",
            FilterParam.Order => $"{EvenOrder(f.Order)}차",
            _ => ""
        };
        string gain = f.Type.UsesGain() ? $"  {(f.GainDb >= 0 ? "+" : "")}{f.GainDb:0.0} dB" : "";
        BandLabel.Text = $"· {f.Type.ShortLabel()}  {FormatHz(f.Fc)}{gain}  {third}";
    }

    // ── Apply / Cancel ──
    private void BtnApply_Click(object sender, RoutedEventArgs e)
    {
        // Flush any in-progress text edits.
        TxtFc_Commit(TxtFc, e);
        if (GainRow.Visibility == Visibility.Visible) TxtGain_Commit(TxtGain, e);
        TxtThird_Commit(TxtThird, e);

        ResultState  = _editState;
        DialogResult = true;
    }

    private void BtnCancel_Click(object sender, RoutedEventArgs e) => DialogResult = false;

    // ── parsing helpers ──
    private static bool TryParseNum(string s, out double v)
    {
        s = new string(TrimUnits(s));
        return double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out v)
            || double.TryParse(s, NumberStyles.Float, CultureInfo.CurrentCulture, out v);
    }

    private static bool TryParseHz(string s, out double hz)
    {
        hz = 0;
        s = s.Trim().ToLowerInvariant();
        bool kilo = s.Contains('k');
        if (!TryParseNum(s, out double v)) return false;
        hz = kilo ? v * 1000.0 : v;
        return true;
    }

    // Keep digits, sign, decimal point; drop unit text like "dB", "Hz", "kHz", "dB/oct".
    private static char[] TrimUnits(string s)
    {
        var buf = new System.Collections.Generic.List<char>();
        foreach (char c in s)
            if (char.IsDigit(c) || c == '.' || c == '-' || c == '+') buf.Add(c);
        return buf.ToArray();
    }

    private static string FormatHz(double hz)
        => hz >= 1000 ? $"{hz / 1000.0:0.##} kHz" : $"{hz:0} Hz";
}
