namespace DeepDrftPublic.Client.Services;

/// <summary>
/// Holds the waveform visualizer's eight continuous-control positions plus two subsystem on/off
/// toggles for the lifetime of the WASM app instance. Scoped in DI, so it lives across SPA navigations within one listening session — open a
/// second mix and the knobs keep where you left them — but a fresh page load (F5) constructs a new
/// instance, resetting to defaults. That matches the spec's "persist within session, reset on fresh
/// load" without any cookie/localStorage round-trip (lava reframe §7c).
///
/// One state object, eight properties — not eight sibling holders, and (deliberately) NO constructor
/// parameters: this is a plain scoped value holder, so widening it (the Phase 10 split of the single
/// density knob into fluid-amount + fluid-viscosity) adds fields + defaults only and never forces a
/// consumer constructor to grow. Each C#-side default mirrors a TS-side tuning anchor in
/// WaveformVisualizer.ts; keep the two in sync, as the <c>DefaultVisibleSeconds</c> /
/// <c>DEFAULT_VISIBLE_SECONDS</c> pair does.
///
/// <para>
/// <see cref="Changed"/> is the decoupling seam between the controls bar and the visualizer bridge.
/// The controls component (a sibling of the backdrop in the page tree) only mutates this shared state
/// and raises <see cref="Changed"/>; the bridge component (<c>WaveformVisualizer</c>) subscribes
/// and pushes the affected uniform/dial to the JS module. This keeps the JS module handle single-owned
/// by the bridge — no handle sharing, no service-locator, no cross-component interop.
/// </para>
/// </summary>
public sealed class WaveformVisualizerControlState
{
    // ── The eight control defaults (Phase 10). Each mirrors a DEFAULT_* anchor in
    // WaveformVisualizer.ts; keep the two in sync, as the default-sync discipline requires.
    // Feel-anchors only — Daniel tunes on screen; the ~20% gravity / ~100% heat pair is his stated
    // sweet spot (§4c).

    /// <summary>
    /// Default scroll-speed dial. Normalized [0,1] → mapped C#-side to a visible time-span in seconds
    /// via <see cref="WaveformZoomMapping"/>, then sent to WaveformVisualizer.ts as a seconds value via
    /// <c>setScrollSpeed</c>. The TS-side anchor is <c>DEFAULT_VISIBLE_SECONDS</c>. Opens mid-range
    /// (0 = slow/wide window, 1 = fast/tight window).
    /// </summary>
    public const double DefaultScrollSpeed = 0.5;

    /// <summary>
    /// Default gradient-rotation-speed dial. Mirrors <c>DEFAULT_GRADIENT_ROTATION_SPEED</c> in
    /// WaveformVisualizer.ts. Normalized [0,1] → slow→fast anchor-rotation; drives the live OKLab
    /// three-colour gradient. 0.45 opens with a clearly-visible ~7 s colour cycle (Phase 10 §3.2).
    /// </summary>
    public const double DefaultGradientRotationSpeed = 0.45;

    /// <summary>
    /// Default lava-gravity dial. Mirrors <c>DEFAULT_LAVA_GRAVITY</c> in WaveformVisualizer.ts. Normalized
    /// [0,1]; 0 = near-weightless float, 1 = wax falls + settles fast. Tuned to Daniel's ~20% sweet spot.
    /// </summary>
    public const double DefaultLavaGravity = 0.2;

    /// <summary>
    /// Default lava-heat dial. Mirrors <c>DEFAULT_LAVA_HEAT</c> in WaveformVisualizer.ts. Normalized [0,1];
    /// 0 = wax rests at the bottom (collision-only), 1 = many small turbulent rising bubbles. Tuned to
    /// Daniel's ~100% sweet spot.
    /// </summary>
    public const double DefaultLavaHeat = 1.0;

    /// <summary>
    /// Default fluid-amount dial. Mirrors <c>DEFAULT_FLUID_AMOUNT</c> in WaveformVisualizer.ts. The first
    /// half of the Phase 10 "bubbles" split. Normalized [0,1]; 0 = few small blobs, 1 = many larger
    /// blobs (more wax in the container — blob count + per-blob volume).
    /// </summary>
    public const double DefaultFluidAmount = 0.4;

    /// <summary>
    /// Default fluid-viscosity / cohesion dial. Mirrors <c>DEFAULT_FLUID_VISCOSITY</c> in
    /// WaveformVisualizer.ts. The second half of the Phase 10 "bubbles" split. Normalized [0,1]; 1 = high
    /// cohesion (crisp spheres that snap back), 0 = low cohesion (deforms freely, stays gooey/merged).
    /// </summary>
    public const double DefaultFluidViscosity = 0.6;

    /// <summary>
    /// Default collision-strength dial. Mirrors <c>DEFAULT_COLLISION_STRENGTH</c> in WaveformVisualizer.ts.
    /// Normalized [0,1]; 0 = soft mush, 1 = hard elastic up-and-out throw.
    /// </summary>
    public const double DefaultCollisionStrength = 0.5;

    /// <summary>
    /// Default waveform-width dial. Mirrors <c>DEFAULT_WAVEFORM_WIDTH</c> in WaveformVisualizer.ts.
    /// Normalized [0,1], mapped onto the useful 10%–95% ribbon-extent band (Phase 10 §3.7); 0.5 opens
    /// mid-band. Narrowing clears room for the lava.
    /// </summary>
    public const double DefaultWaveformWidth = 0.5;

    /// <summary>
    /// Default lava-subsystem on-state. <c>true</c> so the lava field is on out of the box — the
    /// current behavior. Backs the row-1 lava lamp toggle (Phase 15 §6). Has no TS-side anchor: the
    /// bridge pushes it as an enable/disable, not a tuning dial.
    /// </summary>
    public const bool DefaultLavaEnabled = true;

    /// <summary>
    /// Default waveform-subsystem on-state. <c>true</c> so the waveform ribbon is on out of the box.
    /// Backs the row-1 waveform lamp toggle (Phase 15 §6).
    /// </summary>
    public const bool DefaultWaveformEnabled = true;

    /// <summary>
    /// Default Theater-mode state. <c>false</c> so a fresh page load opens with the full release page,
    /// not the bare visualizer (Phase 20 §4/OQ5). Has no TS-side anchor: Theater Mode is a page-chrome
    /// presentation flag, not a visualizer dial — the bridge never reads it.
    /// </summary>
    public const bool DefaultTheaterMode = false;

    /// <summary>Apparent bottom-to-top scroll rate, normalized [0,1]. Bridge maps it to a visible
    /// time-span via <see cref="WaveformZoomMapping"/>; the standalone resolution/zoom control is gone.</summary>
    public double ScrollSpeed { get; set; } = DefaultScrollSpeed;

    /// <summary>Gradient anchor-rotation rate, normalized [0,1]. Drives the live OKLab gradient.</summary>
    public double GradientRotationSpeed { get; set; } = DefaultGradientRotationSpeed;

    /// <summary>Downward force on the wax, normalized [0,1].</summary>
    public double LavaGravity { get; set; } = DefaultLavaGravity;

    /// <summary>Energy into the lava system, normalized [0,1]. 0 = rest-at-bottom, 1 = roiling.</summary>
    public double LavaHeat { get; set; } = DefaultLavaHeat;

    /// <summary>Amount of wax (blob count + per-blob volume), normalized [0,1]. Phase 10 split, part 1.</summary>
    public double FluidAmount { get; set; } = DefaultFluidAmount;

    /// <summary>Fluid viscosity / cohesion, normalized [0,1]. 1 = crisp spheres, 0 = gooey/deformed.
    /// Phase 10 split, part 2.</summary>
    public double FluidViscosity { get; set; } = DefaultFluidViscosity;

    /// <summary>Collision hardness, normalized [0,1]. 0 = soft mush, 1 = hard up-and-out throw.</summary>
    public double CollisionStrength { get; set; } = DefaultCollisionStrength;

    /// <summary>Waveform-band horizontal extent, normalized [0,1]. Narrowing clears room for the lava.</summary>
    public double WaveformWidth { get; set; } = DefaultWaveformWidth;

    /// <summary>
    /// Whether the lava field is drawn. When <c>false</c> the lava subsystem is genuinely not rendered
    /// (the bridge skips its physics + uploads no blobs — no render cost, Phase 15 §6/§10.1), not dimmed.
    /// Also gates the row-1/row-2 control visibility (§3).
    /// </summary>
    public bool LavaEnabled { get; set; } = DefaultLavaEnabled;

    /// <summary>
    /// Whether the waveform ribbon is drawn. When <c>false</c> the ribbon subsystem is genuinely not
    /// rendered (the bridge disables the ribbon SDF + drops its collision boundary — no render cost,
    /// Phase 15 §6/§10.1), not dimmed. Also gates the row-1/row-3 control visibility (§3).
    /// </summary>
    public bool WaveformEnabled { get; set; } = DefaultWaveformEnabled;

    /// <summary>
    /// Whether Theater Mode is on (Phase 20). When <c>true</c> the three release-detail pages remove
    /// their release content via <c>@if</c> so the visualizer fills the surface, and the player bar
    /// grows to carry the playing release's identity. Distinct from the visualizer dials: the bridge
    /// ignores it — the pages and the player bar observe it through the same <see cref="Changed"/> seam.
    /// Gated for visibility on <see cref="LavaEnabled"/> || <see cref="WaveformEnabled"/> at the toggle.
    /// </summary>
    public bool TheaterMode { get; set; } = DefaultTheaterMode;

    /// <summary>
    /// Raised whenever any control value changes. The visualizer bridge subscribes to push the
    /// affected dial(s); the Theater-Mode observers (detail pages, player bar) subscribe to react to
    /// <see cref="TheaterMode"/>. Mutators set the property then raise this; subscribers re-read the values.
    /// </summary>
    public event Action? Changed;

    /// <summary>
    /// Enforces the Theater-Mode invariant: Theater Mode cannot remain on when both visualizer
    /// subsystems are off (there is nothing to go to theater FOR). Call this after mutating
    /// <see cref="LavaEnabled"/> or <see cref="WaveformEnabled"/> and before
    /// <see cref="NotifyChanged"/> so all observers see a consistent, coerced state in the same
    /// <see cref="Changed"/> cycle.
    /// </summary>
    public void CoerceTheaterMode()
    {
        if (TheaterMode && !LavaEnabled && !WaveformEnabled)
            TheaterMode = false;
    }

    /// <summary>Raise <see cref="Changed"/>. Called by the controls component after mutating a value.</summary>
    public void NotifyChanged() => Changed?.Invoke();
}
