"use client";

/**
 * TempoAnalyzer
 *
 * Full workflow:
 *   1. User drops / selects an audio file (MP3, WAV, AAC, OGG, etc.)
 *   2. Browser decodes the audio and runs BPM detection (Web Audio API)
 *   3. Optional: user provides song title, artist, MusicBrainz ID
 *   4. User selects an analysis provider
 *   5. Client sends { bpm, duration, provider, … } to POST /api/analyze
 *   6. Server returns a CTP document draft
 *   7. User can review the sections, download the .ctp.json, or submit to DB
 */

import { useState, useRef, useCallback, useEffect } from "react";
import { detectBPM, type BPMDetectionResult } from "@/lib/analysis/bpm-detect";
import TempoMapEditor from "@/components/TempoMapEditor";
import type { CTPDocument } from "@/lib/ctp/schema";
import type { ProviderInfo } from "@/lib/analysis/providers";
import { RECOMMENDED_OLLAMA_MODELS } from "@/lib/analysis/constants";

const PROVIDER_KEY = "clicktrack_analysis_provider";
const MODEL_KEY = "clicktrack_ollama_model";

// ─── Types ────────────────────────────────────────────────────────────────────

interface SongResult {
  mbid: string;
  title: string;
  artist: string;
  duration_seconds: number | null;
}

type Stage =
  | "idle"
  | "decoding"
  | "detecting"
  | "generating"
  | "review"
  | "saving"
  | "saved"
  | "error";

interface AnalyzerState {
  stage: Stage;
  file: File | null;
  detection: BPMDetectionResult | null;
  ctp: CTPDocument | null;
  warnings: string[];
  errorMsg: string;
  title: string;
  artist: string;
  mbid: string;
  contributedBy: string;
  useHalfTime: boolean;
}

const INITIAL_STATE: AnalyzerState = {
  stage: "idle",
  file: null,
  detection: null,
  ctp: null,
  warnings: [],
  errorMsg: "",
  title: "",
  artist: "",
  mbid: "",
  contributedBy: "",
  useHalfTime: false,
};

// ─── Helpers ──────────────────────────────────────────────────────────────────

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

function confidenceLabel(c: number) {
  if (c >= 0.7) return { label: "High", color: "text-green-400" };
  if (c >= 0.4) return { label: "Medium", color: "text-amber-400" };
  return { label: "Low", color: "text-red-400" };
}

// ─── Component ────────────────────────────────────────────────────────────────

export default function TempoAnalyzer() {
  const [state, setState] = useState<AnalyzerState>(INITIAL_STATE);
  const abortRef = useRef<AbortController | null>(null);
  const dropRef = useRef<HTMLDivElement>(null);
  const [isDragging, setIsDragging] = useState(false);

  // Provider state
  const [providers, setProviders] = useState<ProviderInfo[]>([]);
  const [ollamaModels, setOllamaModels] = useState<string[]>([]);
  const [selectedProvider, setSelectedProvider] = useState<string>("");
  const [selectedOllamaModel, setSelectedOllamaModel] = useState<string>("");
  const [unavailableNotice, setUnavailableNotice] = useState<string>("");
  const [unavailableModelNotice, setUnavailableModelNotice] = useState<string>("");

  // Song search
  const [songQuery, setSongQuery] = useState("");
  const [songResults, setSongResults] = useState<SongResult[]>([]);
  const [songDropdownOpen, setSongDropdownOpen] = useState(false);
  const [songHighlightIdx, setSongHighlightIdx] = useState(-1);
  const [songSearchFailed, setSongSearchFailed] = useState(false);
  const [selectedSongLabel, setSelectedSongLabel] = useState("");
  const searchDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const songDropdownRef = useRef<HTMLDivElement>(null);

  const update = (patch: Partial<AnalyzerState>) =>
    setState((prev) => ({ ...prev, ...patch }));

  // ── Load providers on mount ───────────────────────────────────────────────

  useEffect(() => {
    async function loadProviders() {
      try {
        const res = await fetch("/api/analyze/providers");
        const data = await res.json() as { providers: ProviderInfo[]; ollamaModels: string[] };

        const available = data.providers.filter((p) => p.available);
        setProviders(available);
        setOllamaModels(data.ollamaModels);

        // Restore saved provider preference
        const savedProvider = localStorage.getItem(PROVIDER_KEY);
        if (savedProvider) {
          const found = available.find((p) => p.id === savedProvider);
          if (found) {
            setSelectedProvider(found.id);
          } else {
            // Saved provider no longer available
            const unavailable = data.providers.find((p) => p.id === savedProvider);
            const label = unavailable?.label ?? savedProvider;
            setUnavailableNotice(
              `Your previous provider (${label}) is not currently available.`
            );
            setSelectedProvider(available[0]?.id ?? "");
          }
        } else {
          setSelectedProvider(available[0]?.id ?? "");
        }

        // Restore saved Ollama model
        if (data.ollamaModels.length > 0) {
          const savedModel = localStorage.getItem(MODEL_KEY);
          if (savedModel && data.ollamaModels.includes(savedModel)) {
            setSelectedOllamaModel(savedModel);
          } else {
            if (savedModel) {
              setUnavailableModelNotice(
                `Your previous model (${savedModel}) is no longer available.`
              );
            }
            setSelectedOllamaModel(data.ollamaModels[0]);
          }
        }
      } catch {
        // Provider fetch failure — app remains functional, generate will fail gracefully
      }
    }

    loadProviders();
  }, []);

  useEffect(() => {
    function onMouseDown(e: MouseEvent) {
      if (songDropdownRef.current && !songDropdownRef.current.contains(e.target as Node)) {
        setSongDropdownOpen(false);
      }
    }
    document.addEventListener("mousedown", onMouseDown);
    return () => document.removeEventListener("mousedown", onMouseDown);
  }, []);

  // ── Song search ───────────────────────────────────────────────────────────

  function handleSongQueryChange(value: string) {
    setSongQuery(value);
    setSongHighlightIdx(-1);
    if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);
    if (!value.trim()) {
      setSongResults([]);
      setSongDropdownOpen(false);
      return;
    }
    searchDebounceRef.current = setTimeout(async () => {
      try {
        const res = await fetch(`/api/songs?q=${encodeURIComponent(value)}&limit=5`);
        if (!res.ok) throw new Error("Search failed");
        const data = await res.json() as { songs: SongResult[] };
        setSongResults(data.songs);
        setSongDropdownOpen(true);
        setSongSearchFailed(false);
      } catch {
        setSongSearchFailed(true);
        setSongDropdownOpen(false);
      }
    }, 350);
  }

  function handleSongSelect(song: SongResult) {
    update({ mbid: song.mbid, title: song.title, artist: song.artist });
    const label = `${song.title} — ${song.artist}`;
    setSelectedSongLabel(label);
    setSongQuery(label);
    setSongDropdownOpen(false);
    setSongResults([]);
    setSongHighlightIdx(-1);
  }

  function handleSongClear() {
    update({ mbid: "", title: "", artist: "" });
    setSelectedSongLabel("");
    setSongQuery("");
    setSongResults([]);
    setSongDropdownOpen(false);
    setSongHighlightIdx(-1);
    if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);
  }

  function handleSongKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
    if (e.key === "ArrowDown") {
      e.preventDefault();
      setSongHighlightIdx((i) => Math.min(i + 1, songResults.length - 1));
      setSongDropdownOpen(true);
    } else if (e.key === "ArrowUp") {
      e.preventDefault();
      setSongHighlightIdx((i) => Math.max(i - 1, 0));
    } else if (e.key === "Enter") {
      e.preventDefault();
      if (songHighlightIdx >= 0 && songResults[songHighlightIdx]) {
        handleSongSelect(songResults[songHighlightIdx]);
      }
    } else if (e.key === "Escape") {
      setSongDropdownOpen(false);
    }
  }

  function handleProviderChange(id: string) {
    setSelectedProvider(id);
    setUnavailableNotice("");
    localStorage.setItem(PROVIDER_KEY, id);
  }

  function handleOllamaModelChange(model: string) {
    setSelectedOllamaModel(model);
    setUnavailableModelNotice("");
    localStorage.setItem(MODEL_KEY, model);
  }

  // ── File handling ─────────────────────────────────────────────────────────

  const handleFile = useCallback(async (file: File) => {
    if (!file.type.startsWith("audio/") && !file.name.match(/\.(mp3|wav|aac|ogg|flac|m4a|aiff)$/i)) {
      update({ errorMsg: "Please select an audio file (MP3, WAV, AAC, OGG, FLAC, M4A).", stage: "error" });
      return;
    }

    abortRef.current?.abort();
    const abort = new AbortController();
    abortRef.current = abort;

    const base = file.name.replace(/\.[^.]+$/, "");
    const dashIdx = base.indexOf(" - ");
    const autoTitle = dashIdx > -1 ? base.slice(dashIdx + 3) : base;
    const autoArtist = dashIdx > -1 ? base.slice(0, dashIdx) : "";

    update({
      stage: "decoding",
      file,
      detection: null,
      ctp: null,
      warnings: [],
      errorMsg: "",
      title: autoTitle,
      artist: autoArtist,
    });

    try {
      update({ stage: "detecting" });
      const detection = await detectBPM(file, abort.signal);
      update({ detection, stage: "idle" });
    } catch (err) {
      if ((err as Error).name === "AbortError") return;
      update({
        stage: "error",
        errorMsg: `BPM detection failed: ${err instanceof Error ? err.message : String(err)}`,
      });
    }
  }, []);

  function handleDrop(e: React.DragEvent) {
    e.preventDefault();
    setIsDragging(false);
    const file = e.dataTransfer.files[0];
    if (file) handleFile(file);
  }

  function handleFileInput(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (file) handleFile(file);
    e.target.value = "";
  }

  // ── Generation ────────────────────────────────────────────────────────────

  async function runGenerate(providerId: string, ollamaModel: string) {
    if (!state.detection) return;

    const effectiveBpm =
      state.useHalfTime && state.detection.halfTimeBpm
        ? state.detection.halfTimeBpm
        : state.detection.bpm;

    update({ stage: "generating", ctp: null, warnings: [], errorMsg: "" });

    const body: Record<string, unknown> = {
      bpm: effectiveBpm,
      duration: state.detection.duration,
      title: state.title || undefined,
      artist: state.artist || undefined,
      mbid: state.mbid || undefined,
      contributed_by: state.contributedBy || undefined,
      provider: providerId,
    };

    if (providerId === "ollama") {
      body.ollamaModel = ollamaModel;
    }

    const res = await fetch("/api/analyze", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
    });

    const data = await res.json() as { ctp?: CTPDocument; warnings?: string[]; error?: string };

    if (!res.ok) {
      throw new Error(data.error ?? `Server error ${res.status}`);
    }

    update({ ctp: data.ctp ?? null, warnings: data.warnings ?? [], stage: "review" });
  }

  async function handleGenerate() {
    try {
      await runGenerate(selectedProvider, selectedOllamaModel);
    } catch (err) {
      update({
        stage: "error",
        errorMsg: err instanceof Error ? err.message : String(err),
      });
    }
  }

  async function handleRetry() {
    try {
      await runGenerate(selectedProvider, selectedOllamaModel);
    } catch (err) {
      update({
        stage: "error",
        errorMsg: err instanceof Error ? err.message : String(err),
      });
    }
  }

  // ── Submit to DB ──────────────────────────────────────────────────────────

  async function handleSubmit() {
    if (!state.ctp) return;
    update({ stage: "saving" });

    try {
      const res = await fetch("/api/tracks", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(state.ctp),
      });

      const data = await res.json() as { error?: string };
      if (!res.ok) {
        throw new Error(data.error ?? `Server error ${res.status}`);
      }

      update({ stage: "saved" });
    } catch (err) {
      update({
        stage: "error",
        errorMsg: `Save failed: ${err instanceof Error ? err.message : String(err)}`,
      });
    }
  }

  // ── Download CTP file ─────────────────────────────────────────────────────

  function handleDownload() {
    if (!state.ctp) return;
    const json = JSON.stringify(state.ctp, null, 2);
    const blob = new Blob([json], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    const safeName = `${state.ctp.metadata.artist} - ${state.ctp.metadata.title}`
      .replace(/[^\w\s\-]/g, "")
      .replace(/\s+/g, "_")
      .slice(0, 80);
    a.href = url;
    a.download = `${safeName}.ctp.json`;
    a.click();
    URL.revokeObjectURL(url);
  }

  // ── Reset ─────────────────────────────────────────────────────────────────

  function handleReset() {
    abortRef.current?.abort();
    setState(INITIAL_STATE);
    setSongQuery("");
    setSongResults([]);
    setSongDropdownOpen(false);
    setSelectedSongLabel("");
    setSongHighlightIdx(-1);
    if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);
  }

  // ─── Render ───────────────────────────────────────────────────────────────

  const { stage, file, detection, ctp, warnings, errorMsg, useHalfTime } = state;
  const isProcessing =
    stage === "decoding" || stage === "detecting" || stage === "generating" || stage === "saving";

  const selectedProviderInfo = providers.find((p) => p.id === selectedProvider);

  return (
    <div className="space-y-8">

      {/* ── Drop zone ─────────────────────────────────────────────────── */}
      {!file && stage === "idle" && (
        <div
          ref={dropRef}
          onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }}
          onDragLeave={() => setIsDragging(false)}
          onDrop={handleDrop}
          className={`rounded-xl border-2 border-dashed px-8 py-16 text-center transition-colors ${
            isDragging
              ? "border-green-500 bg-green-950/20"
              : "border-zinc-700 hover:border-zinc-500"
          }`}
        >
          <p className="text-4xl mb-4">🎵</p>
          <p className="text-lg font-medium text-zinc-200 mb-2">
            Drop an audio file here
          </p>
          <p className="text-sm text-zinc-500 mb-6">
            MP3, WAV, AAC, OGG, FLAC, M4A — any format your browser supports
          </p>
          <label className="inline-block cursor-pointer rounded-lg bg-green-700 px-6 py-2.5 text-sm font-semibold text-white hover:bg-green-600 transition-colors">
            Browse files
            <input
              type="file"
              accept="audio/*,.mp3,.wav,.aac,.ogg,.flac,.m4a,.aiff"
              className="hidden"
              onChange={handleFileInput}
            />
          </label>
        </div>
      )}

      {/* ── Processing indicator ───────────────────────────────────────── */}
      {isProcessing && (
        <div className="rounded-xl border border-zinc-800 bg-zinc-900/60 px-6 py-8 text-center">
          <div className="mb-3 text-2xl animate-spin inline-block">⟳</div>
          <p className="font-medium text-zinc-200">
            {stage === "decoding" && "Decoding audio…"}
            {stage === "detecting" && "Detecting tempo…"}
            {stage === "generating" && "Generating tempo map…"}
            {stage === "saving" && "Saving to database…"}
          </p>
          {stage === "generating" && selectedProviderInfo?.id === "ollama" && (
            <p className="mt-1 text-sm text-zinc-500">
              Local AI generation may take 30–90 seconds depending on your hardware.
            </p>
          )}
          {stage === "generating" && selectedProviderInfo?.id !== "ollama" && (
            <p className="mt-1 text-sm text-zinc-500">
              Analysing song structure — this takes ~5–15 seconds.
            </p>
          )}
        </div>
      )}

      {/* ── Error panel ────────────────────────────────────────────────── */}
      {stage === "error" && (
        <div className="rounded-xl border border-red-800 bg-red-950/30 px-6 py-5 space-y-4">
          <div>
            <p className="text-red-400 font-medium mb-1">Generation failed</p>
            <p className="text-sm text-red-300">{errorMsg}</p>
          </div>

          {/* Provider selector in error state */}
          {providers.length >= 2 && (
            <div className="space-y-2">
              <label className="block text-xs text-zinc-500">Analysis provider</label>
              <select
                value={selectedProvider}
                onChange={(e) => handleProviderChange(e.target.value)}
                className="rounded-lg border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 focus:border-green-500 focus:outline-none"
              >
                {providers.map((p) => (
                  <option key={p.id} value={p.id}>
                    {p.label}
                  </option>
                ))}
              </select>
            </div>
          )}

          {selectedProvider === "ollama" && ollamaModels.length > 0 && (
            <div className="space-y-2">
              <label className="block text-xs text-zinc-500">Ollama model</label>
              <select
                value={selectedOllamaModel}
                onChange={(e) => handleOllamaModelChange(e.target.value)}
                className="rounded-lg border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 focus:border-green-500 focus:outline-none"
              >
                {ollamaModels.map((m) => (
                  <option key={m} value={m}>
                    {RECOMMENDED_OLLAMA_MODELS.includes(m) ? `★ ${m}` : m}
                  </option>
                ))}
              </select>
            </div>
          )}

          <div className="flex gap-3">
            <button
              onClick={handleRetry}
              className="rounded-lg bg-red-800/60 px-4 py-2 text-sm font-medium text-red-200 hover:bg-red-700/60 transition-colors"
            >
              Retry
            </button>
            <button
              onClick={handleReset}
              className="text-sm text-zinc-400 hover:text-zinc-200 underline self-center"
            >
              Start over
            </button>
          </div>
        </div>
      )}

      {/* ── Detection results + metadata form ─────────────────────────── */}
      {detection && (stage === "idle" || stage === "review" || stage === "saved") && (
        <div className="rounded-xl border border-zinc-800 bg-zinc-900/60 p-6 space-y-5">
          {/* File name + detection summary */}
          <div className="flex flex-wrap items-start justify-between gap-3">
            <div>
              <p className="text-xs text-zinc-500 mb-0.5">Analysed file</p>
              <p className="font-medium text-zinc-200 truncate max-w-sm">{file?.name}</p>
            </div>
            <button
              onClick={handleReset}
              className="text-xs text-zinc-600 hover:text-zinc-400 underline shrink-0"
            >
              Change file
            </button>
          </div>

          <div className="grid grid-cols-3 gap-4 text-center">
            <div className="rounded-lg bg-zinc-800/60 p-4">
              <p className="text-2xl font-bold font-mono text-green-400">
                {useHalfTime && detection.halfTimeBpm
                  ? detection.halfTimeBpm
                  : detection.bpm}
              </p>
              <p className="text-xs text-zinc-500 mt-1">BPM</p>
            </div>
            <div className="rounded-lg bg-zinc-800/60 p-4">
              <p className={`text-2xl font-bold ${confidenceLabel(detection.confidence).color}`}>
                {confidenceLabel(detection.confidence).label}
              </p>
              <p className="text-xs text-zinc-500 mt-1">Confidence</p>
            </div>
            <div className="rounded-lg bg-zinc-800/60 p-4">
              <p className="text-2xl font-bold text-zinc-200">
                {formatDuration(detection.duration)}
              </p>
              <p className="text-xs text-zinc-500 mt-1">Duration</p>
            </div>
          </div>

          {/* Half-time toggle */}
          {detection.halfTimeBpm && (
            <div className="flex items-center gap-3 rounded-lg border border-amber-800/50 bg-amber-950/20 px-4 py-3">
              <span className="text-sm text-amber-300 flex-1">
                Detected double-time pulse — primary BPM may be 2× the actual feel.
                Half-time: <strong>{detection.halfTimeBpm}</strong> BPM
              </span>
              <button
                onClick={() => update({ useHalfTime: !useHalfTime })}
                className={`rounded px-3 py-1 text-xs font-medium transition-colors ${
                  useHalfTime
                    ? "bg-amber-600 text-white"
                    : "border border-amber-700 text-amber-400 hover:bg-amber-900/40"
                }`}
              >
                {useHalfTime ? "Using half-time" : "Use half-time"}
              </button>
            </div>
          )}

          {/* Confidence warning */}
          {detection.confidence < 0.4 && (
            <p className="text-sm text-amber-400">
              Low confidence — the BPM may be inaccurate. Consider a song with a clearer beat, or adjust the detected value manually before generating.
            </p>
          )}

          {/* Metadata form + provider selector */}
          {stage === "idle" && (
            <>
              {/* Song search */}
              <div ref={songDropdownRef} className="relative">
                <div className="flex items-center justify-between mb-1">
                  <label className="block text-xs text-zinc-500">Song</label>
                  {selectedSongLabel && (
                    <button
                      type="button"
                      onClick={handleSongClear}
                      className="text-xs text-zinc-500 hover:text-zinc-300"
                    >
                      ✕ Clear
                    </button>
                  )}
                </div>
                {songSearchFailed ? (
                  <div className="space-y-1">
                    <p className="text-xs text-amber-400">Search unavailable — enter MusicBrainz ID manually:</p>
                    <input
                      value={state.mbid}
                      onChange={(e) => update({ mbid: e.target.value })}
                      placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
                      className="w-full rounded-lg border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm font-mono text-zinc-100 placeholder:text-zinc-600 focus:border-green-500 focus:outline-none"
                    />
                  </div>
                ) : (
                  <input
                    value={songQuery}
                    onChange={(e) => {
                      const val = e.target.value;
                      if (selectedSongLabel) {
                        update({ mbid: "", title: "", artist: "" });
                        setSelectedSongLabel("");
                      }
                      handleSongQueryChange(val);
                    }}
                    onKeyDown={handleSongKeyDown}
                    placeholder="Search by title or artist…"
                    autoComplete="off"
                    className="w-full rounded-lg border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-green-500 focus:outline-none"
                  />
                )}
                {songDropdownOpen && (
                  <div className="absolute z-10 mt-1 w-full rounded-lg border border-zinc-700 bg-zinc-900 shadow-xl overflow-hidden">
                    {songResults.length === 0 ? (
                      <p className="px-3 py-2 text-sm text-zinc-500">
                        No songs found — try the search page first to register the song.
                      </p>
                    ) : (
                      songResults.map((song, i) => (
                        <button
                          key={song.mbid}
                          type="button"
                          onMouseDown={(e) => { e.preventDefault(); handleSongSelect(song); }}
                          className={`w-full text-left px-3 py-2 text-sm transition-colors ${
                            i === songHighlightIdx
                              ? "bg-zinc-700 text-zinc-100"
                              : "text-zinc-300 hover:bg-zinc-800"
                          }`}
                        >
                          <span className="font-medium">{song.title}</span>
                          <span className="text-zinc-500"> — {song.artist}</span>
                        </button>
                      ))
                    )}
                  </div>
                )}
              </div>

              {/* Title + artist manual overrides */}
              <div className="grid gap-3 sm:grid-cols-2">
                <div>
                  <label className="block text-xs text-zinc-500 mb-1">
                    Song title <span className="text-zinc-600">(override)</span>
                  </label>
                  <input
                    value={state.title}
                    onChange={(e) => update({ title: e.target.value })}
                    placeholder="e.g. Bohemian Rhapsody"
                    className="w-full rounded-lg border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-green-500 focus:outline-none"
                  />
                </div>
                <div>
                  <label className="block text-xs text-zinc-500 mb-1">
                    Artist <span className="text-zinc-600">(override)</span>
                  </label>
                  <input
                    value={state.artist}
                    onChange={(e) => update({ artist: e.target.value })}
                    placeholder="e.g. Queen"
                    className="w-full rounded-lg border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-green-500 focus:outline-none"
                  />
                </div>
              </div>

              {/* Contributed by */}
              <div>
                <label className="block text-xs text-zinc-500 mb-1">Your name / handle</label>
                <input
                  value={state.contributedBy}
                  onChange={(e) => update({ contributedBy: e.target.value })}
                  placeholder="e.g. guitar_pete"
                  className="w-full rounded-lg border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-green-500 focus:outline-none"
                />
              </div>

              {/* Provider selector */}
              {unavailableNotice && (
                <div className="rounded-lg border border-amber-800/50 bg-amber-950/20 px-4 py-3">
                  <p className="text-sm text-amber-300">{unavailableNotice}</p>
                </div>
              )}

              {providers.length >= 2 && (
                <div className="space-y-2">
                  <label className="block text-xs text-zinc-500">Analysis provider</label>
                  <select
                    value={selectedProvider}
                    onChange={(e) => handleProviderChange(e.target.value)}
                    className="rounded-lg border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 focus:border-green-500 focus:outline-none"
                  >
                    {providers.map((p) => (
                      <option key={p.id} value={p.id}>
                        {p.label}
                      </option>
                    ))}
                  </select>
                </div>
              )}

              {/* Ollama model selector */}
              {selectedProvider === "ollama" && ollamaModels.length > 0 && (
                <div className="space-y-2">
                  {unavailableModelNotice && (
                    <div className="rounded-lg border border-amber-800/50 bg-amber-950/20 px-3 py-2">
                      <p className="text-xs text-amber-300">{unavailableModelNotice}</p>
                    </div>
                  )}
                  <label className="block text-xs text-zinc-500">Ollama model</label>
                  <select
                    value={selectedOllamaModel}
                    onChange={(e) => handleOllamaModelChange(e.target.value)}
                    className="rounded-lg border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 focus:border-green-500 focus:outline-none"
                  >
                    {ollamaModels.map((m) => (
                      <option key={m} value={m}>
                        {RECOMMENDED_OLLAMA_MODELS.includes(m) ? `★ ${m}` : m}
                      </option>
                    ))}
                  </select>
                </div>
              )}

              {/* Context-aware callouts */}
              {selectedProvider === "algorithmic" && (
                <div className="rounded-lg border border-zinc-700 bg-zinc-800/40 px-4 py-3">
                  <p className="text-sm text-zinc-400">
                    Algorithmic mode estimates song structure from BPM and duration. Section labels and bar counts are approximate — review carefully before submitting.
                  </p>
                </div>
              )}

              {selectedProvider === "ollama" && (
                <div className="rounded-lg border border-zinc-700 bg-zinc-800/40 px-4 py-3">
                  <p className="text-sm text-zinc-400">
                    Local AI generation may take 30–90 seconds depending on your hardware.
                  </p>
                </div>
              )}

              <button
                onClick={handleGenerate}
                className="w-full rounded-lg bg-green-600 py-3 font-semibold text-white hover:bg-green-500 transition-colors"
              >
                Generate tempo map →
              </button>
            </>
          )}
        </div>
      )}

      {/* ── AI-generated CTP review ────────────────────────────────────── */}
      {ctp && (stage === "review" || stage === "saved") && (
        <div className="space-y-6">
          {warnings.length > 0 && (
            <div className="rounded-lg border border-amber-800/50 bg-amber-950/20 px-4 py-3">
              <p className="text-sm font-medium text-amber-300 mb-1">Validation warnings</p>
              <ul className="text-xs text-amber-400 list-disc pl-4 space-y-0.5">
                {warnings.map((w, i) => <li key={i}>{w}</li>)}
              </ul>
            </div>
          )}

          <div>
            <div className="flex items-center justify-between mb-3">
              <h2 className="text-lg font-semibold">Generated tempo map</h2>
              <span className="text-xs text-zinc-600 italic">AI draft — verify before using</span>
            </div>
            <TempoMapEditor ctpDoc={ctp} readOnly />
          </div>

          {/* Actions */}
          {stage === "review" && (
            <div className="flex flex-wrap gap-3">
              <button
                onClick={handleDownload}
                className="flex items-center gap-2 rounded-lg border border-zinc-700 px-5 py-2.5 text-sm font-medium text-zinc-300 hover:border-zinc-500 hover:text-zinc-100 transition-colors"
              >
                ↓ Download .ctp.json
              </button>

              {ctp.metadata.mbid && (
                <button
                  onClick={handleSubmit}
                  className="flex items-center gap-2 rounded-lg bg-green-700 px-5 py-2.5 text-sm font-semibold text-white hover:bg-green-600 transition-colors"
                >
                  Submit to database
                </button>
              )}

              {!ctp.metadata.mbid && (
                <p className="self-center text-xs text-zinc-600">
                  Add a MusicBrainz ID to submit to the database.
                </p>
              )}

              <button
                onClick={() => update({ stage: "idle", ctp: null })}
                className="text-sm text-zinc-600 hover:text-zinc-400 underline self-center"
              >
                Re-generate
              </button>
            </div>
          )}

          {stage === "saved" && (
            <div className="rounded-lg border border-green-800/50 bg-green-950/20 px-4 py-3 flex items-center gap-3">
              <span className="text-green-400">✓</span>
              <div>
                <p className="text-sm font-medium text-green-300">Saved to database</p>
                {ctp.metadata.mbid && (
                  <a
                    href={`/track/${ctp.metadata.mbid}`}
                    className="text-xs text-green-600 hover:underline"
                  >
                    View track page →
                  </a>
                )}
              </div>
              <button
                onClick={handleDownload}
                className="ml-auto text-xs text-zinc-500 hover:text-zinc-300 underline"
              >
                Download .ctp.json
              </button>
            </div>
          )}
        </div>
      )}
    </div>
  );
}
