// PRE-GENERATED (design session). Renders EqState (+ optional effects) to Equalizer APO config.
using System;
using System.Globalization;
using System.Text;

namespace DansoriEQ.Core.Eq;

public static class ApoRenderer
{
    private static readonly CultureInfo Inv = CultureInfo.InvariantCulture;

    /// <summary>EQ only (back-compat).</summary>
    public static string Render(EqState state, string? headerComment = null)
        => Render(state, new EffectsConfig(), headerComment);

    /// <summary>EQ + effects (balance, crossfeed, bass boost).
    /// <paramref name="volumeDb"/> is an extra digital attenuation (software volume) added
    /// on top of the safety preamp — used when the output device has no hardware volume.</summary>
    public static string Render(EqState state, EffectsConfig fx, string? headerComment = null, double volumeDb = 0)
    {
        var sb = new StringBuilder();
        if (!string.IsNullOrWhiteSpace(headerComment))
            sb.Append("# ").AppendLine(headerComment);

        // Global preamp (safety headroom + software volume attenuation)
        sb.Append("Preamp: ").Append((state.PreampDb + volumeDb).ToString("0.0", Inv)).AppendLine(" dB");

        // Balance / per-channel preamp
        if (fx.PreampLeftDb != 0 || fx.PreampRightDb != 0)
        {
            sb.AppendLine("Channel: L");
            sb.Append("Preamp: ").Append(fx.PreampLeftDb.ToString("0.0", Inv)).AppendLine(" dB");
            sb.AppendLine("Channel: R");
            sb.Append("Preamp: ").Append(fx.PreampRightDb.ToString("0.0", Inv)).AppendLine(" dB");
            sb.AppendLine("Channel: all");
        }

        // Bass boost (low shelf convenience)
        if (fx.BassBoostDb != 0)
            sb.Append("Filter: ON LSC Fc 100 Hz Gain ")
              .Append(fx.BassBoostDb.ToString("0.0", Inv)).AppendLine(" dB Q 0.700");

        // ── CROSSFEED MEMO (READ BEFORE SHIPPING) ─────────────────────────────────────
        // The block below is a PLACEHOLDER: a naive equal-blend of L/R via APO `Copy:`.
        // It is NOT real crossfeed. Proper crossfeed (Bauer / BS2B) requires the CROSSED
        // signal to be LOW-PASS filtered (~700-900 Hz) and DELAYED (~0.3 ms ITD) before
        // mixing, with a slight level/att curve. Implement per-channel via
        // `Channel:` + `Filter:`(LP) + `Delay:` (or port BS2B coefficients) in the dev
        // session. DO NOT ship this naive version as a real crossfeed feature.
        // ──────────────────────────────────────────────────────────────────────────────
        if (fx.CrossfeedLevel > 0)
        {
            double a = (fx.CrossfeedLevel / 100.0) * 0.5;   // max 0.5 blend (placeholder)
            double main = 1 - a;
            sb.AppendLine("# Crossfeed (PLACEHOLDER — not real crossfeed; see ApoRenderer memo)");
            sb.Append("Copy: L=").Append(main.ToString("0.00", Inv)).Append("*L+").Append(a.ToString("0.00", Inv))
              .Append("*R R=").Append(main.ToString("0.00", Inv)).Append("*R+").Append(a.ToString("0.00", Inv))
              .AppendLine("*L");
        }

        // EQ bands
        int idx = 1;
        foreach (var f in state.Filters)
            idx = AppendFilter(sb, f, idx);

        return sb.ToString();
    }

    private static string F(double v)   => v.ToString("0.##", Inv);
    private static string G(double v)   => v.ToString("0.0", Inv);
    private static string Qs(double v)  => v.ToString("0.000", Inv);

    /// <summary>Appends one filter (possibly as a cascade of APO lines) and returns the next index.</summary>
    private static int AppendFilter(StringBuilder sb, Filter f, int idx)
    {
        string on = f.Enabled ? "ON" : "OFF";

        switch (f.Type)
        {
            case FilterType.Peaking:
                Line(sb, ref idx, $"{on} PK Fc {F(f.Fc)} Hz Gain {G(f.GainDb)} dB Q {Qs(f.Q)}");
                break;
            case FilterType.LowPass:
                Line(sb, ref idx, $"{on} LPQ Fc {F(f.Fc)} Hz Q {Qs(f.Q)}");
                break;
            case FilterType.HighPass:
                Line(sb, ref idx, $"{on} HPQ Fc {F(f.Fc)} Hz Q {Qs(f.Q)}");
                break;
            case FilterType.BandPass:
                Line(sb, ref idx, $"{on} BP Fc {F(f.Fc)} Hz");
                break;
            case FilterType.Notch:
                Line(sb, ref idx, $"{on} NO Fc {F(f.Fc)} Hz");
                break;
            case FilterType.AllPass:
                Line(sb, ref idx, $"{on} AP Fc {F(f.Fc)} Hz Q {Qs(f.Q)}");
                break;

            case FilterType.LowShelf:
            case FilterType.LowShelfQSlope:
            case FilterType.LowShelfCornerQSlope:
                Line(sb, ref idx, $"{on} LS Fc {F(f.Fc)} Hz Gain {G(f.GainDb)} dB");
                break;
            case FilterType.HighShelf:
            case FilterType.HighShelfQSlope:
            case FilterType.HighShelfCornerQSlope:
                Line(sb, ref idx, $"{on} HS Fc {F(f.Fc)} Hz Gain {G(f.GainDb)} dB");
                break;
            case FilterType.LowShelfSlope:
                Line(sb, ref idx, $"{on} LSC {G(f.Slope)} dB Fc {F(f.Fc)} Hz Gain {G(f.GainDb)} dB");
                break;
            case FilterType.HighShelfSlope:
                Line(sb, ref idx, $"{on} HSC {G(f.Slope)} dB Fc {F(f.Fc)} Hz Gain {G(f.GainDb)} dB");
                break;

            // Higher-order: emit a cascade of Butterworth biquad sections.
            case FilterType.LowPassButterworth:
                foreach (var q in ButterworthQs(EvenOrder(f.Order)))
                    Line(sb, ref idx, $"{on} LPQ Fc {F(f.Fc)} Hz Q {Qs(q)}");
                break;
            case FilterType.HighPassButterworth:
                foreach (var q in ButterworthQs(EvenOrder(f.Order)))
                    Line(sb, ref idx, $"{on} HPQ Fc {F(f.Fc)} Hz Q {Qs(q)}");
                break;
            case FilterType.LowPassLinkwitzRiley:
                foreach (var q in LinkwitzRileyQs(EvenOrder(f.Order)))
                    Line(sb, ref idx, $"{on} LPQ Fc {F(f.Fc)} Hz Q {Qs(q)}");
                break;
            case FilterType.HighPassLinkwitzRiley:
                foreach (var q in LinkwitzRileyQs(EvenOrder(f.Order)))
                    Line(sb, ref idx, $"{on} HPQ Fc {F(f.Fc)} Hz Q {Qs(q)}");
                break;
        }
        return idx;
    }

    private static void Line(StringBuilder sb, ref int idx, string body)
        => sb.Append("Filter ").Append(idx++).Append(": ").AppendLine(body);

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

    // Section Q's for an order-N Butterworth filter (N even → N/2 sections).
    private static double[] ButterworthQs(int order)
    {
        int sections = order / 2;
        var qs = new double[sections];
        for (int k = 0; k < sections; k++)
            qs[k] = 1.0 / (2.0 * Math.Cos((2 * k + 1) * Math.PI / (2.0 * order)));
        return qs;
    }

    // Linkwitz-Riley order N = two cascaded Butterworth(N/2).
    private static double[] LinkwitzRileyQs(int order)
    {
        int half = order / 2;
        if (half < 2) return new[] { 0.5, 0.5 };   // LR2 ≈ two 1st-order → Q 0.5 pair
        var b = ButterworthQs(EvenOrder(half));
        var qs = new double[b.Length * 2];
        Array.Copy(b, 0, qs, 0, b.Length);
        Array.Copy(b, 0, qs, b.Length, b.Length);
        return qs;
    }
}
