import { useState, useEffect, useRef, useCallback } from "react";
import api from "../api/client";

// ============================================================================
// Web Audio Engine
// ============================================================================

/**
 * Bell frequencies: Bell 1 = highest (880 Hz A5), each subsequent bell 2 semitones lower.
 * freq = 880 * (2^(1/12))^(-2*(bell-1))
 */
function bellFrequency(bellNumber) {
  return 880 * Math.pow(Math.pow(2, 1 / 12), -2 * (bellNumber - 1));
}

function playStep(audioCtx, stepValue, beatDurationMs) {
  if (!stepValue || !audioCtx) return;
  const now = audioCtx.currentTime;
  const duration = beatDurationMs / 1000;
  const fadeIn = 0.005;
  const fadeOut = 0.03;

  for (let bit = 0; bit < 16; bit++) {
    if (stepValue & (1 << bit)) {
      const bellNum = bit + 1;
      const freq = bellFrequency(bellNum);

      const osc = audioCtx.createOscillator();
      const gain = audioCtx.createGain();
      osc.connect(gain);
      gain.connect(audioCtx.destination);

      osc.type = "sine";
      osc.frequency.setValueAtTime(freq, now);

      gain.gain.setValueAtTime(0, now);
      gain.gain.linearRampToValueAtTime(0.3, now + fadeIn);
      gain.gain.setValueAtTime(0.3, now + Math.max(duration - fadeOut, fadeIn + 0.001));
      gain.gain.linearRampToValueAtTime(0, now + duration);

      osc.start(now);
      osc.stop(now + duration);
    }
  }
}

// ============================================================================
// Parse / decode helpers
// ============================================================================

function parseBellNotation(notation) {
  notation = notation.trim();
  if (notation === "0" || !notation) return 0;
  let value = 0;
  for (const part of notation.split("+")) {
    const n = parseInt(part.trim(), 10);
    if (!isNaN(n) && n >= 1 && n <= 16) value |= 1 << (n - 1);
  }
  return value;
}

function parseStepsString(stepsStr) {
  if (!stepsStr || !stepsStr.trim()) return [];
  return stepsStr.trim().split(",").map((s) => parseBellNotation(s));
}

function getActiveBells(stepValue) {
  const bells = [];
  for (let bit = 0; bit < 16; bit++) {
    if (stepValue & (1 << bit)) bells.push(bit + 1);
  }
  return bells;
}

/** Decode a .bsm binary (big-endian uint16 per step) to uint16 array */
async function decodeBsmBinary(url) {
  const token = localStorage.getItem("access_token");
  const res = await fetch(url, {
    headers: token ? { Authorization: `Bearer ${token}` } : {},
  });
  if (!res.ok) throw new Error(`Failed to fetch binary: ${res.statusText}`);
  const buf = await res.arrayBuffer();
  const view = new DataView(buf);
  const steps = [];
  for (let i = 0; i + 1 < buf.byteLength; i += 2) {
    steps.push(view.getUint16(i, false)); // big-endian
  }
  return steps;
}

// ============================================================================
// Speed math
// MIN is derived so that at 50%, speed = normal (geometric mean)
// normal = sqrt(MIN * MAX)  =>  MIN = normal^2 / MAX
// Note: higher ms = SLOWER. MAX ms = fastest hardware speed.
// Normal ms must be HIGHER (slower) than MAX ms to make sense.
// ============================================================================

function calcMin(normal, max) {
  if (!normal || !max || max <= 0) return null;
  return Math.round((normal * normal) / max);
}

// ============================================================================
// Component
// ============================================================================

const labelStyle = { color: "var(--text-secondary)" };
const mutedStyle = { color: "var(--text-muted)" };

// Step Delay slider is inverted: right = fast (low ms), left = slow (high ms)
// We store actual ms in state, but display with inverted slider value.
const DELAY_MIN = 50;
const DELAY_MAX = 3000;
function delayToSlider(ms) { return DELAY_MIN + DELAY_MAX - ms; }
function sliderToDelay(val) { return DELAY_MIN + DELAY_MAX - val; }

export default function SpeedCalculatorModal({ open, melody, builtMelody, archetypeCsv, onClose, onSaved }) {
  const info = melody?.information || {};

  // Raw steps input
  const [stepsInput, setStepsInput] = useState("");
  const [steps, setSteps] = useState([]);
  const [loadingBinary, setLoadingBinary] = useState(false);
  const [binaryLoadError, setBinaryLoadError] = useState("");

  // Playback
  const audioCtxRef = useRef(null);
  const playbackRef = useRef(null); // { timer, stepIndex }
  const stepsRef = useRef([]);
  const stepDelayRef = useRef(500);
  const effectiveBeatRef = useRef(100);
  const loopRef = useRef(true);

  const [playing, setPlaying] = useState(false);
  const [paused, setPaused] = useState(false);
  const [loop, setLoop] = useState(true);
  const [currentStep, setCurrentStep] = useState(-1);

  // Sliders
  const [stepDelay, setStepDelay] = useState(500);
  const [beatDuration, setBeatDuration] = useState(100);

  const effectiveBeat = Math.min(beatDuration, Math.max(20, stepDelay - 20));

  // Keep refs in sync so playback loop always reads current values
  useEffect(() => { stepsRef.current = steps; }, [steps]);
  useEffect(() => { stepDelayRef.current = stepDelay; }, [stepDelay]);
  useEffect(() => { effectiveBeatRef.current = effectiveBeat; }, [effectiveBeat]);
  useEffect(() => { loopRef.current = loop; }, [loop]);

  // Speed capture
  const [capturedMax, setCapturedMax] = useState(null);
  const [capturedNormal, setCapturedNormal] = useState(null);
  const derivedMin = calcMin(capturedNormal, capturedMax);

  const [saving, setSaving] = useState(false);
  const [saveError, setSaveError] = useState("");
  const [saveSuccess, setSaveSuccess] = useState(false);

  // Warnings:
  // MAX is the fastest (lowest ms). Normal must be > MAX (slower than MAX).
  // If Normal < MAX, it means Normal is faster than MAX — nonsensical.
  const maxWarning = capturedMax !== null && capturedMax < 100;
  const orderWarning = capturedMax !== null && capturedNormal !== null && capturedNormal < capturedMax;

  // Reset on open — auto-load archetype CSV if available
  useEffect(() => {
    if (open) {
      const max = info.maxSpeed > 0 ? info.maxSpeed : null;
      const min = info.minSpeed > 0 ? info.minSpeed : null;
      // Reverse-calculate Normal from MIN and MAX: normal = sqrt(min * max)
      const normal = (max !== null && min !== null) ? Math.round(Math.sqrt(min * max)) : null;
      setCapturedMax(max);
      setCapturedNormal(normal);
      setBinaryLoadError("");
      setCurrentStep(-1);
      setPlaying(false);
      setPaused(false);
      setSaveError("");
      setSaveSuccess(false);

      const csv = archetypeCsv || info.archetype_csv || null;
      if (csv) {
        const parsed = parseStepsString(csv);
        setStepsInput(csv);
        setSteps(parsed);
        stepsRef.current = parsed;
      } else {
        setStepsInput("");
        setSteps([]);
      }
    }
  }, [open]); // eslint-disable-line react-hooks/exhaustive-deps

  const stopPlayback = useCallback(() => {
    if (playbackRef.current) {
      clearTimeout(playbackRef.current.timer);
      playbackRef.current = null;
    }
    setPlaying(false);
    setPaused(false);
    setCurrentStep(-1);
  }, []);

  useEffect(() => {
    if (!open) stopPlayback();
  }, [open, stopPlayback]);

  const ensureAudioCtx = () => {
    if (!audioCtxRef.current || audioCtxRef.current.state === "closed") {
      audioCtxRef.current = new (window.AudioContext || window.webkitAudioContext)();
    }
    if (audioCtxRef.current.state === "suspended") {
      audioCtxRef.current.resume();
    }
    return audioCtxRef.current;
  };

  // Recursive scheduler using refs for live updates — no restart needed when sliders change
  const scheduleStep = useCallback((stepIndex) => {
    const currentSteps = stepsRef.current;
    if (!currentSteps.length) return;

    const playFrom = stepIndex % currentSteps.length;

    // End of sequence without loop
    if (!loopRef.current && playFrom === 0 && stepIndex > 0) {
      setPlaying(false);
      setPaused(false);
      setCurrentStep(-1);
      playbackRef.current = null;
      return;
    }

    const ctx = ensureAudioCtx();
    const stepValue = currentSteps[playFrom];

    setCurrentStep(playFrom);
    playStep(ctx, stepValue, effectiveBeatRef.current);

    const delay = stepDelayRef.current;
    const timer = setTimeout(() => {
      const next = playFrom + 1;
      if (next >= stepsRef.current.length) {
        if (loopRef.current) {
          scheduleStep(0);
        } else {
          setPlaying(false);
          setPaused(false);
          setCurrentStep(-1);
          playbackRef.current = null;
        }
      } else {
        scheduleStep(next);
      }
    }, delay);

    playbackRef.current = { timer, stepIndex: playFrom };
  }, []); // no deps — reads everything from refs

  const handlePlay = () => {
    if (!stepsRef.current.length) return;
    if (paused && playbackRef.current) {
      setPaused(false);
      setPlaying(true);
      scheduleStep(playbackRef.current.stepIndex);
      return;
    }
    setPlaying(true);
    setPaused(false);
    scheduleStep(0);
  };

  const handlePause = () => {
    if (playbackRef.current) {
      clearTimeout(playbackRef.current.timer);
    }
    setPaused(true);
    setPlaying(false);
  };

  const handleStepsParse = () => {
    const parsed = parseStepsString(stepsInput);
    setSteps(parsed);
    stepsRef.current = parsed;
    stopPlayback();
    setBinaryLoadError("");
  };

  const handleLoadFromBinary = async () => {
    const binaryUrl = builtMelody?.binary_url
      ? `/api${builtMelody.binary_url}`
      : melody?.url || null;
    if (!binaryUrl) return;
    setLoadingBinary(true);
    setBinaryLoadError("");
    try {
      const decoded = await decodeBsmBinary(binaryUrl);
      setSteps(decoded);
      stepsRef.current = decoded;
      stopPlayback();
      // Also reconstruct a human-readable steps string for the textarea
      const stepsStr = decoded.map((v) => {
        if (!v) return "0";
        return getActiveBells(v).join("+");
      }).join(",");
      setStepsInput(stepsStr);
    } catch (err) {
      setBinaryLoadError(err.message);
    } finally {
      setLoadingBinary(false);
    }
  };

  const handleSetSliderToCapture = (value) => {
    if (value !== null) setStepDelay(value);
  };

  const handleSave = async () => {
    if (!capturedMax || !capturedNormal || !derivedMin) return;
    stopPlayback();
    setSaving(true);
    setSaveError("");
    setSaveSuccess(false);
    try {
      const body = {
        information: {
          ...info,
          minSpeed: derivedMin,
          maxSpeed: capturedMax,
        },
        default_settings: melody.default_settings,
        type: melody.type,
        uid: melody.uid,
        pid: melody.pid,
      };
      if (melody.url) body.url = melody.url;
      await api.put(`/melodies/${melody.id}`, body);
      setSaveSuccess(true);
      setTimeout(() => onSaved(), 800);
    } catch (err) {
      setSaveError(err.message);
    } finally {
      setSaving(false);
    }
  };

  if (!open) return null;

  const totalSteps = steps.length;
  const allBellsUsed = steps.reduce((set, v) => {
    getActiveBells(v).forEach((b) => set.add(b));
    return set;
  }, new Set());
  const currentBells = currentStep >= 0 ? getActiveBells(steps[currentStep] || 0) : [];
  const maxBell = allBellsUsed.size > 0 ? Math.max(...Array.from(allBellsUsed)) : 0;

  return (
    <div
      className="fixed inset-0 flex items-center justify-center z-50"
      style={{ backgroundColor: "rgba(0,0,0,0.7)" }}
      onClick={(e) => e.target === e.currentTarget && !playing && onClose()}
    >
      <div
        className="w-full rounded-lg border shadow-xl"
        style={{
          backgroundColor: "var(--bg-card)",
          borderColor: "var(--border-primary)",
          maxWidth: "900px",
          maxHeight: "92vh",
          display: "flex",
          flexDirection: "column",
        }}
      >
        {/* Header */}
        <div className="flex items-center justify-between px-6 py-4 border-b flex-shrink-0" style={{ borderColor: "var(--border-primary)" }}>
          <div>
            <h2 className="text-lg font-semibold" style={{ color: "var(--text-heading)" }}>Speed Calculator</h2>
            <p className="text-xs mt-0.5" style={mutedStyle}>
              Play the melody at different speeds to find the right MIN / MAX.
            </p>
          </div>
          <button onClick={() => { stopPlayback(); onClose(); }} className="text-xl leading-none" style={mutedStyle}>&times;</button>
        </div>

        {/* Two-column body */}
        <div className="flex-1 overflow-y-auto">
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0", minHeight: 0 }}>

            {/* LEFT COLUMN: Steps, visualizer, controls, sliders */}
            <div className="p-6 space-y-5 border-r" style={{ borderColor: "var(--border-primary)" }}>

              {/* Steps Input */}
              <div>
                <div className="flex items-center justify-between mb-1">
                  <label className="text-sm font-medium" style={labelStyle}>
                    Melody Steps
                  </label>
                  {(builtMelody?.binary_url || melody?.url) && (
                    <button
                      onClick={handleLoadFromBinary}
                      disabled={loadingBinary}
                      className="text-xs px-2 py-1 rounded transition-colors disabled:opacity-50"
                      style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-secondary)", border: "1px solid var(--border-primary)" }}
                    >
                      {loadingBinary ? "Loading..." : "Load from Binary"}
                    </button>
                  )}
                </div>
                {binaryLoadError && (
                  <p className="text-xs mb-1" style={{ color: "var(--danger-text)" }}>{binaryLoadError}</p>
                )}
                <div className="flex gap-2">
                  <textarea
                    value={stepsInput}
                    onChange={(e) => setStepsInput(e.target.value)}
                    rows={3}
                    placeholder="e.g. 1,2,2+1,1,2,3+1,0,3,2..."
                    className="flex-1 px-3 py-2 rounded-md text-sm border"
                    style={{ fontFamily: "monospace", resize: "none" }}
                  />
                  <button
                    onClick={handleStepsParse}
                    className="px-3 py-2 text-sm rounded-md self-start transition-colors"
                    style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
                  >
                    Load
                  </button>
                </div>
                {totalSteps > 0 && (
                  <p className="text-xs mt-1" style={{ color: "var(--success-text)" }}>
                    {totalSteps} steps &nbsp;·&nbsp; {allBellsUsed.size} bell{allBellsUsed.size !== 1 ? "s" : ""}
                  </p>
                )}
              </div>

              {/* Bell visualizer */}
              {totalSteps > 0 && (
                <div>
                  <p className="text-xs mb-2" style={mutedStyle}>
                    Bell indicator
                    {currentStep >= 0 && <span> &nbsp;·&nbsp; Step {currentStep + 1} / {totalSteps}</span>}
                  </p>
                  <div className="flex flex-wrap gap-1.5">
                    {Array.from({ length: maxBell }, (_, i) => i + 1).map((b) => {
                      const isActive = currentBells.includes(b);
                      const isUsed = allBellsUsed.has(b);
                      return (
                        <div
                          key={b}
                          className="w-7 h-7 rounded-full flex items-center justify-center text-xs font-bold transition-all"
                          style={{
                            backgroundColor: isActive ? "var(--accent)" : isUsed ? "var(--bg-card-hover)" : "var(--bg-primary)",
                            color: isActive ? "var(--bg-primary)" : isUsed ? "var(--text-secondary)" : "var(--border-primary)",
                            border: `2px solid ${isActive ? "var(--accent)" : "var(--border-primary)"}`,
                            transform: isActive ? "scale(1.2)" : "scale(1)",
                          }}
                        >
                          {b}
                        </div>
                      );
                    })}
                  </div>
                </div>
              )}

              {/* Playback Controls */}
              <div className="flex items-center gap-2">
                {!playing ? (
                  <button
                    onClick={handlePlay}
                    disabled={!totalSteps}
                    className="px-4 py-2 text-sm rounded-md disabled:opacity-40 transition-colors font-medium"
                    style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
                  >
                    {paused ? "Resume" : "Play"}
                  </button>
                ) : (
                  <button
                    onClick={handlePause}
                    className="px-4 py-2 text-sm rounded-md transition-colors font-medium"
                    style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-primary)", border: "1px solid var(--border-primary)" }}
                  >
                    Pause
                  </button>
                )}
                <button
                  onClick={stopPlayback}
                  disabled={!playing && !paused}
                  className="px-4 py-2 text-sm rounded-md disabled:opacity-40 transition-colors"
                  style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-primary)", border: "1px solid var(--border-primary)" }}
                >
                  Stop
                </button>
                <label className="flex items-center gap-1.5 ml-1 text-sm cursor-pointer" style={labelStyle}>
                  <input
                    type="checkbox"
                    checked={loop}
                    onChange={(e) => { setLoop(e.target.checked); loopRef.current = e.target.checked; }}
                    className="h-4 w-4 rounded"
                  />
                  Loop
                </label>
              </div>

              {/* Step Delay Slider — inverted: right = fast (low ms) */}
              <div>
                <div className="flex items-center justify-between mb-1">
                  <label className="text-sm font-medium" style={labelStyle}>Step Delay (Speed)</label>
                  <span className="text-sm font-bold" style={{ color: "var(--accent)" }}>{stepDelay} ms</span>
                </div>
                <input
                  type="range"
                  min={DELAY_MIN}
                  max={DELAY_MAX}
                  step="10"
                  value={delayToSlider(stepDelay)}
                  onChange={(e) => setStepDelay(sliderToDelay(Number(e.target.value)))}
                  className="w-full h-2 rounded-lg appearance-none cursor-pointer"
                />
                <div className="flex justify-between text-xs mt-0.5" style={mutedStyle}>
                  <span>Slow (3000ms)</span>
                  <span>Fast (50ms)</span>
                </div>
              </div>

              {/* Beat Duration Slider */}
              <div>
                <div className="flex items-center justify-between mb-1">
                  <label className="text-sm font-medium" style={labelStyle}>Tone Length</label>
                  <span className="text-sm" style={mutedStyle}>
                    {effectiveBeat} ms
                    {effectiveBeat < beatDuration && <span style={{ color: "var(--warning, #f59e0b)" }}> (capped)</span>}
                  </span>
                </div>
                <input
                  type="range"
                  min="20"
                  max="500"
                  step="5"
                  value={beatDuration}
                  onChange={(e) => setBeatDuration(Number(e.target.value))}
                  className="w-full h-2 rounded-lg appearance-none cursor-pointer"
                />
                <div className="flex justify-between text-xs mt-0.5" style={mutedStyle}>
                  <span>20ms (short)</span>
                  <span>500ms (long)</span>
                </div>
              </div>
            </div>

            {/* RIGHT COLUMN: Speed Capture */}
            <div className="p-6">
              <div className="rounded-lg border p-4 space-y-4 h-full" style={{ borderColor: "var(--border-primary)", backgroundColor: "var(--bg-primary)" }}>
                <div>
                  <h3 className="text-sm font-semibold" style={{ color: "var(--text-heading)" }}>Speed Capture</h3>
                  <p className="text-xs mt-1" style={mutedStyle}>
                    Play and find the fastest speed the controller can handle (MAX). Then find a speed that feels natural (Normal). MIN is auto-derived so that 50% slider = Normal.
                  </p>
                </div>

                {/* MAX */}
                <div className="rounded-md p-3 border" style={{ borderColor: "var(--border-primary)", backgroundColor: "var(--bg-card)" }}>
                  <span className="text-xs font-semibold uppercase tracking-wide block mb-2" style={{ color: "var(--text-muted)" }}>MAX — fastest safe</span>
                  <div className="flex items-center justify-between">
                    <span className="text-2xl font-bold" style={{ color: capturedMax !== null ? "var(--accent)" : "var(--text-muted)" }}>
                      {capturedMax !== null ? `${capturedMax} ms` : "—"}
                    </span>
                    <div className="flex gap-2">
                      <button
                        onClick={() => handleSetSliderToCapture(capturedMax)}
                        disabled={capturedMax === null}
                        className="px-2 py-1 text-xs rounded disabled:opacity-40 transition-colors"
                        style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-muted)", border: "1px solid var(--border-primary)" }}
                        title="Set slider to this value"
                      >
                        Set ↑
                      </button>
                      <button
                        onClick={() => setCapturedMax(stepDelay)}
                        className="px-3 py-1.5 text-xs rounded-md transition-colors font-medium"
                        style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
                      >
                        Capture
                      </button>
                    </div>
                  </div>
                </div>

                {/* Normal */}
                <div className="rounded-md p-3 border" style={{ borderColor: "var(--border-primary)", backgroundColor: "var(--bg-card)" }}>
                  <span className="text-xs font-semibold uppercase tracking-wide block mb-2" style={{ color: "var(--text-muted)" }}>Normal — at 50% speed</span>
                  <div className="flex items-center justify-between">
                    <span className="text-2xl font-bold" style={{ color: capturedNormal !== null ? "var(--accent)" : "var(--text-muted)" }}>
                      {capturedNormal !== null ? `${capturedNormal} ms` : "—"}
                    </span>
                    <div className="flex gap-2">
                      <button
                        onClick={() => handleSetSliderToCapture(capturedNormal)}
                        disabled={capturedNormal === null}
                        className="px-2 py-1 text-xs rounded disabled:opacity-40 transition-colors"
                        style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-muted)", border: "1px solid var(--border-primary)" }}
                        title="Set slider to this value"
                      >
                        Set ↑
                      </button>
                      <button
                        onClick={() => setCapturedNormal(stepDelay)}
                        className="px-3 py-1.5 text-xs rounded-md transition-colors font-medium"
                        style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
                      >
                        Capture
                      </button>
                    </div>
                  </div>
                </div>

                {/* MIN (derived) */}
                <div className="rounded-md p-3 border" style={{ borderColor: "var(--border-primary)", backgroundColor: "var(--bg-card)" }}>
                  <span className="text-xs font-semibold uppercase tracking-wide block mb-1" style={{ color: "var(--text-muted)" }}>
                    MIN — auto-calculated
                    {derivedMin !== null && <span className="font-normal ml-1">(normal² / max)</span>}
                  </span>
                  <span className="text-2xl font-bold" style={{ color: derivedMin !== null ? "var(--text-secondary)" : "var(--text-muted)" }}>
                    {derivedMin !== null ? `${derivedMin} ms` : "—"}
                  </span>
                </div>

                {/* Warnings */}
                {maxWarning && (
                  <div className="text-xs rounded p-2" style={{ backgroundColor: "var(--danger-bg)", color: "var(--danger-text)" }}>
                    Warning: MAX of {capturedMax}ms is very fast (&lt;100ms). This could damage the device.
                  </div>
                )}
                {orderWarning && (
                  <div className="text-xs rounded p-2" style={{ backgroundColor: "var(--danger-bg)", color: "var(--danger-text)" }}>
                    Warning: Normal ({capturedNormal}ms) is faster than MAX ({capturedMax}ms). Normal should be slower (higher ms). Please re-capture.
                  </div>
                )}

                {/* Save messages */}
                {saveError && (
                  <div className="text-xs rounded-md p-2 border" style={{ backgroundColor: "var(--danger-bg)", borderColor: "var(--danger)", color: "var(--danger-text)" }}>
                    {saveError}
                  </div>
                )}
                {saveSuccess && (
                  <div className="text-xs rounded-md p-2 border" style={{ backgroundColor: "var(--success-bg)", borderColor: "var(--success)", color: "var(--success-text)" }}>
                    Speeds saved to melody!
                  </div>
                )}
              </div>
            </div>
          </div>
        </div>

        {/* Footer */}
        <div className="flex items-center justify-end gap-3 px-6 py-4 border-t flex-shrink-0" style={{ borderColor: "var(--border-primary)" }}>
          <button
            onClick={() => { stopPlayback(); onClose(); }}
            className="px-4 py-2 text-sm rounded-md transition-colors"
            style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-primary)" }}
          >
            Close
          </button>
          <button
            onClick={handleSave}
            disabled={!capturedMax || !capturedNormal || !derivedMin || saving || orderWarning}
            className="px-4 py-2 text-sm rounded-md disabled:opacity-40 transition-colors font-medium"
            style={{ backgroundColor: "#16a34a", color: "#fff" }}
            title={!capturedMax || !capturedNormal ? "Capture MAX and Normal speeds first" : ""}
          >
            {saving ? "Saving..." : "Save to Melody"}
          </button>
        </div>
      </div>
    </div>
  );
}
