"use client";

/**
 * ClickTrackPlayer
 *
 * In-browser playback of the click track via the Web Audio API,
 * plus a download button that hits /api/generate.
 *
 * Playback works by fetching the WAV from /api/generate and decoding
 * it into an AudioBuffer, then scheduling clicks directly rather than
 * streaming — WAV files for typical songs are <5 MB.
 */

import { useState, useRef, useCallback } from "react";
import type { CTPDocument } from "@/lib/ctp/schema";
import { sectionStartBpm } from "@/lib/ctp/schema";

interface ClickTrackPlayerProps {
  tempoMapId: string;
  ctpDoc: CTPDocument;
  verified: boolean;
  upvotes: number;
}

type PlayerState = "idle" | "loading" | "playing" | "paused" | "error";

export default function ClickTrackPlayer({
  tempoMapId,
  ctpDoc,
  verified,
  upvotes,
}: ClickTrackPlayerProps) {
  const [state, setState] = useState<PlayerState>("idle");
  const [errorMsg, setErrorMsg] = useState("");
  const [countIn, setCountIn] = useState(true);
  const [currentTime, setCurrentTime] = useState(0);

  const audioCtxRef = useRef<AudioContext | null>(null);
  const sourceRef = useRef<AudioBufferSourceNode | null>(null);
  const bufferRef = useRef<AudioBuffer | null>(null);
  const startedAtRef = useRef(0); // AudioContext.currentTime when playback started
  const pausedAtRef = useRef(0); // offset into the buffer when paused
  const rafRef = useRef<number>(0);

  const buildUrl = useCallback(
    () =>
      `/api/generate?id=${encodeURIComponent(tempoMapId)}&count_in=${countIn}`,
    [tempoMapId, countIn]
  );

  // ── WAV fetch + decode ───────────────────────────────────────────────────
  async function loadBuffer(): Promise<AudioBuffer> {
    if (bufferRef.current) return bufferRef.current;

    const res = await fetch(buildUrl());
    if (!res.ok) throw new Error(`Server error ${res.status}`);

    const arrayBuffer = await res.arrayBuffer();
    const ctx = getAudioContext();
    const decoded = await ctx.decodeAudioData(arrayBuffer);
    bufferRef.current = decoded;
    return decoded;
  }

  function getAudioContext(): AudioContext {
    if (!audioCtxRef.current) {
      audioCtxRef.current = new AudioContext();
    }
    return audioCtxRef.current;
  }

  // ── Playback controls ────────────────────────────────────────────────────
  async function handlePlay() {
    try {
      setState("loading");
      const buffer = await loadBuffer();
      const ctx = getAudioContext();

      if (ctx.state === "suspended") await ctx.resume();

      const source = ctx.createBufferSource();
      source.buffer = buffer;
      source.connect(ctx.destination);
      source.onended = () => {
        if (state !== "paused") {
          setState("idle");
          pausedAtRef.current = 0;
          cancelAnimationFrame(rafRef.current);
        }
      };

      const offset = pausedAtRef.current;
      source.start(0, offset);
      startedAtRef.current = ctx.currentTime - offset;
      sourceRef.current = source;
      setState("playing");
      tickTimer();
    } catch (err) {
      setErrorMsg(err instanceof Error ? err.message : "Unknown error");
      setState("error");
    }
  }

  function handlePause() {
    if (!sourceRef.current || !audioCtxRef.current) return;
    pausedAtRef.current =
      audioCtxRef.current.currentTime - startedAtRef.current;
    sourceRef.current.stop();
    sourceRef.current = null;
    setState("paused");
    cancelAnimationFrame(rafRef.current);
  }

  function handleStop() {
    if (sourceRef.current) {
      sourceRef.current.stop();
      sourceRef.current = null;
    }
    pausedAtRef.current = 0;
    setState("idle");
    setCurrentTime(0);
    cancelAnimationFrame(rafRef.current);
  }

  function tickTimer() {
    rafRef.current = requestAnimationFrame(() => {
      if (!audioCtxRef.current || !sourceRef.current) return;
      setCurrentTime(audioCtxRef.current.currentTime - startedAtRef.current);
      tickTimer();
    });
  }

  function formatTime(s: number): string {
    const m = Math.floor(s / 60);
    const sec = Math.floor(s % 60);
    return `${m}:${String(sec).padStart(2, "0")}`;
  }

  // When count_in changes, invalidate the cached buffer so it re-fetches
  function handleCountInChange(v: boolean) {
    setCountIn(v);
    bufferRef.current = null;
    if (state !== "idle") {
      handleStop();
    }
  }

  const firstBpm = sectionStartBpm(ctpDoc.sections[0]);

  return (
    <div className="rounded-xl border border-zinc-800 bg-zinc-900/60 p-6 space-y-5">
      {/* Info row */}
      <div className="flex flex-wrap items-center gap-3 text-sm text-zinc-500">
        <span className="font-mono text-green-400 text-base font-semibold">
          {firstBpm} BPM
        </span>
        <span>·</span>
        <span>{ctpDoc.sections.length} sections</span>
        {verified && (
          <>
            <span>·</span>
            <span className="text-green-400">✓ Verified</span>
          </>
        )}
        <span>·</span>
        <span>{upvotes} upvotes</span>
      </div>

      {/* Count-in toggle */}
      <label className="flex items-center gap-3 cursor-pointer w-fit">
        <span className="text-sm text-zinc-400">Count-in</span>
        <button
          role="switch"
          aria-checked={countIn}
          onClick={() => handleCountInChange(!countIn)}
          className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none ${
            countIn ? "bg-green-600" : "bg-zinc-700"
          }`}
        >
          <span
            className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
              countIn ? "translate-x-6" : "translate-x-1"
            }`}
          />
        </button>
      </label>

      {/* Transport */}
      <div className="flex items-center gap-3">
        {state !== "playing" ? (
          <button
            onClick={handlePlay}
            disabled={state === "loading"}
            className="flex items-center gap-2 rounded-lg bg-green-600 px-5 py-2.5 font-semibold text-white hover:bg-green-500 disabled:opacity-50 transition-colors"
          >
            {state === "loading" ? (
              <>
                <span className="animate-spin">⟳</span> Loading…
              </>
            ) : (
              <>▶ {state === "paused" ? "Resume" : "Play"}</>
            )}
          </button>
        ) : (
          <button
            onClick={handlePause}
            className="flex items-center gap-2 rounded-lg bg-amber-600 px-5 py-2.5 font-semibold text-white hover:bg-amber-500 transition-colors"
          >
            ⏸ Pause
          </button>
        )}

        {(state === "playing" || state === "paused") && (
          <button
            onClick={handleStop}
            className="rounded-lg border border-zinc-700 px-4 py-2.5 text-zinc-400 hover:text-zinc-200 hover:border-zinc-500 transition-colors"
          >
            ■ Stop
          </button>
        )}

        {state !== "idle" && state !== "loading" && (
          <span className="font-mono text-sm text-zinc-500">
            {formatTime(currentTime)}
          </span>
        )}
      </div>

      {/* Download */}
      <a
        href={buildUrl()}
        download
        className="inline-flex items-center gap-2 text-sm text-zinc-400 hover:text-zinc-200 transition-colors"
      >
        ↓ Download WAV
      </a>

      {state === "error" && (
        <p className="text-sm text-red-400">Error: {errorMsg}</p>
      )}
    </div>
  );
}
