"use client";

import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect, useMemo, useRef, useState } from "react";
import { ChevronDown, ChevronLeft, ChevronRight, Play, Search, X } from "lucide-react";

import type { EpisodeOption, SeasonOption } from "@/components/player/watch-client";
import { cn } from "@/lib/utils";

/**
 * Collapsible episode panel for TV and anime watch pages.
 *
 * Geometry
 *   The panel is `position: fixed` against the right edge and full viewport
 *   height rather than a grid column, so opening and closing it never reflows
 *   the `<iframe>`: the video keeps its exact box and never re-resolves its
 *   source. That is the whole reason this is an overlay and not a layout
 *   sibling — a width change on an ancestor would restart playback.
 *
 *   * collapsed (desktop) — nothing but the toggle tab on the right edge
 *   * expanded (desktop)  — a 380px panel sliding in over the artwork, with a
 *                           dimmed scrim so the player still reads as the focus
 *   * phone              — a full-height drawer at 92vw with a scrim that closes
 *                           it on tap
 *
 * Density
 *   The panel is sized so that a whole screen of episodes is browsable without
 *   scrolling through a wall of oversized cards. The row is thumb-driven rather
 *   than text-driven: the still is a fixed 16:9 box, the text beside it is
 *   clamped to one title line, one metadata line and two overview lines, and the
 *   title is never allowed to grow the row. Header, season selector and search
 *   are compact and pinned; only the list scrolls, so none of the fixed chrome
 *   is competing with the episodes for the same vertical space.
 *
 * Data
 *   Every field comes from the TMDB season payload the watch page already
 *   fetched (`app/watch/tv/[id]`): still image, episode number, name, runtime
 *   and overview. Selecting an episode is a navigation to the existing
 *   `/watch/tv/[id]?season=&episode=` route, so the player re-resolves its
 *   source through exactly the same code path as a deep link.
 */
export function EpisodePanel({
  open,
  onToggle,
  tmdbId,
  title,
  season,
  episode,
  episodes,
  seasons,
}: {
  open: boolean;
  onToggle: (next: boolean) => void;
  tmdbId: number;
  title: string;
  season: number;
  episode: number;
  episodes: EpisodeOption[];
  seasons?: SeasonOption[];
}) {
  const router = useRouter();
  const [query, setQuery] = useState("");
  const listRef = useRef<HTMLOListElement>(null);

  /*
   * Close the drawer on Escape. The desktop panel is not a modal — the player
   * stays reachable — so this is a convenience, not a focus trap.
   */
  useEffect(() => {
    if (!open) return;
    const onKey = (event: KeyboardEvent) => {
      if (event.key === "Escape") onToggle(false);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onToggle, open]);

  const filtered = useMemo(() => {
    const q = query.trim().toLowerCase();
    if (!q) return episodes;

    const asNumber = Number.parseInt(q.replace(/\D/g, ""), 10);

    return episodes.filter((ep) => {
      if (ep.name.toLowerCase().includes(q)) return true;
      if (ep.overview?.toLowerCase().includes(q)) return true;
      // A bare number ("3") or a zero-padded one ("03") matches the episode number.
      if (Number.isFinite(asNumber) && ep.episode === asNumber) return true;
      return String(ep.episode).includes(q);
    });
  }, [episodes, query]);

  /*
   * Keep the playing episode in view whenever the panel is open. `nearest`
   * rather than `center` so switching season does not jump the whole list.
   */
  useEffect(() => {
    if (!open) return;
    const node = listRef.current?.querySelector<HTMLElement>('[data-active="true"]');
    node?.scrollIntoView({ block: "nearest" });
  }, [episode, open, season]);

  const changeSeason = (nextSeason: number) => {
    if (nextSeason === season) return;
    // Episode resets to 1 for the new season; the URL stays the source of truth.
    setQuery("");
    router.push(`/watch/tv/${tmdbId}?season=${nextSeason}&episode=1`);
  };

  const selectEpisode = (nextEpisode: number) => {
    if (nextEpisode === episode) return;
    router.push(`/watch/tv/${tmdbId}?season=${season}&episode=${nextEpisode}`);
  };

  const current = episodes.find((e) => e.episode === episode) ?? null;
  const watched = episodes.filter((e) => e.episode <= episode).length;

  return (
    <>
      {/* Scrim: phone only, where the panel is a drawer that blocks the player. */}
      <div
        aria-hidden
        onClick={() => onToggle(false)}
        className={cn(
          "fixed inset-0 z-[900] bg-black/65 backdrop-blur-[2px] transition-opacity duration-300 md:hidden",
          open ? "opacity-100" : "pointer-events-none opacity-0",
        )}
      />

      {/* ── toggle tab ─────────────────────────────────────────────────────── */}
      {/*
        Always present for series, in both states, so the panel is one tap away
        without the visitor hunting for a control. Collapsed it sits flush to
        the right edge; expanded it rides the panel's own left edge.
      */}
      <button
        type="button"
        onClick={() => onToggle(!open)}
        aria-expanded={open}
        aria-controls="episode-panel"
        aria-label={open ? "Hide episodes" : "Show episodes"}
        className={cn(
          "fixed right-0 z-[960] flex flex-col items-center gap-1.5 rounded-l-xl border border-r-0 border-white/12",
          "bg-black/70 px-2 py-3 text-[11px] font-bold uppercase tracking-[0.14em]",
          "text-white/75 backdrop-blur-xl transition-all duration-300",
          "hover:bg-black/85 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-[-4px] focus-visible:outline-white",
          /*
            The icon and the label are stacked so the tab reads as one vertical
            strip at every width. `writing-mode: vertical-rl` rotates only the
            text, so "EPISODES" runs down the tab instead of sitting horizontally
            across a 40px-tall control — a horizontal label is what made the
            collapsed state look like a header button rather than a tab.
          */
          "flex-col",
          // Vertically centred on the collapsed stage; top-aligned once the panel
          // is open so it lines up with the panel header.
          "top-1/2 -translate-y-1/2",
          open && "top-[104px] translate-y-0",
        )}
      >
        <PanelIcon open={open} />
        <span className="[writing-mode:vertical-rl]">Episodes</span>
      </button>

      {/* ── panel ──────────────────────────────────────────────────────────── */}
      <aside
        id="episode-panel"
        aria-label="Episodes"
        aria-hidden={!open}
        inert={!open}
        className={cn(
          "fixed inset-y-0 right-0 z-[950] flex w-[92vw] max-w-[380px] flex-col",
          "border-l border-white/10 bg-black/88 backdrop-blur-2xl",
          "transition-transform duration-300 ease-[cubic-bezier(0.16,1,0.3,1)]",
          open ? "translate-x-0" : "translate-x-full",
        )}
      >
        {/* Header: title, progress, season selector, close */}
        <div className="shrink-0 border-b border-white/10 px-3 pt-3 pb-2.5">
          <div className="flex items-start justify-between gap-3">
            <div className="min-w-0">
              <h2 className="truncate text-[14px] font-bold tracking-[-0.01em] text-white">
                {title}
              </h2>
              <p className="mt-0.5 truncate text-[11px] font-medium text-white/55">
                {current ? `Season ${season} · Episode ${episode} of ${episodes.length}` : `Season ${season}`}
              </p>
            </div>

            <button
              type="button"
              onClick={() => onToggle(false)}
              aria-label="Close episode panel"
              className="grid size-7 shrink-0 place-items-center rounded-full text-white/60 transition-colors hover:bg-white/12 hover:text-white"
            >
              <X className="size-3.5" />
            </button>
          </div>

          {/* Season selector */}
          <div className="mt-2.5">
            {seasons && seasons.length > 1 ? (
              <label className="relative block">
                <span className="sr-only">Select season</span>
                <select
                  aria-label="Select season"
                  value={season}
                  onChange={(e) => changeSeason(Number(e.target.value))}
                  className={cn(
                    "h-8 w-full cursor-pointer appearance-none rounded-md border border-white/12",
                    "bg-white/8 pl-2.5 pr-7 text-[11.5px] font-bold text-white outline-none",
                    "transition-colors hover:bg-white/12 focus:border-white/35",
                  )}
                >
                  {seasons.map((s) => (
                    <option key={s.season} value={s.season} className="bg-surface-2 text-ink">
                      {s.name}
                      {s.episodeCount > 0 ? ` — ${s.episodeCount} eps` : ""}
                    </option>
                  ))}
                </select>
                <ChevronDown
                  aria-hidden
                  className="pointer-events-none absolute right-2 top-1/2 size-3.5 -translate-y-1/2 text-white/60"
                />
              </label>
            ) : (
              <p className="text-[11.5px] font-bold uppercase tracking-[0.12em] text-white/55">
                Season {season}
              </p>
            )}
          </div>
        </div>

        {/* Search */}
        <div className="relative shrink-0 px-3 py-2">
          <Search
            aria-hidden
            className="pointer-events-none absolute left-5 top-1/2 size-3.5 -translate-y-1/2 text-white/40"
          />
          <input
            type="search"
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            placeholder="Search episodes…"
            aria-label="Search episodes"
            className={cn(
              "h-8 w-full rounded-md border border-white/12 bg-white/6 pl-8 pr-7 text-[12px]",
              "text-white outline-none transition-colors placeholder:text-white/40",
              "hover:border-white/20 focus:border-white/35",
            )}
          />
          {query ? (
            <button
              type="button"
              onClick={() => setQuery("")}
              aria-label="Clear episode search"
              className="absolute right-5 top-1/2 -translate-y-1/2 text-white/50 transition-colors hover:text-white"
            >
              <X className="size-3.5" />
            </button>
          ) : null}
        </div>

        {watched > 0 && episodes.length > watched ? (
          <div className="shrink-0 px-3 pb-1.5">
            <div className="h-0.5 w-full overflow-hidden rounded-pill bg-white/12">
              <div
                className="h-full rounded-pill bg-brand-500"
                style={{ width: `${Math.round((watched / episodes.length) * 100)}%` }}
              />
            </div>
          </div>
        ) : null}

        {/* Episode list — scrolls independently of the page. */}
        <ol
          ref={listRef}
          className="no-scrollbar min-h-0 flex-1 space-y-1 overflow-y-auto overscroll-contain px-2.5 pb-5"
        >
          {filtered.length === 0 ? (
            <li className="px-2 py-10 text-center text-[13px] text-white/50">
              No episodes found.
            </li>
          ) : (
            filtered.map((ep) => {
              const active = ep.episode === episode;
              return (
                <li key={ep.episode}>
                  <Link
                    href={`/watch/tv/${tmdbId}?season=${season}&episode=${ep.episode}`}
                    onClick={() => selectEpisode(ep.episode)}
                    aria-current={active ? "true" : undefined}
                    data-active={active ? "true" : undefined}
                    className={cn(
                      /*
                        `items-center` is what keeps the row compact: the still is a
                        fixed 16:9 box, so centring the text beside it means a short
                        title or a missing overview can never stretch the card.
                      */
                      "group flex items-center gap-2 rounded-md border p-1.5 transition-colors",
                      active
                        ? "border-brand-500/70 bg-brand-500/14"
                        : "border-transparent hover:border-white/12 hover:bg-white/6",
                    )}
                  >
                    <EpisodeThumb ep={ep} active={active} />
                    <span className="min-w-0 flex-1">
                      {/* Title — one line, always first, never wraps. */}
                      <span
                        className={cn(
                          "line-clamp-1x block text-[12.5px] font-semibold leading-tight",
                          active ? "text-white" : "text-white/90",
                        )}
                      >
                        {ep.name}
                      </span>

                      {/* Number and duration share the second line. */}
                      {ep.episode > 0 || ep.runtime ? (
                        <span
                          className={cn(
                            "mt-0.5 block text-[10.5px] font-bold uppercase tracking-[0.07em]",
                            active ? "text-brand-300" : "text-white/45",
                          )}
                        >
                          E{ep.episode}
                          {ep.runtime ? ` · ${durationLabel(ep.runtime)}` : ""}
                        </span>
                      ) : null}

                      {ep.overview ? (
                        <span className="line-clamp-2x mt-1 block text-[11px] leading-[1.45] text-white/50">
                          {ep.overview}
                        </span>
                      ) : null}
                    </span>
                  </Link>
                </li>
              );
            })
          )}
        </ol>
      </aside>
    </>
  );
}

/* ── pieces ────────────────────────────────────────────────────────────────── */

/**
 * Chevron points at the panel when collapsed, and back toward the player when
 * the panel is open — the glyph rotates with the panel state rather than being
 * swapped, so the tab reads as one moving control.
 */
function PanelIcon({ open }: { open: boolean }) {
  const Icon = open ? ChevronRight : ChevronLeft;
  return (
    <Icon
      aria-hidden
      className={cn("size-4 shrink-0 transition-transform duration-300", open && "rotate-180")}
    />
  );
}

/**
 * Duration in the compact "E1 · 54 min" metadata form. `formatRuntime` is
 * deliberately not used here: its shorthand ("54m") is fine beside a title but
 * reads as noise in a small uppercase metadata line, so the unit is spelled out.
 */
function durationLabel(minutes: number | null | undefined): string {
  if (!minutes || minutes <= 0) return "";
  const h = Math.floor(minutes / 60);
  const m = minutes % 60;
  if (h === 0) return `${m} min`;
  if (m === 0) return `${h} h`;
  return `${h} h ${m} min`;
}

function EpisodeThumb({ ep, active }: { ep: EpisodeOption; active: boolean }) {
  return (
    <span
      className={cn(
        /*
          A fixed 16:9 box at 132px wide, so the still is 74px tall and the row
          height is set by the image rather than by the text beside it.
          `object-cover` crops to fill instead of distorting, and there is no
          hover scale: upscaling a 16:9 bitmap on hover is what made it read
          soft, and the panel does not need the zoom to feel interactive.
        */
        "relative aspect-video w-[132px] shrink-0 overflow-hidden rounded-sm",
        active ? "ring-2 ring-brand-500" : "ring-1 ring-white/10",
      )}
    >
      {ep.stillPath ? (
        <Image
          src={ep.stillPath}
          alt=""
          fill
          sizes="132px"
          className="object-cover"
        />
      ) : (
        // Not every episode has a still on TMDB; a placeholder beats a broken image.
        <span className="grid size-full place-items-center bg-white/8 text-white/40">
          <Play className="size-4" />
        </span>
      )}
      {active ? (
        <span className="absolute inset-0 grid place-items-center bg-black/45">
          <span className="grid size-6 place-items-center rounded-full bg-brand-500">
            <Play className="ml-0.5 size-2.5 fill-white text-white" />
          </span>
        </span>
      ) : null}
    </span>
  );
}
