"use client";

import Link from "next/link";
import { useRouter } from "next/navigation";
import { useTransition } from "react";
import { ManualSearch } from "../manual-search";
import { setTmdbLinkVerified } from "./actions";
import { cancelReplacement, requestReplacement } from "./series/replace-actions";

export type InventoryRow = {
  id: string;
  title: string;
  year: number | null;
  overview: string | null;
  seasonCount: string;
  episodeCount: string;
  fileCount: string;
  totalBytes: string;
  qualities: string;
  codecs: string;
  isClassic: boolean;
  // Every tier this title has files on, regardless of the active tier filter.
  tiers: { tier: string; files: number }[];
  // 0 means nothing here has been read by ffprobe, so quality/codec are
  // filename guesses rather than measurements.
  probedFiles: number;
  // The TMDB id this title is linked to, and whether a person has confirmed
  // that link is the right one. Null id means nothing is linked at all.
  tmdbId: string | null;
  tmdbVerified: boolean;
};

// "ok" mirrored at the same size · "stale" present but a different size ·
// "missing" no backup copy · "n/a" archive, which is not mirrored.
export type BackupState = "ok" | "stale" | "missing" | "n/a";

export type FileEntry = {
  id: string;
  relativePath: string;
  sizeBytes: number;
  quality: string | null;
  codec: string | null;
  width: number | null;
  height: number | null;
  tier: string | null;
  edition: string | null;
  probed: boolean;
  backup: BackupState;
  replaceRequested: boolean;
};

export type EpisodeEntry = {
  id: string;
  number: number;
  title: string | null;
  airDate: string | null;
  files: FileEntry[];
};

export type SeasonEntry = {
  id: string;
  number: number;
  episodes: EpisodeEntry[];
};

function formatBytes(value: number | string | null) {
  const bytes = Number(value ?? 0);
  if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
  const units = ["B", "KB", "MB", "GB", "TB"];
  let size = bytes;
  let unit = 0;
  while (size >= 1024 && unit < units.length - 1) {
    size /= 1024;
    unit += 1;
  }
  return size.toFixed(unit === 0 ? 0 : 1) + " " + units[unit];
}

function seasonLabel(seasonNumber: number) {
  return seasonNumber === 0 ? "Specials" : "Season " + String(seasonNumber).padStart(2, "0");
}

// Live is the tier that matters most, so it reads as good; archive is merely
// informational; backup missing is what a mirror problem looks like.
const TIER_BADGE: Record<string, string> = {
  live: "border-admin-good text-admin-text",
  backup: "border-admin-line text-admin-muted",
  archive: "border-admin-accent text-admin-accent",
};

// Backup coverage for a whole episode: worst state of its live files wins, so
// a season that is half-mirrored never reads as fully backed up.
function episodeBackupState(files: FileEntry[]): BackupState {
  const live = files.filter((file) => file.tier === "live");
  if (!live.length) return "n/a";
  if (live.some((file) => file.backup === "missing")) return "missing";
  if (live.some((file) => file.backup === "stale")) return "stale";
  return "ok";
}

function BackupFlair({ state }: { state: BackupState }) {
  if (state === "n/a") return null;
  if (state === "ok") {
    return (
      <span title="Mirrored to backup at the same size" className="text-xs font-semibold text-admin-good">
        ✓ backed up
      </span>
    );
  }
  if (state === "stale") {
    return (
      <span title="A backup copy exists at this path but its size differs from live" className="text-xs font-semibold text-admin-warn">
        ! backup differs
      </span>
    );
  }
  return (
    <span title="No backup copy of this file" className="text-xs font-semibold text-admin-warn">
      ✗ not backed up
    </span>
  );
}

export function InventoryTable({
  kind,
  rows,
  query,
  page,
  tier,
  link,
  expandedId,
  seasons,
  movieFiles,
}: {
  kind: "movie" | "tv";
  rows: InventoryRow[];
  query: string;
  page: number;
  tier: string;
  link: string;
  expandedId: string | null;
  seasons: SeasonEntry[];
  movieFiles: FileEntry[];
}) {
  const router = useRouter();
  const [isPending, startTransition] = useTransition();

  const columnCount = kind === "tv" ? 11 : 10;

  function hrefFor(nextExpanded: string | null) {
    const params = new URLSearchParams();
    if (kind !== "movie") params.set("kind", kind);
    if (tier !== "live") params.set("tier", tier);
    if (link !== "any") params.set("md", link);
    if (query) params.set("q", query);
    if (page > 1) params.set("page", String(page));
    if (nextExpanded) params.set("expanded", nextExpanded);
    const value = params.toString();
    return value ? "/admin/inventory?" + value : "/admin/inventory";
  }

  function toggleRow(id: string) {
    const next = id === expandedId ? null : id;
    startTransition(() => router.replace(hrefFor(next), { scroll: false }));
  }

  return (
    <div className="overflow-x-auto">
      <table className="min-w-full text-left text-sm">
        <thead className="border-b border-admin-line bg-admin-subpanel text-xs uppercase tracking-[0.18em] text-admin-muted">
          <tr>
            <th className="w-10 px-3 py-3">Pick</th>
            <th className="min-w-72 px-3 py-3">Title</th>
            {kind === "tv" ? (
              <>
                <th className="px-3 py-3">Seasons</th>
                <th className="px-3 py-3">Episodes</th>
              </>
            ) : (
              <th className="px-3 py-3">Year</th>
            )}
            <th className="px-3 py-3">Files</th>
            <th className="px-3 py-3">Size</th>
            <th className="px-3 py-3">Quality</th>
            <th className="px-3 py-3">Codec</th>
            <th className="px-3 py-3">Tiers</th>
            <th className="px-3 py-3">TMDB</th>
            <th className="px-3 py-3">State</th>
          </tr>
        </thead>

        {rows.length ? rows.map((item) => {
          const isExpanded = item.id === expandedId;
          return (
            <tbody key={item.id} className={"border-b border-admin-line " + (item.isClassic ? "bg-admin-subpanel shadow-[inset_4px_0_0_rgba(204,177,95,0.95)]" : "")}>
              <tr
                onClick={() => toggleRow(item.id)}
                aria-expanded={isExpanded}
                className={"cursor-pointer align-top hover:bg-admin-subpanel " + (isExpanded ? "bg-admin-subpanel" : "")}
              >
                <td className="px-3 py-3">
                  {/* Selecting for bulk actions must not toggle the row. */}
                  <input
                    data-inventory-select="true"
                    type="checkbox"
                    value={item.id}
                    onClick={(event) => event.stopPropagation()}
                    className="h-4 w-4 accent-[var(--admin-accent)]"
                    aria-label={"Select " + item.title}
                  />
                </td>
                <td className="px-3 py-3">
                  <p className="font-medium text-admin-text">
                    <span aria-hidden className="mr-2 inline-block w-3 text-admin-muted">{isExpanded ? "▾" : "▸"}</span>
                    {item.title}
                    {/* The series page exists and nothing linked to it, so the
                        only way in was to know the URL. The row itself is the
                        expander, so this has to be its own target and has to
                        stop the click reaching the row. */}
                    {kind === "tv" ? (
                      <Link
                        href={`/admin/inventory/series/${item.id}`}
                        onClick={(event) => event.stopPropagation()}
                        className="ml-2 text-xs font-normal text-admin-muted underline decoration-dotted underline-offset-2 transition hover:text-admin-accent"
                      >
                        full detail
                      </Link>
                    ) : null}
                  </p>
                  <p className="mt-1 line-clamp-1 max-w-2xl pl-5 text-xs text-admin-muted">
                    {item.overview ?? "No canonical overview stored yet."}
                  </p>
                </td>
                {kind === "tv" ? (
                  <>
                    <td className="whitespace-nowrap px-3 py-3">{item.seasonCount}</td>
                    <td className="whitespace-nowrap px-3 py-3">{item.episodeCount}</td>
                  </>
                ) : (
                  <td className="whitespace-nowrap px-3 py-3 text-admin-muted">{item.year ?? "Unknown"}</td>
                )}
                <td className="whitespace-nowrap px-3 py-3">{item.fileCount}</td>
                <td className="whitespace-nowrap px-3 py-3">{formatBytes(item.totalBytes)}</td>
                <td className="whitespace-nowrap px-3 py-3 text-admin-muted">
                  {item.qualities || "Unknown"}
                  {item.probedFiles === 0 ? (
                    <span title="No file here has been read by ffprobe — quality is guessed from the filename" className="ml-2 text-xs text-admin-warn">
                      unverified
                    </span>
                  ) : null}
                </td>
                <td className="whitespace-nowrap px-3 py-3 text-admin-muted">{item.codecs || "Unknown"}</td>
                <td className="whitespace-nowrap px-3 py-3">
                  <div className="flex flex-wrap gap-1">
                    {item.tiers.length ? item.tiers.map((entry) => (
                      <span
                        key={entry.tier}
                        title={entry.files + " file(s) on " + entry.tier}
                        className={
                          "rounded border px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide " +
                          (TIER_BADGE[entry.tier] ?? "border-admin-line text-admin-muted")
                        }
                      >
                        {entry.tier} {entry.files}
                      </span>
                    )) : <span className="text-xs text-admin-muted">—</span>}
                  </div>
                </td>
                {/* The button lives inside the row, and the row is a toggle.
                    Stopping the click here is what keeps confirming a link
                    from also expanding the record underneath it. */}
                <td className="whitespace-nowrap px-3 py-3" onClick={(event) => event.stopPropagation()}>
                  {item.tmdbId ? (
                    <form action={setTmdbLinkVerified} className="flex items-center gap-2">
                      <input type="hidden" name="mediaItemId" value={item.id} />
                      <input type="hidden" name="verified" value={item.tmdbVerified ? "0" : "1"} />
                      <a
                        href={"https://www.themoviedb.org/" + (kind === "tv" ? "tv" : "movie") + "/" + item.tmdbId}
                        target="_blank"
                        rel="noreferrer"
                        title="Open this title on TMDB to check the link"
                        className="text-xs text-admin-accent underline-offset-2 hover:underline"
                      >
                        {item.tmdbId}
                      </a>
                      <button
                        type="submit"
                        title={item.tmdbVerified ? "Withdraw confirmation of this link" : "Confirm this link is the right title"}
                        className={
                          "rounded-md border px-2 py-1 text-xs font-semibold " +
                          (item.tmdbVerified
                            ? "border-admin-good text-admin-text"
                            : "border-admin-line text-admin-muted hover:border-admin-accent hover:text-admin-accent")
                        }
                      >
                        {item.tmdbVerified ? "Verified" : "Verify"}
                      </button>
                    </form>
                  ) : (
                    <span title="No TMDB id is linked to this title at all" className="text-xs text-admin-warn">none</span>
                  )}
                </td>
                <td className="whitespace-nowrap px-3 py-3">
                  {item.isClassic
                    ? <span className="rounded-md border border-admin-accent bg-admin-subpanel px-2 py-1 text-xs font-semibold text-admin-accent">Timeless</span>
                    : <span className="text-admin-muted">Managed</span>}
                </td>
              </tr>

              {isExpanded ? (
                <tr>
                  <td colSpan={columnCount} className="px-3 pb-3">
                    {isPending ? <p className="px-2 py-3 text-xs text-admin-muted">Loading…</p> : null}

                    {kind === "tv" ? (
                      <div className="space-y-2">
                        <div className="flex justify-end">
                          <Link
                            href={"/admin/inventory/series/" + item.id}
                            onClick={(event) => event.stopPropagation()}
                            className="admin-nav-button px-2 py-1.5 text-xs font-medium"
                          >
                            Open full series view
                          </Link>
                        </div>

                        {/* Indent level 1: seasons */}
                        {seasons.map((season) => {
                          const missing = season.episodes.filter((episode) => !episode.files.length).length;
                          const held = season.episodes.filter((episode) => episode.files.length);
                          const unbacked = held.filter((episode) => episodeBackupState(episode.files) !== "ok" && episodeBackupState(episode.files) !== "n/a").length;
                          return (
                            <div key={season.id} className="ml-6 rounded-md border border-admin-line bg-admin-subpanel">
                              <div className="flex flex-wrap items-center justify-between gap-2 border-b border-admin-line px-3 py-2">
                                <p className="text-sm font-semibold">{seasonLabel(season.number)}</p>
                                <p className="text-xs text-admin-muted">
                                  {season.episodes.length} episodes
                                  {missing ? <span className="text-admin-warn"> · {missing} missing</span> : null}
                                  {unbacked ? <span className="text-admin-warn"> · {unbacked} not backed up</span> : null}
                                  {held.length && !unbacked ? <span className="text-admin-good"> · fully backed up</span> : null}
                                </p>
                              </div>

                              {/* Indent level 2: episodes */}
                              <div className="divide-y divide-admin-line">
                                {season.episodes.map((episode) => {
                                  const hasFile = episode.files.length > 0;
                                  return (
                                    <div key={episode.id} className={"ml-6 px-3 py-2 " + (hasFile ? "" : "bg-admin-missing")}>
                                      <div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
                                        <span className="font-mono text-xs text-admin-muted">
                                          {"E" + String(episode.number).padStart(2, "0")}
                                        </span>
                                        <span className="text-sm">{episode.title ?? "Untitled episode"}</span>
                                        {episode.airDate ? (
                                          <span className="text-xs text-admin-muted">{episode.airDate}</span>
                                        ) : null}
                                        {hasFile ? (
                                          <BackupFlair state={episodeBackupState(episode.files)} />
                                        ) : (
                                          <span className="text-xs font-semibold uppercase tracking-wide text-admin-muted">
                                            Not in collection
                                          </span>
                                        )}
                                        {/* Offered on every episode, not only the missing ones: the
                                            other reason to search is replacing a copy that is present
                                            but bad. */}
                                        <span className="ml-auto">
                                          <ManualSearch
                                            mediaItemId={item.id}
                                            seasonNumber={season.number}
                                            episodeNumber={episode.number}
                                            label={item.title + " S" + String(season.number).padStart(2, "0") + "E" + String(episode.number).padStart(2, "0")}
                                            compact
                                          />
                                        </span>
                                      </div>

                                      {episode.files.map((file) => (
                                        <div key={file.id} className="mt-1 flex flex-wrap items-center gap-2 text-xs text-admin-muted">
                                          {file.tier ? (
                                            <span className={"rounded border px-1.5 py-0.5 uppercase " + (TIER_BADGE[file.tier] ?? "border-admin-line")}>
                                              {file.tier}
                                            </span>
                                          ) : null}
                                          {file.edition ? (
                                            <span className="rounded border border-admin-accent px-1.5 py-0.5 font-semibold uppercase text-admin-accent">
                                              {file.edition === "bw" ? "B&W" : file.edition}
                                            </span>
                                          ) : null}
                                          <span className="font-semibold text-admin-text">{file.quality ?? "Unknown"}</span>
                                          {file.width && file.height ? <span>{file.width}x{file.height}</span> : null}
                                          <span>{file.codec ?? "Unknown"}</span>
                                          {file.probed ? null : (
                                            <span title="Not read by ffprobe — quality and codec are guessed from the filename" className="text-admin-warn">
                                              unverified
                                            </span>
                                          )}
                                          <span>{formatBytes(file.sizeBytes)}</span>
                                          <span className="break-all">{file.relativePath}</span>
                                          {/* Live only: a replacement request
                                              is about the copy being served,
                                              and the reaper never looks at any
                                              other tier. */}
                                          {file.tier === "live" ? (
                                            file.replaceRequested ? (
                                              <form action={cancelReplacement} className="contents">
                                                <input type="hidden" name="fileId" value={file.id} />
                                                <span className="font-semibold text-admin-warn">replacing</span>
                                                <button
                                                  type="submit"
                                                  className="rounded border border-admin-line px-1.5 py-0.5 transition hover:text-admin-text"
                                                >
                                                  cancel
                                                </button>
                                              </form>
                                            ) : (
                                              <form action={requestReplacement} className="contents">
                                                <input type="hidden" name="fileId" value={file.id} />
                                                <button
                                                  type="submit"
                                                  title="Look for a better copy, and delete this one once it arrives"
                                                  className="rounded border border-admin-line px-1.5 py-0.5 transition hover:border-admin-accent hover:text-admin-accent"
                                                >
                                                  replace
                                                </button>
                                              </form>
                                            )
                                          ) : null}
                                        </div>
                                      ))}
                                    </div>
                                  );
                                })}
                              </div>
                            </div>
                          );
                        })}

                        {!seasons.length && !isPending ? (
                          <p className="ml-6 px-3 py-2 text-xs text-admin-muted">No seasons recorded for this series.</p>
                        ) : null}
                      </div>
                    ) : (
                      <div className="ml-6 space-y-2">
                      <div className="flex justify-end">
                        <ManualSearch mediaItemId={item.id} label={item.title} compact />
                      </div>
                      <div className="divide-y divide-admin-line rounded-md border border-admin-line bg-admin-subpanel">
                        {movieFiles.map((file) => (
                          <div key={file.id} className="grid gap-2 px-3 py-2 text-xs lg:grid-cols-[5rem_1fr_7rem_6rem_6rem_6rem]">
                            <span>
                              {file.tier ? (
                                <span className={"rounded border px-1.5 py-0.5 text-[10px] font-semibold uppercase " + (TIER_BADGE[file.tier] ?? "border-admin-line text-admin-muted")}>
                                  {file.tier}
                                </span>
                              ) : null}
                            </span>
                            <span className="break-all text-admin-muted">{file.relativePath}</span>
                            <span>{formatBytes(file.sizeBytes)}</span>
                            <span>{file.quality ?? "Unknown"}{file.edition ? ` (${file.edition === "bw" ? "B&W" : file.edition})` : ""}</span>
                            <span>{file.width && file.height ? `${file.width}x${file.height}` : "—"}</span>
                            <span>{file.codec ?? "Unknown"}</span>
                          </div>
                        ))}
                        {!movieFiles.length && !isPending ? (
                          <p className="px-3 py-2 text-xs text-admin-muted">
                            {tier === "all" ? "No files on any tier." : "No files on the " + tier + " tier."}
                          </p>
                        ) : null}
                      </div>
                      </div>
                    )}
                  </td>
                </tr>
              ) : null}
            </tbody>
          );
        }) : (
          <tbody>
            <tr>
              <td colSpan={columnCount} className="p-5 text-sm text-admin-muted">
                {kind === "tv" ? "No main television inventory records matched." : "No main movie inventory records matched."}
              </td>
            </tr>
          </tbody>
        )}
      </table>
    </div>
  );
}
