import { Suspense } from "react";
import type { Metadata } from "next";

import { ContentRow, ContinueCard } from "@/components/home/content-row";
import { HeroSlider, type HeroSlide } from "@/components/home/hero-slider";
import { RowSkeleton, HeroSkeleton, CardSkeleton } from "@/components/ui/skeleton";
import { HOME_ROWS, type RowDefinition } from "@/config/home-rows";
import { getContinueWatching } from "@/db/queries/progress";
import { getCurrentUser } from "@/lib/auth/session";
import { buildSearchParams } from "@/lib/utils";
import {
  getMovieDetail,
  getPopularMovies,
  getPopularTv,
  getTopRatedMovies,
  getTopRatedTv,
  getTrending,
  getTvDetail,
  resolveRow,
  toMediaCard,
} from "@/services/tmdb";
import type { TmdbMovieDetail, TmdbTvDetail } from "@/services/tmdb/types";
import { siteConfig } from "@/config/site";
import type { MediaCardData } from "@/lib/types";

/**
 * Homepage.
 *
 * The whole page is dynamically rendered (the root layout reads the session
 * cookie), so every rail is a Suspense boundary: the hero and the first rows
 * paint immediately, later rows stream in. Each rail is cached in the datastore by the
 * TMDB service, so a warm cache makes these fetches sub-millisecond.
 */
export const metadata: Metadata = {
  title: `${siteConfig.name} — ${siteConfig.tagline}`,
  description: siteConfig.description,
  alternates: { canonical: "/" },
};

export default function HomePage() {
  return (
    <>
      <Suspense fallback={<HeroSkeleton />}>
        <HeroRegion />
      </Suspense>

      <div className="relative z-10 -mt-8 md:-mt-10">
        <Suspense fallback={null}>
          <ContinueWatchingRail />
        </Suspense>

        {HOME_ROWS.map((row) => (
          <Suspense key={row.key} fallback={<RowSkeleton />}>
            <Row region={row} />
          </Suspense>
        ))}
      </div>
    </>
  );
}

/* ── Hero ──────────────────────────────────────────────────────────────────── */

/**
 * Promote one trending entry into a hero slide.
 *
 * The trending list is a summary resource: it carries no `logo_path` and no
 * `runtime`. Both are needed for the hero's wordmark and its runtime badge, so
 * the full detail resource is fetched per slide and the summary is merged with
 * it — the summary stays the source of truth for the card fields (backdrop,
 * genres, vote counts) while the detail supplies only what it uniquely has.
 *
 * A failed detail fetch is not fatal: the slide falls back to the summary data
 * alone, and the hero renders its text title when there is no logo.
 */
async function toHeroSlide(item: MediaCardData): Promise<HeroSlide | null> {
  const detail: TmdbMovieDetail | TmdbTvDetail | null =
    item.mediaType === "tv"
      ? await getTvDetail(item.id).catch(() => null)
      : await getMovieDetail(item.id).catch(() => null);

  if (!detail) {
    return { item, runtime: null, tagline: null, logoPath: item.logoPath ?? null };
  }

  const normalised = toMediaCard(detail, item.mediaType);

  // The two detail resources disagree on where the runtime lives, exactly as
  // they do on the field names themselves: a movie carries one `runtime` in
  // minutes, a series carries an `episode_run_time` array. `normalised` already
  // stored the series value in `episodeRunTime`, so both branches read from the
  // normalised shape rather than from the raw TMDB payload.
  const runtime =
    item.mediaType === "tv"
      ? (normalised.episodeRunTime?.[0] ?? null)
      : ((detail as TmdbMovieDetail).runtime ?? null);

  return {
    // The detail resource is the authoritative record for this id, so its title
    // (`title` for movies, `name` for series, both resolved in `toMediaCard`)
    // and artwork win over the summary's.
    item: {
      ...item,
      ...normalised,
      // A missing detail-side image must never blank out artwork the summary
      // already provided.
      posterPath: normalised.posterPath ?? item.posterPath,
      backdropPath: normalised.backdropPath ?? item.backdropPath,
      logoPath: normalised.logoPath ?? item.logoPath ?? null,
    },
    runtime,
    tagline: detail.tagline ?? null,
    logoPath: normalised.logoPath ?? item.logoPath ?? null,
  };
}

async function HeroRegion() {
  /*
   * The hero draws from this week's trending list, which mixes movies and
   * series, so both the movie and TV branches of the display-title rule are
   * exercised on the homepage. Each entry is already normalised to a single
   * `MediaCardData` object, and the slider renders every field from that one
   * object — the artwork and the title can never belong to different titles.
   *
   * Popular movies are the fallback so the hero is never empty.
   */
  let candidates = (await getTrending("all", "week").catch(() => [])) as MediaCardData[];
  if (candidates.length < 3) candidates = await getPopularMovies().catch(() => []);

  const slides: HeroSlide[] = (
    await Promise.all(
      candidates
        .filter((item) => item.backdropPath)
        .slice(0, 6)
        .map((item) => toHeroSlide(item)),
    )
  ).filter((slide): slide is HeroSlide => slide !== null);

  if (!slides.length) {
    return (
      <div className="relative h-[91svh] min-h-[520px] w-full bg-linear-to-br from-surface-2 via-bg to-surface" />
    );
  }

  return (
    <>
      <HeroSlider slides={slides} />
      {/* Gradient bridge that blends the bottom of the hero into the first row */}
      <div
        aria-hidden
        className="pointer-events-none h-16 bg-linear-to-b from-transparent to-bg md:h-20"
      />
    </>
  );
}

/* ── Continue watching ─────────────────────────────────────────────────────── */

async function ContinueWatchingRail() {
  const user = await getCurrentUser();
  if (!user) return null;

  const items = await getContinueWatching(user.id, 14);
  if (!items.length) return null;

  return (
    <section className="mb-10 md:mb-12">
      <div className="mb-4 flex items-end justify-between gap-4 px-4 sm:px-6 lg:px-10">
        <div className="flex items-center gap-3">
          <span className="row-tick" aria-hidden />
          <h2 className="text-[17px] font-bold text-ink sm:text-[19px]">Continue Watching</h2>
        </div>
        <a
          href="/history"
          className="shrink-0 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
        </a>
      </div>
      <div className="no-scrollbar rail flex gap-3.5 overflow-x-auto px-4 sm:gap-4 sm:px-6 lg:px-10">
        {items.map((item) => (
          <ContinueCard
            key={`${item.mediaType}-${item.tmdbId}-${item.seasonNumber}-${item.episodeNumber}`}
            item={{
              id: item.tmdbId,
              mediaType: item.mediaType,
              title: item.title,
              posterPath: item.posterPath,
              backdropPath: item.backdropPath,
              seasonNumber: item.seasonNumber,
              episodeNumber: item.episodeNumber,
              durationSeconds: item.durationSeconds,
              updatedAt: item.updatedAt,
            }}
          />
        ))}
      </div>
    </section>
  );
}

/* ── Generic rail ──────────────────────────────────────────────────────────── */

function browseHref(row: RowDefinition): string {
  const params: Record<string, string | number> = {};
  if (row.genreId) params.genre = row.genreId;
  if (row.year) params.year = row.year;
  if (row.sort) params.sort = row.sort;
  if (row.mediaType === "tv") return `/tv${buildSearchParams(params)}`;
  if (row.mediaType === "movie") return `/movies${buildSearchParams(params)}`;
  return `/search${buildSearchParams(params)}`;
}

async function Row({ region }: { region: RowDefinition }) {
  const items = await resolveRow({
    source: region.source,
    mediaType: region.mediaType,
    sort: region.sort,
    window: region.window,
    genreId: region.genreId,
    minVote: region.minVote,
    year: region.year,
    limit: 20,
  });

  if (!items.length) {
    if (region.optional) return null;
    return <RowSkeleton count={6} />;
  }

  return (
    <ContentRow
      title={region.title}
      href={browseHref(region)}
      items={items}
      variant={region.variant === "ranked" ? "ranked" : region.variant === "backdrop" ? "backdrop" : "poster"}
    />
  );
}

export { getPopularMovies, getPopularTv, getTopRatedMovies, getTopRatedTv, CardSkeleton };
