import type { JellyItem } from './shared';
import { useEffect, useState } from 'react';
import { Link, useLocation, useSearchParams } from 'react-router';
import { Check, Heart, Loader2, Play, X } from 'lucide-react';
import { useClient } from 'hooks/useClient';
import { Button } from '@/components/ui/button';
import {
  backdropUrl,
  closeHref,
  episodeLabel,
  formatBytes,
  formatRuntime,
  ITEM_PARAM,
  itemHref,
  posterUrl,
  progressPercent,
  ticksToSeconds,
} from './shared';
import { useEpisodes, useItemFlags, useJellyfinItem, useSeasons } from './useJellyfinData';

// The detail sheet — an overlay ON the grid, opened by `?item=<id>`.
//
// It is not a route because the grid behind it should stay mounted and scrolled: closing is dropping one
// query parameter, and the back button does it for free. The same component covers a film, a series, a season
// and an episode, because Jellyfin's item shape does — what changes is which extra list is worth showing
// underneath, and that is decided by `Type`.

const META = 'text-xs text-muted-foreground';

const Chip = ({ children }: { children: React.ReactNode }) => (
  <span className="rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground">{children}</span>
);

/** The technical line — what is actually in the file, which is what predicts whether it will transcode. */
const StreamSummary = ({ item }: { item: JellyItem }) => {
  const source = item.MediaSources?.[0];
  if (!source) return null;
  const video = source.MediaStreams?.find((s) => s.Type === 'Video');
  const audio = source.MediaStreams?.find((s) => s.Type === 'Audio');
  const parts = [
    source.Container?.toUpperCase(),
    video?.Height ? `${video.Height}p` : null,
    video?.Codec?.toUpperCase(),
    audio?.Codec?.toUpperCase(),
    audio?.Channels ? `${audio.Channels}ch` : null,
    formatBytes(source.Size),
  ].filter(Boolean);
  return <p className={META}>{parts.join(' · ')}</p>;
};

const SeriesEpisodes = ({ series }: { series: JellyItem }) => {
  const { data: seasonData } = useSeasons(series.Id);
  const seasons = seasonData?.Items ?? [];
  const [seasonId, setSeasonId] = useState<string | null>(null);

  // Default to the first season only once the list arrives; picking one is then the owner's choice and is
  // not overwritten by a refetch.
  useEffect(() => {
    if (!seasonId && seasons.length) setSeasonId(seasons[0]?.Id ?? null);
  }, [seasonId, seasons]);

  const { data: episodeData, isLoading } = useEpisodes(series.Id, seasonId);
  const episodes = episodeData?.Items ?? [];

  if (!seasons.length) return null;

  return (
    <div className="flex flex-col gap-2">
      <div className="flex flex-wrap gap-1">
        {seasons.map((season) => (
          <button
            key={season.Id}
            type="button"
            onClick={() => setSeasonId(season.Id)}
            className={`rounded-md px-2 py-1 text-[11px] transition-colors ${
              seasonId === season.Id ? 'bg-primary/10 font-medium text-primary' : 'bg-muted text-muted-foreground'
            }`}
          >
            {season.Name ?? `Season ${season.IndexNumber ?? '?'}`}
          </button>
        ))}
      </div>

      {isLoading ? (
        <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
      ) : (
        <ul className="flex flex-col divide-y rounded-lg border">
          {episodes.map((episode) => (
            <EpisodeRow key={episode.Id} episode={episode} />
          ))}
        </ul>
      )}
    </div>
  );
};

const EpisodeRow = ({ episode }: { episode: JellyItem }) => {
  const { pathname } = useLocation();
  const [params] = useSearchParams();
  const progress = progressPercent(episode);

  return (
    <li className="relative">
      <Link to={itemHref(pathname, params, episode.Id, true)} className="flex gap-3 p-3 hover:bg-muted/50">
        <div className="min-w-0 flex-1">
          <p className="truncate text-xs font-medium">{episodeLabel(episode)}</p>
          {episode.Overview && <p className="line-clamp-2 text-[11px] text-muted-foreground">{episode.Overview}</p>}
        </div>
        <div className="flex shrink-0 items-center gap-2">
          <span className="text-[11px] text-muted-foreground">{formatRuntime(episode.RunTimeTicks)}</span>
          {episode.UserData?.Played && <Check className="h-3.5 w-3.5 text-primary" />}
        </div>
      </Link>
      {progress > 0 && progress < 100 && (
        <span className="absolute inset-x-0 bottom-0 h-0.5 bg-primary" style={{ width: `${progress}%` }} />
      )}
    </li>
  );
};

export const ItemDetail = ({ id }: { id: string }) => {
  const { token } = useClient();
  const { pathname } = useLocation();
  const [params] = useSearchParams();
  const { data: item, isLoading } = useJellyfinItem(id);
  const { setPlayed, setFavorite } = useItemFlags();

  const close = closeHref(pathname, params, ITEM_PARAM);

  if (isLoading || !item) {
    return (
      <div className="absolute inset-0 z-20 flex items-center justify-center bg-background/95">
        <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
      </div>
    );
  }

  const backdrop = backdropUrl(item, token);
  const poster = posterUrl(item, token, 300);
  const resumeSeconds = ticksToSeconds(item.UserData?.PlaybackPositionTicks);
  const played = item.UserData?.Played === true;
  const favorite = item.UserData?.IsFavorite === true;

  // A series is not playable itself; its Next Up episode is. Sending the series id to the player would ask
  // Jellyfin to negotiate playback for a folder, which fails in a way that reads as a broken video.
  const playableId = item.Type === 'Series' ? null : item.Id;

  const meta = [
    item.ProductionYear ? String(item.ProductionYear) : null,
    formatRuntime(item.RunTimeTicks),
    item.OfficialRating,
    item.CommunityRating ? `★ ${item.CommunityRating.toFixed(1)}` : null,
  ].filter(Boolean);

  return (
    <div className="absolute inset-0 z-20 overflow-y-auto bg-background">
      <div className="relative">
        {backdrop && (
          <div className="relative h-48 w-full overflow-hidden sm:h-64">
            <img src={backdrop} alt="" className="h-full w-full object-cover" />
            <div className="absolute inset-0 bg-gradient-to-t from-background via-background/40 to-transparent" />
          </div>
        )}
        <Link
          to={close}
          aria-label="Close"
          className="absolute right-3 top-3 flex h-8 w-8 items-center justify-center rounded-full bg-black/50 text-white hover:bg-black/70"
        >
          <X className="h-4 w-4" />
        </Link>
      </div>

      <div className={`flex flex-col gap-4 p-4 ${backdrop ? '-mt-16' : ''}`}>
        <div className="flex gap-4">
          {poster && (
            <img
              src={poster}
              alt=""
              className="h-40 w-[107px] shrink-0 rounded-lg object-cover shadow-lg ring-1 ring-black/10"
            />
          )}
          <div className="flex min-w-0 flex-col gap-1.5">
            <h2 className="text-lg font-semibold leading-tight">{item.Name}</h2>
            {item.Type === 'Episode' && item.SeriesName && (
              <p className={META}>
                {item.SeriesName} · {episodeLabel(item)}
              </p>
            )}
            {item.Taglines?.[0] && <p className="text-xs italic text-muted-foreground">{item.Taglines[0]}</p>}
            <p className={META}>{meta.join(' · ')}</p>
            <div className="flex flex-wrap gap-1">
              {item.Genres?.slice(0, 5).map((g) => (
                <Chip key={g}>{g}</Chip>
              ))}
            </div>
          </div>
        </div>

        <div className="flex flex-wrap items-center gap-2">
          {playableId && (
            <Button asChild size="sm">
              <Link to={itemHref(pathname, params, playableId, true)}>
                <Play className="mr-1.5 h-3.5 w-3.5" fill="currentColor" />
                {resumeSeconds > 0 ? 'Resume' : 'Play'}
              </Link>
            </Button>
          )}
          <Button
            variant="outline"
            size="sm"
            disabled={setPlayed.isPending}
            onClick={() => void setPlayed.mutateAsync({ id: item.Id, played: !played }).catch(() => undefined)}
          >
            <Check className={`mr-1.5 h-3.5 w-3.5 ${played ? 'text-primary' : ''}`} />
            {played ? 'Watched' : 'Mark watched'}
          </Button>
          <Button
            variant="outline"
            size="sm"
            disabled={setFavorite.isPending}
            onClick={() => void setFavorite.mutateAsync({ id: item.Id, favorite: !favorite }).catch(() => undefined)}
          >
            <Heart className={`mr-1.5 h-3.5 w-3.5 ${favorite ? 'fill-current text-red-500' : ''}`} />
            Favourite
          </Button>
        </div>

        {item.Overview && <p className="max-w-3xl text-xs leading-relaxed text-muted-foreground">{item.Overview}</p>}

        <StreamSummary item={item} />

        {item.Type === 'Series' && <SeriesEpisodes series={item} />}

        {!!item.People?.length && (
          <div className="flex flex-col gap-2">
            <h3 className="text-sm font-semibold">Cast</h3>
            <div className="flex flex-wrap gap-x-4 gap-y-1">
              {item.People.filter((person) => person.Type === 'Actor')
                .slice(0, 12)
                .map((person) => (
                  <p key={`${person.Id}-${person.Role}`} className="text-[11px]">
                    <span className="font-medium">{person.Name}</span>
                    {person.Role && <span className="text-muted-foreground"> as {person.Role}</span>}
                  </p>
                ))}
            </div>
          </div>
        )}
      </div>
    </div>
  );
};
