"use client";

import { useEffect, useState } from "react";
import { describeJob, etaLabel, timeAgo } from "./_ui/status";
import { StatTiles } from "./_ui/stat-tiles";
import { SectionHeader } from "./_ui/section-header";
import { JobRow } from "./_ui/job-row";
import { ProgressBar } from "./_ui/progress-bar";
import { PressedList } from "./_ui/pressed-list";
import { KebabMenu } from "./_ui/kebab-menu";
import { toast } from "./_ui/toast";

type Tab = "active" | "queue" | "errors";

// Split the on-the-press rows into three honest buckets so active presses aren't buried between
// queued items and errors. Active = being worked (searching/downloading/finishing), Queue =
// waiting to be claimed, Errors = needs attention.
function categoryOf(state: string, stage: string): Tab {
  const kind = describeJob(state, stage).kind;
  if (kind === "attention") return "errors";
  if (state === "requested") return "queue";
  return "active";
}

const TAB_LABEL: Record<Tab, string> = { active: "Active", queue: "In queue", errors: "Errors" };
const TAB_EMPTY: Record<Tab, string> = {
  active: "Nothing pressing right now.",
  queue: "The queue is empty.",
  errors: "No errors — everything's flowing.",
};

type Row = {
  id: string;
  artist: string;
  album: string;
  status: string;
  createdAt?: string;
  rgMbid?: string | null;
  job: {
    state: string;
    currentStage: string;
    attempts: number;
    error: string | null;
    claimedAt: string | null;
    updatedAt: string | null;
    candidateCount: number;
    chosen: { source: string; format: string; trackCount: number } | null;
    downloadProgress: number;
    downloadEtaSeconds: number | null;
    paused: boolean;
  } | null;
};

function jobOf(r: Row) {
  return (
    r.job ?? {
      state: "requested",
      currentStage: "intake",
      attempts: 0,
      error: null,
      claimedAt: null,
      updatedAt: null,
      candidateCount: 0,
      chosen: null,
      downloadProgress: 0,
      downloadEtaSeconds: null,
      paused: false,
    }
  );
}

function pressedMark(r: Row): string {
  const when = r.job?.updatedAt ?? r.createdAt; // when it was pressed, not when it was requested
  if (!when) return "Lyra";
  const d = new Date(when);
  return `Lyra · ${d.toLocaleDateString("en-GB", { day: "2-digit", month: "short" }).toUpperCase()}`;
}

export function Queue() {
  const [rows, setRows] = useState<Row[]>([]);
  const [wanted, setWanted] = useState(0);
  const [watching, setWatching] = useState(0);
  const [artist, setArtist] = useState("");
  const [album, setAlbum] = useState("");
  const [paused, setPaused] = useState(false);
  // null = follow the smart default (Active if anything's active, else Queue, else Errors); once
  // the user picks a tab, their choice sticks.
  const [tab, setTab] = useState<Tab | null>(null);

  async function refresh() {
    const [reqRes, wantRes, artRes, floorRes] = await Promise.all([
      fetch("/api/requests"),
      fetch("/api/wanted"),
      fetch("/api/artists"),
      fetch("/api/floor"),
    ]);
    setRows((await reqRes.json()).requests ?? []);
    setWanted(((await wantRes.json()).wanted ?? []).length);
    setWatching(((await artRes.json()).artists ?? []).length);
    setPaused((await floorRes.json()).paused ?? false);
  }

  useEffect(() => {
    refresh();
    const id = setInterval(refresh, 2000);
    return () => clearInterval(id);
  }, []);

  async function retry(id: string) {
    const res = await fetch(`/api/requests/${id}/retry`, { method: "POST" }).catch(() => null);
    if (!res?.ok) {
      toast("Couldn't retry");
      return;
    }
    toast("Retrying…");
    refresh();
  }

  async function floorAction(action: string, note: string) {
    const res = await fetch("/api/floor", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ action }),
    }).catch(() => null);
    if (!res?.ok) {
      toast("Couldn't update the press");
      return;
    }
    toast(note);
    refresh();
  }

  async function clearPress() {
    const queued = rows.filter((r) => jobOf(r).state === "requested").length;
    if (queued === 0) return;
    if (!confirm(`Clear ${queued} queued item${queued === 1 ? "" : "s"} from the press?`)) return;
    await floorAction("clear", "Cleared the queue");
  }

  async function stopPress() {
    if (!confirm("Stop the current download and pause the press? The worker restarts and the aborted album re-downloads when you resume.")) return;
    await floorAction("stop", "Stopping the press…");
  }

  async function pauseItem(id: string, next: boolean) {
    const res = await fetch(`/api/requests/${id}/pause`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ paused: next }),
    }).catch(() => null);
    if (!res?.ok) {
      toast("Couldn't update the item");
      return;
    }
    refresh();
  }

  async function removeItem(id: string) {
    const res = await fetch(`/api/requests/${id}`, { method: "DELETE" }).catch(() => null);
    if (!res?.ok) {
      toast("Couldn't remove the item");
      return;
    }
    toast("Removed from the press");
    refresh();
  }

  async function submit(e: React.FormEvent) {
    e.preventDefault();
    if (!artist.trim() || !album.trim()) return;
    const label = album.trim();
    const res = await fetch("/api/requests", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ artist, album }),
    }).catch(() => null);
    if (!res?.ok) {
      toast(`Couldn't queue ${label}`);
      return;
    }
    toast(`Queued ${label}`);
    setArtist("");
    setAlbum("");
    refresh();
  }

  const active = rows.filter((r) => jobOf(r).state !== "imported");
  const buckets: Record<Tab, Row[]> = { active: [], queue: [], errors: [] };
  for (const r of active) {
    const j = jobOf(r);
    buckets[categoryOf(j.state, j.currentStage)].push(r);
  }
  // The API returns rows newest-first, but the worker claims the OLDEST queued job next
  // (claim_next: ORDER BY createdAt ASC). Order the queue tab to match, so the item that runs
  // next sits at the top instead of the bottom.
  const ts = (r: Row) => (r.createdAt ? new Date(r.createdAt).getTime() : 0);
  buckets.queue.sort((a, b) => ts(a) - ts(b));
  // "Next up" = the first queued item the worker will actually claim (oldest, not individually
  // paused) — mark it so the ordering reads clearly.
  const nextUpId = buckets.queue.find((r) => !jobOf(r).paused)?.id ?? null;
  const queuedCount = buckets.queue.length;
  const downloading = buckets.active.length > 0;
  // Smart default: land on the tab that has something worth looking at.
  const defaultTab: Tab = buckets.active.length ? "active" : queuedCount ? "queue" : "errors";
  const activeTab: Tab = tab ?? defaultTab;
  const shown = buckets[activeTab];

  // "Recently Pressed": imported albums ordered by when they were PRESSED (job.updatedAt ≈ import
  // time), newest first — an album requested long ago but imported just now is recently pressed
  // even though its Request is old (rows arrive by request createdAt, which buried fresh imports).
  // Dedupe by artist+album (re-downloads leave several completed Requests; keep the most recent).
  const seenPressed = new Set<string>();
  const pressedAll = rows
    .filter((r) => jobOf(r).state === "imported")
    .sort((a, b) => new Date(jobOf(b).updatedAt ?? 0).getTime() - new Date(jobOf(a).updatedAt ?? 0).getTime())
    .filter((r) => {
      const key = `${r.artist} ${r.album}`.toLowerCase();
      if (seenPressed.has(key)) return false;
      seenPressed.add(key);
      return true;
    });
  const pressed = pressedAll.slice(0, 10); // show only the 10 most recently pressed

  const now = Date.now();
  function renderRow(r: Row) {
    const j = jobOf(r);
    const d = describeJob(j.state, j.currentStage);
    const attention = d.kind === "attention";

    let meta: string | undefined;
    if (j.state === "downloading" || j.state === "tagging") {
      const parts: string[] = [];
      if (j.chosen) parts.push(`${j.chosen.source} · ${j.chosen.format}`);
      const elapsed = timeAgo(j.claimedAt ?? j.updatedAt, now);
      if (elapsed) parts.push(elapsed);
      meta = parts.join(" · ") || undefined;
    } else if (j.state === "matching" || j.state === "matched") {
      meta =
        j.candidateCount > 0
          ? `${j.candidateCount} sources${j.chosen ? ` · best: ${j.chosen.source} ${j.chosen.format}` : ""}`
          : "Searching sources…";
    } else if (j.state === "requested") {
      const elapsed = timeAgo(r.createdAt, now);
      meta = elapsed ? `In the queue · ${elapsed}` : "In the queue";
    } else {
      meta = undefined;
    }

    const note = attention ? (
      <>
        {j.error?.trim() || "No source met the quality cutoff, or the pipeline stalled."}{" "}
        <button className="btn sm" onClick={() => retry(r.id)}>
          Retry
        </button>
      </>
    ) : j.state === "requested" ? (
      <span className="item-controls">
        {j.paused ? (
          <>
            <span className="chip">Paused</span>{" "}
            <button className="btn sm ghost" onClick={() => pauseItem(r.id, false)}>
              Resume
            </button>
          </>
        ) : (
          <button className="btn sm ghost" onClick={() => pauseItem(r.id, true)}>
            Pause
          </button>
        )}{" "}
        <button className="btn sm ghost" onClick={() => removeItem(r.id)}>
          Remove
        </button>
      </span>
    ) : undefined;

    let bar: React.ReactNode;
    if (j.state === "matching" || j.state === "matched") {
      bar = <ProgressBar kind="working" indeterminate caption="searching" />;
    } else if (j.state === "downloading") {
      const pct = Math.round((j.downloadProgress || 0) * 100);
      if (pct <= 0) {
        // Bytes haven't started flowing yet — Qobuz login/metadata/artwork, a Soulseek peer
        // queue, or a YouTube info-fetch. Show an indeterminate bar instead of a stuck-looking
        // 0% (it flips to the % bar on the first byte).
        bar = <ProgressBar kind="working" indeterminate caption="preparing…" />;
      } else {
        const total = j.chosen?.trackCount ?? 0;
        const done = total ? Math.round((pct / 100) * total) : 0;
        const base = total ? `${pct}% · ${done}/${total} tracks` : `${pct}%`;
        const eta = etaLabel(j.downloadEtaSeconds);
        bar = <ProgressBar kind="working" percent={pct} caption={eta ? `${base} · ${eta}` : base} />;
      }
    } else if (j.state === "tagging") {
      bar = <ProgressBar kind="verify" indeterminate caption="finishing" />;
    } else {
      bar = undefined;
    }

    return (
      <JobRow
        key={r.id}
        artist={r.artist}
        album={r.album}
        kind={d.kind}
        label={d.label}
        marker={r.id === nextUpId ? "Next up" : undefined}
        meta={meta}
        note={note}
        bar={bar}
      />
    );
  }

  return (
    <div>
      <StatTiles
        items={[
          { k: "On the press", v: active.length },
          { k: "Pressed", v: pressedAll.length, accent: true },
          { k: "Wanted", v: wanted },
          { k: "Watching", v: watching, unit: "artists" },
        ]}
      />

      <div className="dept has-actions">
        <h2>On the Press</h2>
        <span className="fill" />
        {paused ? <span className="count">paused</span> : null}
        <KebabMenu label="Press actions">
          {paused ? (
            <button className="menu-item" onClick={() => floorAction("resume", "Press resumed")}>
              Resume the press
            </button>
          ) : (
            <button className="menu-item" onClick={() => floorAction("pause", "Press paused")}>
              Pause the press
            </button>
          )}
          <button className="menu-item" onClick={clearPress} disabled={queuedCount === 0}>
            Clear queue
          </button>
          {downloading ? (
            <button className="menu-item danger" onClick={stopPress}>
              Stop now
            </button>
          ) : null}
        </KebabMenu>
      </div>

      <form className="request-form" onSubmit={submit}>
        <label className="field">
          <span>Artist</span>
          <input aria-label="artist" placeholder="Artist" value={artist} onChange={(e) => setArtist(e.target.value)} />
        </label>
        <label className="field">
          <span>Album</span>
          <input aria-label="album" placeholder="Album" value={album} onChange={(e) => setAlbum(e.target.value)} />
        </label>
        <button type="submit" className="btn accent">
          Request
        </button>
      </form>

      {active.length === 0 ? (
        <p className="empty">The press is quiet. Queue a release above, or send one over from Wanted or Discover.</p>
      ) : (
        <>
          <div className="tabs" role="tablist">
            {(["active", "queue", "errors"] as Tab[]).map((t) => (
              <button
                key={t}
                role="tab"
                aria-selected={activeTab === t}
                className={`tab${activeTab === t ? " active" : ""}${t === "errors" && buckets.errors.length ? " has-errors" : ""}`}
                onClick={() => setTab(t)}
              >
                {TAB_LABEL[t]}
                <span className="n">{buckets[t].length}</span>
              </button>
            ))}
          </div>
          {shown.length === 0 ? (
            <p className="empty">{TAB_EMPTY[activeTab]}</p>
          ) : (
            <section className="floor">{shown.map(renderRow)}</section>
          )}
        </>
      )}

      {pressed.length > 0 ? (
        <>
          <SectionHeader title="Recently Pressed" note={`last ${pressed.length} · ${pressedAll.length} in library`} />
          <PressedList
            items={pressed.map((r) => ({ id: r.id, artist: r.artist, album: r.album, matrix: pressedMark(r), rgMbid: r.rgMbid ?? null }))}
          />
        </>
      ) : null}

      <div className="colophon">
        <span>Lyra — self-hosted pressing plant</span>
        <span>MusicBrainz · Qobuz · Soulseek · YouTube</span>
      </div>
    </div>
  );
}
