"use client";

import Link from "next/link";
import { useCallback, useEffect, useRef, useState } from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";

import { BackdropCard, PosterCard, RankedCard } from "@/components/media/poster-card";
import { ContinueDismissButton } from "@/components/home/continue-dismiss-button";
import { cn, formatRelativeTime, formatRuntimeSeconds } from "@/lib/utils";
import type { MediaCardData } from "@/lib/types";

/**
 * Horizontal content rail.
 *
 * Native scroll container (not a transform carousel) so touch, trackpad and
 * keyboard scrolling all work for free. The `rail` utility applies a negative
 * block margin with matching padding so the card hover-scale and shadow are
 * never clipped.
 *
 * Arrows appear on hover and are hidden on touch devices, where swiping is the
 * expected gesture.
 */
export function ContentRow({
  title,
  subtitle,
  href,
  items,
  variant = "poster",
  priority = false,
  emptyMessage,
}: {
  title: string;
  subtitle?: string;
  href?: string;
  items: MediaCardData[];
  variant?: "poster" | "backdrop" | "ranked";
  priority?: boolean;
  emptyMessage?: string;
}) {
  const scrollerRef = useRef<HTMLDivElement>(null);
  const [edges, setEdges] = useState({ start: true, end: false });

  const measure = useCallback(() => {
    const el = scrollerRef.current;
    if (!el) return;
    const max = el.scrollWidth - el.clientWidth;
    setEdges({
      start: el.scrollLeft <= 4,
      end: max <= 4 || el.scrollLeft >= max - 4,
    });
  }, []);

  useEffect(() => {
    measure();
    const el = scrollerRef.current;
    if (!el) return;
    const observer = new ResizeObserver(measure);
    observer.observe(el);
    return () => observer.disconnect();
  }, [measure, items.length]);

  const scrollBy = (direction: 1 | -1) => {
    const el = scrollerRef.current;
    if (!el) return;
    el.scrollBy({ left: direction * Math.max(el.clientWidth * 0.82, 320), behavior: "smooth" });
  };

  if (!items.length) {
    if (!emptyMessage) return null;
    return (
      <section className="mb-10 md:mb-12">
        <RowHeading title={title} subtitle={subtitle} href={href} />
        <p className="px-4 text-[13px] text-ink-muted sm:px-6 lg:px-10">{emptyMessage}</p>
      </section>
    );
  }

  return (
    <section className="group/row relative mb-10 md:mb-12">
      <RowHeading title={title} subtitle={subtitle} href={href} />

      <div className="relative">
        <div
          ref={scrollerRef}
          onScroll={measure}
          className="no-scrollbar rail flex snap-x snap-mandatory gap-3.5 overflow-x-auto px-4 sm:gap-4 sm:px-6 lg:px-10"
        >
          {items.map((item, index) => {
            if (variant === "ranked") {
              return (
                <div key={`${item.mediaType}-${item.id}`} className="snap-start">
                  <RankedCard item={item} rank={index + 1} />
                </div>
              );
            }
            if (variant === "backdrop") {
              return (
                <div key={`${item.mediaType}-${item.id}`} className="snap-start">
                  <BackdropCard item={item} priority={priority && index < 4} />
                </div>
              );
            }
            return (
              <div key={`${item.mediaType}-${item.id}`} className="snap-start">
                <PosterCard item={item} priority={priority && index < 6} index={index} />
              </div>
            );
          })}
        </div>

        {/* Edge fades + arrows. Hidden on small screens where swiping wins. */}
        <div
          aria-hidden
          className={cn(
            "pointer-events-none absolute inset-y-0 left-0 w-16 bg-linear-to-r from-bg to-transparent transition-opacity duration-300 lg:w-24",
            edges.start ? "opacity-0" : "opacity-100",
          )}
        />
        <div
          aria-hidden
          className={cn(
            "pointer-events-none absolute inset-y-0 right-0 w-16 bg-linear-to-l from-bg to-transparent transition-opacity duration-300 lg:w-24",
            edges.end ? "opacity-0" : "opacity-100",
          )}
        />

        <ArrowButton side="left" visible={!edges.start} onClick={() => scrollBy(-1)} />
        <ArrowButton side="right" visible={!edges.end} onClick={() => scrollBy(1)} />
      </div>
    </section>
  );
}

function RowHeading({
  title,
  subtitle,
  href,
}: {
  title: string;
  subtitle?: string;
  href?: string;
}) {
  return (
    <div className="mb-4 flex items-end justify-between gap-4 px-4 sm:px-6 lg:px-10">
      <div className="flex min-w-0 items-center gap-3">
        <span className="row-tick" aria-hidden />
        <div className="min-w-0">
          <h2 className="truncate text-[17px] font-bold text-ink sm:text-[19px]">{title}</h2>
          {subtitle ? (
            <p className="mt-0.5 truncate text-[12px] text-ink-muted">{subtitle}</p>
          ) : null}
        </div>
      </div>
      {href ? (
        <Link
          href={href}
          className="group/viewall inline-flex shrink-0 items-center gap-1.5 rounded-pill px-2.5 py-1 text-[12px] font-semibold text-ink-muted transition-colors hover:bg-white/8 hover:text-ink"
        >
          View all
          <ChevronRight className="h-3.5 w-3.5 transition-transform duration-300 group-hover/viewall:translate-x-0.5" />
        </Link>
      ) : null}
    </div>
  );
}

function ArrowButton({
  side,
  visible,
  onClick,
}: {
  side: "left" | "right";
  visible: boolean;
  onClick: () => void;
}) {
  const Icon = side === "left" ? ChevronLeft : ChevronRight;
  return (
    <button
      type="button"
      onClick={onClick}
      aria-label={side === "left" ? "Scroll left" : "Scroll right"}
      className={cn(
        "absolute top-1/2 z-10 hidden h-10 w-10 -translate-y-1/2 place-items-center rounded-full",
        "border border-white/12 bg-black/60 text-white backdrop-blur-md",
        "transition-all duration-300 hover:bg-white/18 focus-visible:opacity-100 md:grid",
        side === "left" ? "-left-1 lg:left-2" : "-right-1 lg:right-2",
        visible ? "opacity-0 group-hover/row:opacity-100" : "pointer-events-none opacity-0",
      )}
    >
      <Icon className="h-5 w-5" strokeWidth={2.2} />
    </button>
  );
}

/**
 * Continue-watching card.
 *
 * A wide 16:9 tile that reopens the title where the visitor left the series off.
 * Data comes from the datastore and is only rendered for signed-in visitors.
 *
 * ── There is no progress bar, and that is a decision ──────────────────────────
 *
 * This card used to fill a bar to `item.percent` and caption it "37% watched".
 * Those numbers are gone, along with the fields that produced them, because the
 * page cannot know them: playback is a cross-origin embed, so the browser cannot
 * read its playhead. The only clock available was elapsed page time, which keeps
 * running while the video is paused, buffering, or open in a background tab —
 * so the bar was not a measurement of the visit, it was an estimate drawn to
 * look like one. On a card that says "you watched" it was a claim the visitor
 * never made, and filling a bar next to a film title is how that claim becomes
 * the app's fault.
 *
 * What replaces it is the two facts that are real and useful for deciding whether
 * to press play: **which episode** was last opened, and **how long** it runs.
 * The runtime is TMDB's, so it is a property of the title rather than of this
 * visit — the distinction the removed code kept losing.
 *
 * "Resume" is still the badge, and it is still accurate: the link reopens the
 * last episode of the last season opened. What it does not do is skip ahead,
 * because there is no honest offset to skip to. See `db/queries/progress.ts`.
 */
export function ContinueCard({
  item,
}: {
  item: {
    id: number;
    mediaType: "movie" | "tv";
    title: string;
    posterPath: string | null;
    backdropPath: string | null;
    seasonNumber: number | null;
    episodeNumber: number | null;
    /** The title's real runtime in seconds. A length, never a position. */
    durationSeconds: number;
    updatedAt: Date | string;
  };
}) {
  // Reopens the last episode of the last season opened. It carries no timestamp
  // or offset parameter, because there is no honest position to seek to.
  const href =
    item.mediaType === "movie"
      ? `/watch/movie/${item.id}`
      : `/watch/tv/${item.id}?season=${item.seasonNumber ?? 1}&episode=${item.episodeNumber ?? 1}`;

  const episodeLabel =
    item.mediaType === "tv" && item.seasonNumber != null
      ? `S${String(item.seasonNumber).padStart(2, "0")}E${String(item.episodeNumber ?? 1).padStart(2, "0")}`
      : null;
  const runtime = formatRuntimeSeconds(item.durationSeconds);
  const started = formatRelativeTime(item.updatedAt);

  // Only ever one of these, so the metadata line is never "·" on its own.
  const meta = [episodeLabel, runtime, started ? `started ${started}` : null]
    .filter(Boolean)
    .join(" · ");

  return (
    <article className="group relative w-[240px] shrink-0 sm:w-[272px]">
      <Link href={href} className="block">
        <div className="relative aspect-video overflow-hidden rounded-md bg-surface-2 shadow-card transition-shadow duration-300 group-hover:shadow-lift">
          {/* eslint-disable-next-line @next/next/no-img-element */}
          <img
            src={`https://image.tmdb.org/t/p/w500${item.backdropPath ?? item.posterPath ?? ""}`}
            alt=""
            loading="lazy"
            className="absolute inset-0 h-full w-full object-cover transition-transform duration-500 ease-[cubic-bezier(0.25,0.46,0.45,0.94)] group-hover:scale-105"
            onError={(e) => {
              (e.currentTarget as HTMLImageElement).style.display = "none";
            }}
          />
          <span
            aria-hidden
            className="pointer-events-none absolute inset-0 bg-linear-to-t from-black/90 via-black/30 to-transparent"
          />
          <span className="absolute left-2 top-2 rounded-xs bg-brand-500/90 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-white">
            Resume
          </span>
          <div className="absolute inset-x-0 bottom-0 p-3">
            <p className="line-clamp-1x text-[13px] font-bold text-white">{item.title}</p>
            <p className="mt-0.5 line-clamp-1x text-[11px] font-medium text-white/60">{meta}</p>
          </div>
        </div>
      </Link>

      {/*
        Deliberately a sibling of the <Link>, not a child.

        A dismiss control nested inside a link is the classic way to end up with a
        button that navigates half the time. It is also hidden until hover, which
        is fine for a pointer and useless for a keyboard — hence
        `focus-visible:opacity-100`, so it is never `hidden` and never
        unreachable.
      */}
      <ContinueDismissButton
        tmdbId={item.id}
        mediaType={item.mediaType}
        title={item.title}
        className="absolute right-1.5 top-1.5 z-10 opacity-0 focus-visible:opacity-100 group-hover:opacity-100"
      />
    </article>
  );
}
