import { useEffect, useRef, useState } from "react";
import { Loader2, Volume2, Square } from "lucide-react";
import { toast } from "sonner";

/** Streams spoken audio for a block of text so it can be listened to instead of read. */
export function ListenButton({ text, label = "Listen", className = "" }: { text: string; label?: string; className?: string }) {
  const [state, setState] = useState<"idle" | "loading" | "playing">("idle");
  const ctxRef = useRef<AudioContext | null>(null);
  const abortRef = useRef<AbortController | null>(null);
  const sourcesRef = useRef<AudioBufferSourceNode[]>([]);

  useEffect(() => () => stop(), []);

  function stop() {
    abortRef.current?.abort();
    abortRef.current = null;
    for (const s of sourcesRef.current) { try { s.stop(); } catch { /* already stopped */ } }
    sourcesRef.current = [];
    ctxRef.current?.close().catch(() => {});
    ctxRef.current = null;
    setState("idle");
  }

  async function play() {
    const clean = text.trim();
    if (!clean) return;
    setState("loading");
    const controller = new AbortController();
    abortRef.current = controller;

    const ctx = new AudioContext({ sampleRate: 24000 });
    ctxRef.current = ctx;
    if (ctx.state === "suspended") await ctx.resume().catch(() => {});
    let playhead = 0;
    let pending = new Uint8Array(0);

    const playChunk = (incoming: Uint8Array) => {
      const bytes = new Uint8Array(pending.length + incoming.length);
      bytes.set(pending);
      bytes.set(incoming, pending.length);
      const usable = bytes.length - (bytes.length % 2);
      pending = bytes.slice(usable);
      if (usable === 0) return;
      const samples = new Int16Array(bytes.buffer, 0, usable / 2);
      const floats = Float32Array.from(samples, (s) => s / 32768);
      const buffer = ctx.createBuffer(1, floats.length, 24000);
      buffer.copyToChannel(floats, 0);
      const source = ctx.createBufferSource();
      source.buffer = buffer;
      source.connect(ctx.destination);
      playhead = playhead === 0 ? ctx.currentTime + 0.05 : Math.max(playhead, ctx.currentTime);
      source.start(playhead);
      playhead += buffer.duration;
      sourcesRef.current.push(source);
      source.onended = () => {
        sourcesRef.current = sourcesRef.current.filter((s) => s !== source);
        if (sourcesRef.current.length === 0 && controller.signal.aborted === false && abortRef.current === null) setState("idle");
      };
    };

    try {
      const res = await fetch("/api/tts", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ text: clean }),
        signal: controller.signal,
      });
      if (!res.ok || !res.body) throw new Error(await res.text().catch(() => "") || "Could not play this right now");
      setState("playing");

      const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
      let buf = "";
      while (true) {
        const { value, done } = await reader.read();
        if (done) break;
        buf += value;
        const parts = buf.split("\n\n");
        buf = parts.pop() ?? "";
        for (const part of parts) {
          for (const line of part.split("\n")) {
            if (!line.startsWith("data:")) continue;
            const data = line.slice(5).trim();
            if (!data || data === "[DONE]") continue;
            let payload: { type?: string; audio?: string };
            try { payload = JSON.parse(data); } catch { continue; }
            if (payload.type !== "speech.audio.delta" || !payload.audio) continue;
            const binary = atob(payload.audio);
            const bytes = new Uint8Array(binary.length);
            for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
            playChunk(bytes);
          }
        }
      }
      abortRef.current = null;
      const remaining = Math.max(0, playhead - ctx.currentTime);
      window.setTimeout(() => {
        if (abortRef.current === null) setState("idle");
      }, remaining * 1000 + 200);
    } catch (e: any) {
      if (controller.signal.aborted) return;
      toast.error(e?.message || "Could not play this right now");
      stop();
    }
  }

  const active = state !== "idle";
  return (
    <button
      type="button"
      onClick={() => (active ? stop() : play())}
      aria-label={active ? "Stop reading aloud" : `${label} — read aloud`}
      className={`inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-[11px] font-medium bg-white/5 hover:bg-white/10 text-muted-foreground transition-colors ${className}`}
    >
      {state === "loading" ? <Loader2 className="size-3.5 animate-spin" /> : state === "playing" ? <Square className="size-3.5" /> : <Volume2 className="size-3.5" />}
      <span>{state === "loading" ? "Preparing…" : state === "playing" ? "Stop" : label}</span>
    </button>
  );
}
