import { useState, useEffect, useRef, useCallback } from "react";

// ============================================================================
// Web Audio Engine (shared with SpeedCalculatorModal pattern)
// ============================================================================

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 freq = bellFrequency(bit + 1);
      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);
    }
  }
}

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

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));
  }
  return steps;
}

// ============================================================================
// Speed math — exponential mapping matching the Flutter app:
//   value = minSpeed * pow(maxSpeed / minSpeed, t)   where t = percent / 100
// Note: in this system, MIN ms > MAX ms (MIN = slowest, MAX = fastest).
// ============================================================================

function mapPercentageToSpeed(percent, minSpeed, maxSpeed) {
  if (minSpeed == null || maxSpeed == null) return null;
  const t = Math.max(0, Math.min(100, percent)) / 100;
  const a = minSpeed;
  const b = maxSpeed;
  if (a <= 0 || b <= 0) return Math.round(a + (b - a) * t);
  return Math.round(a * Math.pow(b / a, t));
}

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

const BEAT_DURATION_MS = 80; // fixed tone length for playback

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

export default function PlaybackModal({ open, melody, builtMelody, files, onClose }) {
  const info = melody?.information || {};
  const minSpeed = info.minSpeed || null;
  const maxSpeed = info.maxSpeed || null;

  const [steps, setSteps] = useState([]);
  const [loading, setLoading] = useState(false);
  const [loadError, setLoadError] = useState("");

  const [playing, setPlaying] = useState(false);
  const [currentStep, setCurrentStep] = useState(-1);
  const [speedPercent, setSpeedPercent] = useState(50);

  const audioCtxRef = useRef(null);
  const playbackRef = useRef(null);
  const stepsRef = useRef([]);
  const speedMsRef = useRef(500);

  // Derived speed in ms from the current percent
  const speedMs = mapPercentageToSpeed(speedPercent, minSpeed, maxSpeed) ?? 500;

  // Keep refs in sync so the playback loop reads live values
  useEffect(() => { stepsRef.current = steps; }, [steps]);
  useEffect(() => { speedMsRef.current = speedMs; }, [speedMs]);

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

  // Load binary on open
  useEffect(() => {
    if (!open) {
      stopPlayback();
      setSteps([]);
      setCurrentStep(-1);
      setLoadError("");
      setSpeedPercent(50);
      return;
    }

    // builtMelody.binary_url is a relative path needing /api prefix;
    // files.binary_url from the /files endpoint is already a full URL path.
    const binaryUrl = builtMelody?.binary_url
      ? `/api${builtMelody.binary_url}`
      : files?.binary_url || null;

    if (!binaryUrl) {
      setLoadError("No binary file available for this melody.");
      return;
    }

    setLoading(true);
    setLoadError("");
    decodeBsmBinary(binaryUrl)
      .then((decoded) => {
        setSteps(decoded);
        stepsRef.current = decoded;
      })
      .catch((err) => setLoadError(err.message))
      .finally(() => setLoading(false));
  }, [open]); // eslint-disable-line react-hooks/exhaustive-deps

  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;
  };

  const scheduleStep = useCallback((stepIndex) => {
    const currentSteps = stepsRef.current;
    if (!currentSteps.length) return;

    const playFrom = stepIndex % currentSteps.length;

    // End of sequence — loop back
    if (playFrom === 0 && stepIndex > 0) {
      scheduleStep(0);
      return;
    }

    const ctx = ensureAudioCtx();
    const stepValue = currentSteps[playFrom];
    setCurrentStep(playFrom);
    playStep(ctx, stepValue, BEAT_DURATION_MS);

    const timer = setTimeout(() => {
      const next = playFrom + 1;
      if (next >= stepsRef.current.length) {
        scheduleStep(0);
      } else {
        scheduleStep(next);
      }
    }, speedMsRef.current);

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

  const handlePlay = () => {
    if (!stepsRef.current.length) return;
    setPlaying(true);
    scheduleStep(0);
  };

  const handleStop = () => {
    stopPlayback();
  };

  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 maxBell = allBellsUsed.size > 0 ? Math.max(...Array.from(allBellsUsed)) : 0;
  const currentBells = currentStep >= 0 ? getActiveBells(steps[currentStep] || 0) : [];

  const hasSpeedInfo = minSpeed != null && maxSpeed != null;

  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: "460px",
        }}
      >
        {/* Header */}
        <div
          className="flex items-center justify-between px-6 py-4 border-b"
          style={{ borderColor: "var(--border-primary)" }}
        >
          <div>
            <h2 className="text-lg font-semibold" style={{ color: "var(--text-heading)" }}>
              Melody Playback
            </h2>
            <p className="text-xs mt-0.5" style={mutedStyle}>
              {melody?.information?.name?.en || "Melody"} — looping
            </p>
          </div>
          <button
            onClick={() => { stopPlayback(); onClose(); }}
            className="text-xl leading-none"
            style={mutedStyle}
          >
            &times;
          </button>
        </div>

        {/* Body */}
        <div className="px-6 py-5 space-y-5">
          {/* Loading / error states */}
          {loading && (
            <p className="text-sm text-center py-4" style={mutedStyle}>Loading binary...</p>
          )}
          {loadError && (
            <div
              className="text-sm rounded-md p-3 border"
              style={{ backgroundColor: "var(--danger-bg)", borderColor: "var(--danger)", color: "var(--danger-text)" }}
            >
              {loadError}
            </div>
          )}

          {!loading && !loadError && totalSteps > 0 && (
            <>
              {/* Step info */}
              <div className="flex items-center justify-between">
                <span className="text-xs" style={mutedStyle}>
                  {totalSteps} steps &nbsp;·&nbsp; {allBellsUsed.size} bell{allBellsUsed.size !== 1 ? "s" : ""}
                </span>
                {currentStep >= 0 && (
                  <span className="text-xs font-mono" style={{ color: "var(--accent)" }}>
                    Step {currentStep + 1} / {totalSteps}
                  </span>
                )}
              </div>

              {/* Bell visualizer */}
              <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>

              {/* Play / Stop */}
              <div className="flex items-center gap-3">
                {!playing ? (
                  <button
                    onClick={handlePlay}
                    className="px-5 py-2 text-sm rounded-md font-medium transition-colors"
                    style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
                  >
                    ▶ Play
                  </button>
                ) : (
                  <button
                    onClick={handleStop}
                    className="px-5 py-2 text-sm rounded-md font-medium transition-colors"
                    style={{ backgroundColor: "var(--danger-btn)", color: "var(--text-white)" }}
                  >
                    ■ Stop
                  </button>
                )}
                <span className="text-xs" style={mutedStyle}>Loops continuously</span>
              </div>

              {/* Speed Slider */}
              <div>
                <div className="flex items-center justify-between mb-1.5">
                  <label className="text-sm font-medium" style={labelStyle}>Speed</label>
                  <div className="text-right">
                    <span className="text-sm font-bold" style={{ color: "var(--accent)" }}>
                      {speedPercent}%
                    </span>
                    {hasSpeedInfo && (
                      <span className="text-xs ml-2" style={mutedStyle}>
                        ({speedMs} ms)
                      </span>
                    )}
                  </div>
                </div>
                <input
                  type="range"
                  min="1"
                  max="100"
                  step="1"
                  value={speedPercent}
                  onChange={(e) => setSpeedPercent(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>1% (slowest)</span>
                  <span>100% (fastest)</span>
                </div>
                {!hasSpeedInfo && (
                  <p className="text-xs mt-1.5" style={{ color: "var(--warning, #f59e0b)" }}>
                    No MIN/MAX speed set for this melody — using linear fallback.
                  </p>
                )}
              </div>
            </>
          )}
        </div>

        {/* Footer */}
        <div
          className="flex justify-end px-6 py-4 border-t"
          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>
        </div>
      </div>
    </div>
  );
}
