'use client';

import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Hls from 'hls.js';
import {
  AlertCircle,
  Captions,
  Check,
  ChevronLeft,
  ChevronRight,
  Clapperboard,
  Clock3,
  Expand,
  Gauge,
  Heart,
  Info,
  LoaderCircle,
  LogOut,
  Maximize,
  Minimize,
  Pause,
  PictureInPicture,
  Play,
  Plus,
  RotateCcw,
  RotateCw,
  Search,
  Settings2,
  Unplug,
  UserPlus,
  UserRound,
  Volume1,
  Volume2,
  VolumeX,
  X,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';

type CatalogDef = {
  id: string;
  type: string;
  name?: string;
  extra?: { name: string; isRequired?: boolean }[];
};
type Manifest = {
  id: string;
  name: string;
  description?: string;
  version?: string;
  logo?: string;
  background?: string;
  resources?: (string | { name: string })[];
  catalogs: CatalogDef[];
  types?: string[];
};
type Video = {
  id: string;
  title?: string;
  season?: number;
  episode?: number;
  released?: string;
  overview?: string;
  thumbnail?: string;
};
type Meta = {
  id: string;
  type: string;
  name: string;
  poster?: string;
  background?: string;
  logo?: string;
  description?: string;
  releaseInfo?: string;
  year?: string | number;
  genres?: string[];
  runtime?: string;
  videos?: Video[];
};
type Stream = {
  url?: string;
  externalUrl?: string;
  title?: string;
  name?: string;
  description?: string;
  ytId?: string;
  infoHash?: string;
  subtitles?: Subtitle[];
  behaviorHints?: {
    notWebReady?: boolean;
    bingeGroup?: string;
    headers?: Record<string, string>;
  };
};
type Subtitle = {
  id?: string;
  url: string;
  lang?: string;
  name?: string;
  label?: string;
  language?: string;
};
type CatalogState = { def: CatalogDef; items: Meta[]; error?: string };
type DetailState = {
  meta: Meta;
  streams: Stream[];
  subtitles: Subtitle[];
  loading: boolean;
  error?: string;
  videoId: string;
};
type WatchProgress = {
  key: string;
  metaId: string;
  videoId: string;
  metaName: string;
  episodeTitle?: string;
  season?: number;
  episode?: number;
  poster?: string;
  background?: string;
  thumbnail?: string;
  currentTime: number;
  duration: number;
  updatedAt: number;
  completed?: boolean;
};
type Profile = { id: string; name: string; color: string };
type Account = {
  email: string;
  passwordHash: string;
  profiles: Profile[];
};

const DEFAULT_MANIFEST_URL = 'https://fuzzylota.top/gibi/manifest.json';
const PROFILE_COLORS = ['#7c5cff', '#ec4899', '#14b8a6', '#f59e0b'];

function normalizeManifestUrl(value: string) {
  const converted = value.trim().replace(/^stremio:\/\//i, 'https://');
  const url = new URL(converted);
  if (!['http:', 'https:'].includes(url.protocol))
    throw new Error('Yalnızca HTTPS veya HTTP manifest adresleri desteklenir.');
  return url.toString();
}

function baseFromManifest(url: string) {
  return url.replace(/\/manifest\.json(?:\?.*)?$/i, '').replace(/\/$/, '');
}

function resourceUrl(
  base: string,
  resource: string,
  type: string,
  id: string,
  extra?: string,
) {
  return `${base}/${resource}/${encodeURIComponent(type)}/${encodeURIComponent(id)}${extra ? `/${extra}` : ''}.json`;
}

async function getJson<T>(url: string): Promise<T> {
  const controller = new AbortController();
  const timeout = window.setTimeout(() => controller.abort(), 18000);
  try {
    const response = await fetch(url, {
      signal: controller.signal,
      headers: { Accept: 'application/json' },
    });
    if (!response.ok)
      throw new Error(`Sunucu ${response.status} yanıtı verdi.`);
    return (await response.json()) as T;
  } finally {
    window.clearTimeout(timeout);
  }
}

function hasResource(manifest: Manifest | null, name: string) {
  return Boolean(
    manifest?.resources?.some((resource) =>
      typeof resource === 'string' ? resource === name : resource.name === name,
    ),
  );
}

function streamLabel(stream: Stream, index: number) {
  return (
    [stream.name, stream.title, stream.description]
      .filter(Boolean)
      .join(' · ') || `Kaynak ${index + 1}`
  );
}

function isPlayableStream(stream: Stream) {
  return (
    Boolean(stream.url || stream.externalUrl) &&
    !stream.behaviorHints?.notWebReady
  );
}

function isHlsStreamUrl(value: string) {
  try {
    const pathname = new URL(value, window.location.href).pathname.toLowerCase();
    return (
      pathname.endsWith('.m3u8') ||
      pathname.endsWith('/master.txt') ||
      /\/hls\/[^/]+\/[^/]+$/.test(pathname)
    );
  } catch {
    return /\.m3u8(?:$|\?)|\/master\.txt(?:$|\?)|\/hls\//i.test(value);
  }
}

function formatTime(seconds: number) {
  if (!Number.isFinite(seconds)) return '0:00';
  const h = Math.floor(seconds / 3600);
  const m = Math.floor((seconds % 3600) / 60);
  const s = Math.floor(seconds % 60);
  return h
    ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
    : `${m}:${String(s).padStart(2, '0')}`;
}

function progressKey(metaId: string, videoId: string) {
  return `${metaId}::${videoId}`;
}

function subtitleLabel(track: Subtitle, index: number) {
  const raw = track.name || track.label || track.lang || track.language;
  if (!raw) return `Altyazı ${index + 1}`;
  const languageNames: Record<string, string> = {
    tr: 'Türkçe',
    tur: 'Türkçe',
    en: 'İngilizce',
    eng: 'İngilizce',
    de: 'Almanca',
    ger: 'Almanca',
    es: 'İspanyolca',
    fr: 'Fransızca',
  };
  return languageNames[raw.toLocaleLowerCase('tr')] || raw;
}

function srtToVtt(text: string) {
  const normalized = text.replace(/^\uFEFF/, '').replace(/\r+/g, '');
  if (/^WEBVTT/m.test(normalized)) return normalized;
  return `WEBVTT\n\n${normalized.replace(
    /(\d{2}:\d{2}:\d{2}),(\d{3})\s*-->/g,
    '$1.$2 -->',
  )}`;
}

async function hashPassword(password: string) {
  const bytes = new TextEncoder().encode(password);
  const digest = await crypto.subtle.digest('SHA-256', bytes);
  return Array.from(new Uint8Array(digest))
    .map((byte) => byte.toString(16).padStart(2, '0'))
    .join('');
}

function readAccounts(): Account[] {
  try {
    return JSON.parse(localStorage.getItem('veyra:accounts') || '[]');
  } catch {
    return [];
  }
}

function saveAccounts(accounts: Account[]) {
  localStorage.setItem('veyra:accounts', JSON.stringify(accounts));
}

function profileDataKey(account: Account, profile: Profile, kind: string) {
  return `veyra:${account.email}:${profile.id}:${kind}`;
}

function AuthScreen({
  onAuthenticated,
}: {
  onAuthenticated: (account: Account) => void;
}) {
  const [mode, setMode] = useState<'login' | 'register'>('login');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [profileName, setProfileName] = useState('');
  const [error, setError] = useState('');
  const [busy, setBusy] = useState(false);

  const submit = async (event: React.FormEvent) => {
    event.preventDefault();
    setError('');
    const normalizedEmail = email.trim().toLocaleLowerCase('tr');
    if (!/^\S+@\S+\.\S+$/.test(normalizedEmail)) {
      setError('Geçerli bir e-posta adresi girin.');
      return;
    }
    if (password.length < 6) {
      setError('Parola en az 6 karakter olmalı.');
      return;
    }
    setBusy(true);
    const accounts = readAccounts();
    const passwordHash = await hashPassword(password);
    if (mode === 'register') {
      if (accounts.some((account) => account.email === normalizedEmail)) {
        setError('Bu e-posta ile daha önce hesap oluşturulmuş.');
        setBusy(false);
        return;
      }
      const account: Account = {
        email: normalizedEmail,
        passwordHash,
        profiles: [
          {
            id: crypto.randomUUID(),
            name: profileName.trim() || normalizedEmail.split('@')[0],
            color: PROFILE_COLORS[0],
          },
        ],
      };
      saveAccounts([...accounts, account]);
      onAuthenticated(account);
    } else {
      const account = accounts.find(
        (candidate) => candidate.email === normalizedEmail,
      );
      if (!account || account.passwordHash !== passwordHash) {
        setError('E-posta veya parola hatalı.');
        setBusy(false);
        return;
      }
      onAuthenticated(account);
    }
    setBusy(false);
  };

  return (
    <main className="auth-shell min-h-screen text-white">
      <div className="absolute left-6 top-6 flex items-center gap-2.5 text-lg font-semibold md:left-10 md:top-9">
        <span className="grid size-10 place-items-center rounded-xl bg-primary shadow-[0_0_32px_rgba(124,92,255,.38)]">
          <Clapperboard className="size-5" />
        </span>
        Veyra
      </div>
      <section className="auth-card">
        <p className="text-xs font-semibold uppercase tracking-[.2em] text-primary">
          Kişisel izleme alanın
        </p>
        <h1 className="mt-3 text-3xl font-semibold tracking-tight">
          {mode === 'login' ? 'Tekrar hoş geldin' : 'Veyra hesabını oluştur'}
        </h1>
        <p className="mt-2 text-sm leading-6 text-white/48">
          İzleme geçmişin, listen ve kaldığın yer yalnızca seçtiğin profile
          kaydedilir.
        </p>
        <form className="mt-7 space-y-4" onSubmit={submit}>
          <label className="block text-sm text-white/70">
            E-posta
            <Input
              type="email"
              value={email}
              onChange={(event) => setEmail(event.target.value)}
              className="mt-2 h-12 border-white/10 bg-white/5"
              placeholder="sen@ornek.com"
              autoComplete="email"
            />
          </label>
          {mode === 'register' && (
            <label className="block text-sm text-white/70">
              İlk profil adı
              <Input
                value={profileName}
                onChange={(event) => setProfileName(event.target.value)}
                className="mt-2 h-12 border-white/10 bg-white/5"
                placeholder="Örn. Ahmet"
                autoComplete="nickname"
              />
            </label>
          )}
          <label className="block text-sm text-white/70">
            Parola
            <Input
              type="password"
              value={password}
              onChange={(event) => setPassword(event.target.value)}
              className="mt-2 h-12 border-white/10 bg-white/5"
              placeholder="En az 6 karakter"
              autoComplete={
                mode === 'login' ? 'current-password' : 'new-password'
              }
            />
          </label>
          {error && (
            <p role="alert" className="text-sm text-red-300">
              {error}
            </p>
          )}
          <Button
            type="submit"
            className="h-12 w-full text-base"
            disabled={busy}
          >
            {busy && <LoaderCircle className="animate-spin" />}
            {mode === 'login' ? 'Giriş yap' : 'Hesap oluştur'}
          </Button>
        </form>
        <button
          className="mt-5 w-full text-sm text-white/52 transition hover:text-white"
          onClick={() => {
            setMode(mode === 'login' ? 'register' : 'login');
            setError('');
          }}
        >
          {mode === 'login'
            ? 'Hesabın yok mu? Hesap oluştur'
            : 'Zaten hesabın var mı? Giriş yap'}
        </button>
        <p className="mt-6 border-t border-white/8 pt-5 text-xs leading-5 text-white/30">
          Yerel önizlemede hesap bilgileri bu cihazda saklanır. cPanel sürümünde
          güvenli sunucu oturumuna taşınacaktır.
        </p>
      </section>
    </main>
  );
}

function ProfilePicker({
  account,
  onSelect,
  onUpdate,
  onLogout,
}: {
  account: Account;
  onSelect: (profile: Profile) => void;
  onUpdate: (account: Account) => void;
  onLogout: () => void;
}) {
  const [adding, setAdding] = useState(false);
  const [name, setName] = useState('');
  const addProfile = () => {
    if (!name.trim() || account.profiles.length >= 5) return;
    const next = {
      ...account,
      profiles: [
        ...account.profiles,
        {
          id: crypto.randomUUID(),
          name: name.trim(),
          color:
            PROFILE_COLORS[account.profiles.length % PROFILE_COLORS.length],
        },
      ],
    };
    const accounts = readAccounts().map((item) =>
      item.email === account.email ? next : item,
    );
    saveAccounts(accounts);
    onUpdate(next);
    setName('');
    setAdding(false);
  };

  return (
    <main className="profile-shell min-h-screen text-white">
      <button
        className="absolute right-6 top-6 flex items-center gap-2 text-sm text-white/50 hover:text-white"
        onClick={onLogout}
      >
        <LogOut className="size-4" /> Çıkış yap
      </button>
      <section className="w-full max-w-4xl px-6 text-center">
        <p className="text-xs font-semibold uppercase tracking-[.22em] text-primary">
          Veyra profilleri
        </p>
        <h1 className="mt-3 text-4xl font-semibold tracking-tight md:text-5xl">
          Kim izliyor?
        </h1>
        <div className="mt-10 flex flex-wrap justify-center gap-6">
          {account.profiles.map((profile) => (
            <button
              key={profile.id}
              className="profile-tile group"
              onClick={() => onSelect(profile)}
            >
              <span
                className="profile-avatar"
                style={{
                  background: `linear-gradient(145deg, ${profile.color}, #171821)`,
                }}
              >
                {profile.name.slice(0, 1).toLocaleUpperCase('tr')}
              </span>
              <span className="mt-3 block text-white/55 transition group-hover:text-white">
                {profile.name}
              </span>
            </button>
          ))}
          {account.profiles.length < 5 && (
            <button
              className="profile-tile group"
              onClick={() => setAdding(true)}
            >
              <span className="profile-avatar border border-dashed border-white/20 bg-white/4 text-white/45 group-hover:border-primary/60 group-hover:text-primary">
                <UserPlus className="size-8" />
              </span>
              <span className="mt-3 block text-white/55 transition group-hover:text-white">
                Profil ekle
              </span>
            </button>
          )}
        </div>
        {adding && (
          <div className="mx-auto mt-8 flex max-w-sm gap-2">
            <Input
              value={name}
              onChange={(event) => setName(event.target.value)}
              onKeyDown={(event) => event.key === 'Enter' && addProfile()}
              autoFocus
              className="h-11 border-white/10 bg-white/5"
              placeholder="Profil adı"
            />
            <Button onClick={addProfile}>Ekle</Button>
            <Button variant="ghost" onClick={() => setAdding(false)}>
              İptal
            </Button>
          </div>
        )}
      </section>
    </main>
  );
}

function Player({
  detail,
  initialIndex,
  progress,
  onProgress,
  onClose,
}: {
  detail: DetailState;
  initialIndex: number;
  progress?: WatchProgress;
  onProgress: (progress: WatchProgress) => void;
  onClose: () => void;
}) {
  const playable = useMemo(
    () => detail.streams.filter(isPlayableStream),
    [detail.streams],
  );
  const [sourceIndex, setSourceIndex] = useState(
    Math.min(initialIndex, Math.max(0, playable.length - 1)),
  );
  const [playing, setPlaying] = useState(false);
  const [time, setTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const [volume, setVolume] = useState(1);
  const [muted, setMuted] = useState(false);
  const [speed, setSpeed] = useState(1);
  const [subtitle, setSubtitle] = useState('off');
  const [showSettings, setShowSettings] = useState(false);
  const [showSubtitles, setShowSubtitles] = useState(false);
  const [fullscreen, setFullscreen] = useState(false);
  const [playerError, setPlayerError] = useState<string | null>(null);
  const [resolvedTracks, setResolvedTracks] = useState<
    (Subtitle & { src: string })[]
  >([]);
  const [addedTracks, setAddedTracks] = useState<
    (Subtitle & { src: string })[]
  >([]);
  const videoRef = useRef<HTMLVideoElement>(null);
  const shellRef = useRef<HTMLDivElement>(null);
  const lastSavedSecond = useRef(-1);
  const addedTrackUrls = useRef<string[]>([]);
  const sourceResumeAt = useRef<number | null>(null);
  const source = playable[sourceIndex];
  const allSubtitleTracks = useMemo(
    () => [...resolvedTracks, ...addedTracks],
    [addedTracks, resolvedTracks],
  );
  const episode = detail.meta.videos?.find(
    (video) => video.id === detail.videoId,
  );

  const persistProgress = useCallback(
    (currentTime: number, totalDuration: number, completed = false) => {
      if (!Number.isFinite(totalDuration) || totalDuration <= 0) return;
      onProgress({
        key: progressKey(detail.meta.id, detail.videoId),
        metaId: detail.meta.id,
        videoId: detail.videoId,
        metaName: detail.meta.name,
        episodeTitle: episode?.title,
        season: episode?.season,
        episode: episode?.episode,
        poster: detail.meta.poster,
        background: detail.meta.background,
        thumbnail: episode?.thumbnail,
        currentTime,
        duration: totalDuration,
        updatedAt: Date.now(),
        completed: completed || currentTime / totalDuration > 0.95,
      });
    },
    [detail.meta, detail.videoId, episode, onProgress],
  );

  useEffect(() => {
    const video = videoRef.current;
    if (!video || !source?.url) return;
    let hls: Hls | null = null;
    let disposed = false;
    setPlayerError(null);

    const tryPlay = () => {
      video.play().catch((error: unknown) => {
        if (disposed) return;
        if (error instanceof DOMException && error.name === 'NotAllowedError')
          return;
        setPlaying(false);
      });
    };

    if (isHlsStreamUrl(source.url) && Hls.isSupported()) {
      hls = new Hls({ enableWorker: true, capLevelToPlayerSize: true });
      hls.attachMedia(video);
      hls.loadSource(source.url);
      hls.on(Hls.Events.MANIFEST_PARSED, tryPlay);
      hls.on(Hls.Events.ERROR, (_event, data) => {
        if (!data.fatal || disposed || !hls) return;
        if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
          hls.startLoad();
          return;
        }
        if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
          hls.recoverMediaError();
          return;
        }
        setPlayerError('Bu yayın tarayıcıda başlatılamadı.');
        hls.destroy();
      });
    } else if (
      isHlsStreamUrl(source.url) &&
      video.canPlayType('application/vnd.apple.mpegurl')
    ) {
      video.src = source.url;
      video.load();
      tryPlay();
    } else if (isHlsStreamUrl(source.url)) {
      setPlayerError('Bu tarayıcı HLS yayınlarını desteklemiyor.');
    } else {
      video.src = source.url;
      video.load();
      tryPlay();
    }

    return () => {
      disposed = true;
      hls?.destroy();
      video.pause();
      video.removeAttribute('src');
    };
  }, [source?.url]);

  useEffect(() => {
    let active = true;
    const objectUrls: string[] = [];
    Promise.all(
      detail.subtitles.map(async (track) => {
        if (/\.vtt(?:$|\?)/i.test(track.url))
          return { ...track, src: track.url };
        try {
          const response = await fetch(track.url);
          if (!response.ok) throw new Error('Altyazı indirilemedi');
          const blobUrl = URL.createObjectURL(
            new Blob([srtToVtt(await response.text())], { type: 'text/vtt' }),
          );
          objectUrls.push(blobUrl);
          return { ...track, src: blobUrl };
        } catch {
          return { ...track, src: track.url };
        }
      }),
    ).then((tracks) => active && setResolvedTracks(tracks));
    return () => {
      active = false;
      objectUrls.forEach((url) => URL.revokeObjectURL(url));
    };
  }, [detail.subtitles]);

  useEffect(() => {
    const video = videoRef.current;
    if (!video) return;
    Array.from(video.textTracks).forEach((track, index) => {
      track.mode = subtitle === String(index) ? 'showing' : 'disabled';
    });
  }, [subtitle, allSubtitleTracks]);

  useEffect(() => {
    const video = videoRef.current;
    if (!video) return;
    video.volume = volume;
    video.muted = muted;
    video.playbackRate = speed;
  }, [muted, speed, volume]);

  const togglePlay = useCallback(() => {
    const video = videoRef.current;
    if (!video) return;
    if (video.paused) {
      video.play().catch(() => setPlaying(false));
    } else {
      video.pause();
    }
  }, []);
  const switchSource = useCallback((nextIndex: number) => {
    const video = videoRef.current;
    sourceResumeAt.current = video?.currentTime || 0;
    setSourceIndex(nextIndex);
    setShowSettings(false);
  }, []);
  const seek = useCallback((amount: number) => {
    if (videoRef.current)
      videoRef.current.currentTime = Math.max(
        0,
        Math.min(
          videoRef.current.duration || 0,
          videoRef.current.currentTime + amount,
        ),
      );
  }, []);
  const toggleFullscreen = useCallback(async () => {
    if (!document.fullscreenElement)
      await shellRef.current?.requestFullscreen();
    else await document.exitFullscreen();
  }, []);
  const closePlayer = useCallback(() => {
    const video = videoRef.current;
    if (video) persistProgress(video.currentTime, video.duration);
    onClose();
  }, [onClose, persistProgress]);
  const addSubtitleFile = useCallback(
    async (file?: File) => {
      if (!file) return;
      const src = URL.createObjectURL(
        new Blob([srtToVtt(await file.text())], { type: 'text/vtt' }),
      );
      addedTrackUrls.current.push(src);
      setAddedTracks((tracks) => [
        ...tracks,
        { src, url: src, name: file.name.replace(/\.(srt|vtt)$/i, '') },
      ]);
      setSubtitle(String(allSubtitleTracks.length));
      setShowSubtitles(false);
    },
    [allSubtitleTracks.length],
  );
  useEffect(
    () => () =>
      addedTrackUrls.current.forEach((url) => URL.revokeObjectURL(url)),
    [],
  );
  useEffect(() => {
    const key = (event: KeyboardEvent) => {
      if ((event.target as HTMLElement)?.tagName === 'INPUT') return;
      if (event.code === 'Space') {
        event.preventDefault();
        togglePlay();
      }
      if (event.key === 'ArrowRight') seek(10);
      if (event.key === 'ArrowLeft') seek(-10);
      if (event.key.toLowerCase() === 'f') toggleFullscreen();
      if (event.key.toLowerCase() === 'm') setMuted((value) => !value);
      if (event.key === 'Escape' && !document.fullscreenElement) closePlayer();
    };
    const fs = () => setFullscreen(Boolean(document.fullscreenElement));
    document.addEventListener('keydown', key);
    document.addEventListener('fullscreenchange', fs);
    return () => {
      document.removeEventListener('keydown', key);
      document.removeEventListener('fullscreenchange', fs);
    };
  }, [closePlayer, seek, toggleFullscreen, togglePlay]);

  if (source?.externalUrl && !source.url) {
    return (
      <div ref={shellRef} className="fixed inset-0 z-[100] bg-black text-white">
        <iframe
          src={source.externalUrl}
          title={`${detail.meta.name} harici web oynatıcısı`}
          className="h-full w-full border-0"
          allow="autoplay; fullscreen; picture-in-picture; encrypted-media"
          allowFullScreen
          sandbox="allow-scripts allow-same-origin allow-forms allow-presentation"
        />
        <div className="pointer-events-none absolute inset-x-0 top-0 flex items-center gap-3 bg-gradient-to-b from-black/90 to-transparent p-5 pb-16">
          <button
            onClick={onClose}
            className="pointer-events-auto grid size-10 place-items-center rounded-full bg-black/45 hover:bg-white/15"
            aria-label="Oynatıcıyı kapat"
          >
            <ChevronLeft />
          </button>
          <div className="min-w-0">
            <p className="truncate font-semibold">{detail.meta.name}</p>
            <p className="truncate text-xs text-white/60">
              {streamLabel(source, sourceIndex)} · Gömülü web oynatıcı
            </p>
          </div>
          {playable.length > 1 && (
            <select
              value={sourceIndex}
              onChange={(event) => switchSource(Number(event.target.value))}
              className="player-select pointer-events-auto ml-auto !w-auto max-w-64"
              aria-label="Yayın kaynağı"
            >
              {playable.map((item, index) => (
                <option
                  key={`${item.url || item.externalUrl}-${index}`}
                  value={index}
                >
                  {streamLabel(item, index)}
                </option>
              ))}
            </select>
          )}
        </div>
        <p className="pointer-events-none absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full bg-black/70 px-4 py-2 text-center text-xs text-white/65 backdrop-blur">
          Kaynak native HLS sunmadığı için sağlayıcının web oynatıcısı
          kullanılıyor.
        </p>
      </div>
    );
  }

  return (
    <div
      ref={shellRef}
      className="fixed inset-0 z-[100] flex bg-black text-white"
    >
      <video
        ref={videoRef}
        className="h-full w-full object-contain"
        playsInline
        onClick={togglePlay}
        onPlay={() => setPlaying(true)}
        onPause={(e) => {
          setPlaying(false);
          persistProgress(
            e.currentTarget.currentTime,
            e.currentTarget.duration,
          );
        }}
        onEnded={(e) =>
          persistProgress(
            e.currentTarget.duration,
            e.currentTarget.duration,
            true,
          )
        }
        onLoadedMetadata={(e) => {
          const resumeAt = sourceResumeAt.current ?? progress?.currentTime ?? 0;
          if (resumeAt > 5 && resumeAt < e.currentTarget.duration - 8)
            e.currentTarget.currentTime = resumeAt;
          sourceResumeAt.current = null;
        }}
        onTimeUpdate={(e) => {
          const current = e.currentTarget.currentTime;
          setTime(current);
          const wholeSecond = Math.floor(current);
          if (
            wholeSecond > 0 &&
            wholeSecond % 5 === 0 &&
            wholeSecond !== lastSavedSecond.current
          ) {
            lastSavedSecond.current = wholeSecond;
            persistProgress(current, e.currentTarget.duration);
          }
        }}
        onDurationChange={(e) => setDuration(e.currentTarget.duration)}
        onError={() => {
          const error = videoRef.current?.error;
          if (error?.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED)
            setPlayerError('Yayın biçimi bu tarayıcı tarafından desteklenmiyor.');
          else if (error)
            setPlayerError('Yayın yüklenirken bir medya hatası oluştu.');
        }}
      >
        {allSubtitleTracks.map((track, index) => (
          <track
            key={`${track.src}-${index}`}
            src={track.src}
            label={subtitleLabel(track, index)}
            srcLang={track.lang || track.language || 'und'}
            kind="subtitles"
            onLoad={() => {
              const tracks = videoRef.current?.textTracks;
              if (tracks?.[index])
                tracks[index].mode =
                  subtitle === String(index) ? 'showing' : 'disabled';
            }}
          />
        ))}
      </video>
      {playerError && (
        <div className="pointer-events-none absolute inset-0 z-10 grid place-items-center bg-black/55 p-6">
          <div className="max-w-md rounded-2xl border border-red-400/20 bg-[#171217]/95 px-6 py-5 text-center shadow-2xl backdrop-blur-xl">
            <AlertCircle className="mx-auto mb-3 size-8 text-red-400" />
            <p className="font-semibold">Yayın açılamadı</p>
            <p className="mt-1 text-sm text-white/60">{playerError}</p>
          </div>
        </div>
      )}
      {!playing && (
        <button
          className="absolute left-1/2 top-1/2 grid size-20 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full bg-white/18 backdrop-blur-xl transition hover:scale-105 hover:bg-white/25"
          onClick={togglePlay}
          aria-label="Oynat"
        >
          <Play className="size-8 fill-current" />
        </button>
      )}
      <div className="pointer-events-none absolute inset-x-0 top-0 flex items-center gap-3 bg-gradient-to-b from-black/80 to-transparent p-5 pb-16">
        <button
          onClick={closePlayer}
          className="pointer-events-auto grid size-10 place-items-center rounded-full bg-black/25 hover:bg-white/15"
          aria-label="Oynatıcıyı kapat"
        >
          <ChevronLeft />
        </button>
        <div>
          <p className="font-semibold">{detail.meta.name}</p>
          <p className="text-xs text-white/60">
            {streamLabel(source, sourceIndex)}
          </p>
        </div>
      </div>
      <div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/95 via-black/55 to-transparent px-4 pb-5 pt-20 md:px-7">
        <input
          aria-label="Oynatma konumu"
          className="player-range w-full"
          type="range"
          min="0"
          max={duration || 0}
          value={time}
          onChange={(e) => {
            if (videoRef.current)
              videoRef.current.currentTime = Number(e.target.value);
          }}
        />
        <div className="mt-3 flex items-center gap-1 md:gap-2">
          <button
            className="player-icon"
            onClick={togglePlay}
            aria-label={playing ? 'Duraklat' : 'Oynat'}
          >
            {playing ? (
              <Pause className="fill-current" />
            ) : (
              <Play className="fill-current" />
            )}
          </button>
          <button
            className="player-icon hidden sm:grid"
            onClick={() => seek(-10)}
            aria-label="10 saniye geri"
          >
            <RotateCcw />
          </button>
          <button
            className="player-icon hidden sm:grid"
            onClick={() => seek(10)}
            aria-label="10 saniye ileri"
          >
            <RotateCw />
          </button>
          <button
            className="player-icon"
            onClick={() => setMuted((value) => !value)}
            aria-label="Sesi aç/kapat"
          >
            {muted || volume === 0 ? (
              <VolumeX />
            ) : volume < 0.5 ? (
              <Volume1 />
            ) : (
              <Volume2 />
            )}
          </button>
          <input
            aria-label="Ses seviyesi"
            className="player-range hidden !w-24 sm:block"
            type="range"
            min="0"
            max="1"
            step="0.05"
            value={muted ? 0 : volume}
            onChange={(e) => {
              setVolume(Number(e.target.value));
              setMuted(false);
            }}
          />
          <span className="ml-1 text-xs tabular-nums text-white/65">
            {formatTime(time)} / {formatTime(duration)}
          </span>
          <div className="ml-auto flex items-center gap-1">
            <div className="relative">
              <button
                className="player-icon"
                onClick={() => setShowSettings((value) => !value)}
                aria-label="Oynatıcı ayarları"
              >
                <Settings2 />
              </button>
              {showSettings && (
                <div className="absolute bottom-12 right-0 w-64 rounded-2xl border border-white/10 bg-[#151620]/96 p-3 shadow-2xl backdrop-blur-xl">
                  <label className="mb-1 block px-2 text-[11px] uppercase tracking-wider text-white/45">
                    Kaynak / kalite
                  </label>
                  <select
                    value={sourceIndex}
                    onChange={(e) => switchSource(Number(e.target.value))}
                    className="player-select"
                  >
                    {playable.map((item, index) => (
                      <option
                        key={`${item.url || item.externalUrl}-${index}`}
                        value={index}
                      >
                        {streamLabel(item, index)}
                      </option>
                    ))}
                  </select>
                  <label className="mb-1 mt-3 block px-2 text-[11px] uppercase tracking-wider text-white/45">
                    Oynatma hızı
                  </label>
                  <select
                    value={speed}
                    onChange={(e) => setSpeed(Number(e.target.value))}
                    className="player-select"
                  >
                    {[0.5, 0.75, 1, 1.25, 1.5, 2].map((item) => (
                      <option key={item} value={item}>
                        {item}x
                      </option>
                    ))}
                  </select>
                </div>
              )}
            </div>
            <div className="relative">
              <button
                className={`player-icon relative ${subtitle !== 'off' ? 'bg-primary text-white' : ''}`}
                onClick={() => {
                  setShowSubtitles((value) => !value);
                  setShowSettings(false);
                }}
                aria-label={`Altyazı: ${subtitle === 'off' || !allSubtitleTracks[Number(subtitle)] ? 'Kapalı' : subtitleLabel(allSubtitleTracks[Number(subtitle)], Number(subtitle))}`}
              >
                <Captions />
                {subtitle !== 'off' && (
                  <span className="absolute -right-0.5 -top-0.5 size-2 rounded-full bg-emerald-400 ring-2 ring-black" />
                )}
              </button>
              {showSubtitles && (
                <div className="absolute bottom-12 right-0 w-64 overflow-hidden rounded-2xl border border-white/10 bg-[#151620]/96 p-2 shadow-2xl backdrop-blur-xl">
                  <div className="flex items-center justify-between px-2 pb-2 pt-1">
                    <span className="text-xs font-semibold">Altyazılar</span>
                    <span className="text-[10px] text-white/40">
                      {allSubtitleTracks.length} seçenek
                    </span>
                  </div>
                  <button
                    className={`subtitle-option ${subtitle === 'off' ? 'subtitle-option-active' : ''}`}
                    onClick={() => {
                      setSubtitle('off');
                      setShowSubtitles(false);
                    }}
                  >
                    <span>Kapalı</span>
                    {subtitle === 'off' && <Check className="size-4" />}
                  </button>
                  {allSubtitleTracks.map((item, index) => (
                    <button
                      key={`${item.src}-${index}`}
                      className={`subtitle-option ${subtitle === String(index) ? 'subtitle-option-active' : ''}`}
                      onClick={() => {
                        setSubtitle(String(index));
                        setShowSubtitles(false);
                      }}
                    >
                      <span>{subtitleLabel(item, index)}</span>
                      {subtitle === String(index) && (
                        <Check className="size-4" />
                      )}
                    </button>
                  ))}
                  {!allSubtitleTracks.length && (
                    <p className="px-2 py-3 text-xs leading-5 text-white/45">
                      Bu manifest altyazı kaynağı sunmuyor.
                    </p>
                  )}
                  <label className="mt-1 flex cursor-pointer items-center justify-center gap-2 rounded-xl border border-dashed border-white/15 px-3 py-2.5 text-xs text-white/65 transition hover:border-primary/45 hover:bg-primary/8 hover:text-white">
                    <Plus className="size-3.5" />
                    SRT veya VTT dosyası ekle
                    <input
                      type="file"
                      accept=".srt,.vtt,text/vtt,application/x-subrip"
                      className="sr-only"
                      onChange={(event) =>
                        addSubtitleFile(event.currentTarget.files?.[0])
                      }
                    />
                  </label>
                </div>
              )}
            </div>
            <button
              className="player-icon hidden sm:grid"
              onClick={() => videoRef.current?.requestPictureInPicture?.()}
              aria-label="Resim içinde resim"
            >
              <PictureInPicture />
            </button>
            <button
              className="player-icon"
              onClick={toggleFullscreen}
              aria-label="Tam ekran"
            >
              {fullscreen ? <Minimize /> : <Maximize />}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

export default function Home() {
  const [hydrated, setHydrated] = useState(false);
  const [account, setAccount] = useState<Account | null>(null);
  const [activeProfile, setActiveProfile] = useState<Profile | null>(null);
  const [showProfileMenu, setShowProfileMenu] = useState(false);
  const [manifestInput, setManifestInput] = useState('');
  const [manifestUrl, setManifestUrl] = useState('');
  const [manifest, setManifest] = useState<Manifest | null>(null);
  const [catalogs, setCatalogs] = useState<CatalogState[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [query, setQuery] = useState('');
  const [selected, setSelected] = useState<Meta | null>(null);
  const [selectedVideo, setSelectedVideo] = useState('');
  const [selectedSeason, setSelectedSeason] = useState(1);
  const [detail, setDetail] = useState<DetailState | null>(null);
  const [player, setPlayer] = useState<{
    detail: DetailState;
    index: number;
  } | null>(null);
  const [favorites, setFavorites] = useState<string[]>([]);
  const [watchProgress, setWatchProgress] = useState<
    Record<string, WatchProgress>
  >({});
  const detailRequest = useRef(0);
  const didAutoConnect = useRef(false);

  useEffect(() => {
    setManifestInput(
      localStorage.getItem('veyra:manifest') || DEFAULT_MANIFEST_URL,
    );
    try {
      const session = JSON.parse(localStorage.getItem('veyra:session') || '{}');
      const savedAccount = readAccounts().find(
        (item) => item.email === session.email,
      );
      if (savedAccount) {
        setAccount(savedAccount);
        const savedProfile = savedAccount.profiles.find(
          (profile) => profile.id === session.profileId,
        );
        if (savedProfile) setActiveProfile(savedProfile);
      }
    } catch {
      localStorage.removeItem('veyra:session');
    }
    setHydrated(true);
  }, []);

  useEffect(() => {
    if (!account || !activeProfile) {
      setFavorites([]);
      setWatchProgress({});
      return;
    }
    try {
      setFavorites(
        JSON.parse(
          localStorage.getItem(
            profileDataKey(account, activeProfile, 'favorites'),
          ) || '[]',
        ),
      );
      setWatchProgress(
        JSON.parse(
          localStorage.getItem(
            profileDataKey(account, activeProfile, 'progress'),
          ) || '{}',
        ),
      );
    } catch {
      setFavorites([]);
      setWatchProgress({});
    }
  }, [account, activeProfile]);

  const connectManifest = async (requestedUrl = manifestInput) => {
    setLoading(true);
    setError('');
    try {
      const url = normalizeManifestUrl(requestedUrl);
      const data = await getJson<Manifest>(url);
      if (!data.id || !data.name || !Array.isArray(data.catalogs))
        throw new Error('Bu adres geçerli bir Stremio manifesti değil.');
      const base = baseFromManifest(url);
      const results = await Promise.allSettled(
        data.catalogs.map(async (def) => {
          if (def.extra?.some((item) => item.isRequired))
            return {
              def,
              items: [],
              error: 'Bu katalog yapılandırma parametresi istiyor.',
            } as CatalogState;
          const response = await getJson<{ metas?: Meta[] }>(
            resourceUrl(base, 'catalog', def.type, def.id),
          );
          return { def, items: response.metas || [] } as CatalogState;
        }),
      );
      const next = results.map((result, index) =>
        result.status === 'fulfilled'
          ? result.value
          : {
              def: data.catalogs[index],
              items: [],
              error:
                result.reason instanceof Error
                  ? result.reason.message
                  : 'Katalog yüklenemedi.',
            },
      );
      setManifest(data);
      setManifestUrl(url);
      setCatalogs(next);
      localStorage.setItem('veyra:manifest', url);
    } catch (reason) {
      setError(
        reason instanceof Error
          ? `${reason.message} Manifest sunucusunun tarayıcı erişimine (CORS) izin verdiğini de kontrol edin.`
          : 'Manifest yüklenemedi.',
      );
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    if (!activeProfile || !manifestInput || manifest || didAutoConnect.current)
      return;
    didAutoConnect.current = true;
    connectManifest(manifestInput);
    // The saved manifest is restored once per page load.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [activeProfile, manifestInput]);

  const disconnect = () => {
    setManifest(null);
    setManifestUrl('');
    setCatalogs([]);
    setError('');
    localStorage.removeItem('veyra:manifest');
  };

  const openDetail = async (item: Meta, preferredVideoId?: string) => {
    setSelected(item);
    setDetail({
      meta: item,
      streams: [],
      subtitles: [],
      loading: true,
      videoId: item.id,
    });
    const request = ++detailRequest.current;
    let full = item;
    try {
      if (manifest && manifestUrl && hasResource(manifest, 'meta')) {
        const response = await getJson<{ meta?: Meta }>(
          resourceUrl(
            baseFromManifest(manifestUrl),
            'meta',
            item.type,
            item.id,
          ),
        );
        full = response.meta || item;
      }
      if (request !== detailRequest.current) return;
      setSelected(full);
      const preferredVideo = full.videos?.find(
        (video) => video.id === preferredVideoId,
      );
      const firstVideo = preferredVideo?.id || full.videos?.[0]?.id || full.id;
      setSelectedVideo(firstVideo);
      setSelectedSeason(
        preferredVideo?.season || full.videos?.[0]?.season || 1,
      );
      return await loadSources(full, firstVideo, request);
    } catch (reason) {
      if (request === detailRequest.current)
        setDetail({
          meta: full,
          streams: [],
          subtitles: [],
          loading: false,
          videoId: full.id,
          error:
            reason instanceof Error ? reason.message : 'Detay yüklenemedi.',
        });
    }
  };

  const loadSources = async (
    meta: Meta,
    videoId: string,
    request = ++detailRequest.current,
  ) => {
    setDetail((current) => ({
      meta,
      streams: current?.streams || [],
      subtitles: current?.subtitles || [],
      loading: true,
      videoId,
    }));
    if (!manifest) {
      const unavailable: DetailState = {
        meta,
        streams: [],
        subtitles: [],
        loading: false,
        videoId,
        error: 'Önce bir manifest bağlayın.',
      };
      setDetail(unavailable);
      return unavailable;
    }
    const base = baseFromManifest(manifestUrl);
    const [streamsResult, subtitlesResult] = await Promise.allSettled([
      hasResource(manifest, 'stream')
        ? getJson<{ streams?: Stream[] }>(
            resourceUrl(base, 'stream', meta.type, videoId),
          )
        : Promise.resolve({ streams: [] }),
      hasResource(manifest, 'subtitles')
        ? getJson<{ subtitles?: Subtitle[] }>(
            resourceUrl(base, 'subtitles', meta.type, videoId),
          )
        : Promise.resolve({ subtitles: [] }),
    ]);
    if (request !== detailRequest.current) return;
    const streams =
      streamsResult.status === 'fulfilled'
        ? streamsResult.value.streams || []
        : [];
    let subtitles =
      subtitlesResult.status === 'fulfilled'
        ? subtitlesResult.value.subtitles || []
        : [];
    if (
      !subtitles.length &&
      hasResource(manifest, 'subtitles') &&
      videoId !== meta.id
    ) {
      try {
        const fallback = await getJson<{ subtitles?: Subtitle[] }>(
          resourceUrl(
            base,
            'subtitles',
            meta.type,
            meta.id,
            `videoId=${encodeURIComponent(videoId)}`,
          ),
        );
        subtitles = fallback.subtitles || [];
      } catch {
        // Some addons only support the direct video id route.
      }
    }
    if (request !== detailRequest.current) return;
    const streamSubtitles = streams.flatMap((stream) => stream.subtitles || []);
    subtitles = [...subtitles, ...streamSubtitles].filter(
      (track, index, all) =>
        Boolean(track.url) &&
        all.findIndex((candidate) => candidate.url === track.url) === index,
    );
    const resolved: DetailState = {
      meta,
      streams,
      subtitles,
      loading: false,
      videoId,
      error:
        streamsResult.status === 'rejected'
          ? 'Yayın kaynakları alınamadı.'
          : undefined,
    };
    setDetail(resolved);
    return resolved;
  };

  const chooseEpisode = async (video: Video) => {
    if (!selected) return;
    setSelectedVideo(video.id);
    const resolved = await loadSources(selected, video.id);
    if (resolved && resolved.streams.some(isPlayableStream)) {
      setPlayer({ detail: resolved, index: 0 });
    }
  };
  const toggleFavorite = (id: string) => {
    const next = favorites.includes(id)
      ? favorites.filter((item) => item !== id)
      : [...favorites, id];
    setFavorites(next);
    if (account && activeProfile)
      localStorage.setItem(
        profileDataKey(account, activeProfile, 'favorites'),
        JSON.stringify(next),
      );
  };
  const saveProgress = useCallback(
    (entry: WatchProgress) => {
      setWatchProgress((current) => {
        const next = { ...current, [entry.key]: entry };
        if (account && activeProfile)
          localStorage.setItem(
            profileDataKey(account, activeProfile, 'progress'),
            JSON.stringify(next),
          );
        return next;
      });
    },
    [account, activeProfile],
  );
  const filteredCatalogs = useMemo(
    () =>
      !query.trim()
        ? catalogs
        : catalogs.map((catalog) => ({
            ...catalog,
            items: catalog.items.filter((item) =>
              `${item.name} ${item.genres?.join(' ')}`
                .toLocaleLowerCase('tr')
                .includes(query.toLocaleLowerCase('tr')),
            ),
          })),
    [catalogs, query],
  );
  const featured = catalogs.find((catalog) => catalog.items.length)?.items[0];
  const featuredImage = featured?.background || featured?.poster;
  const continueWatching = useMemo(
    () =>
      Object.values(watchProgress)
        .filter(
          (entry) =>
            !entry.completed &&
            entry.currentTime > 5 &&
            entry.duration > entry.currentTime + 8,
        )
        .sort((a, b) => b.updatedAt - a.updatedAt)
        .slice(0, 10),
    [watchProgress],
  );
  const selectedSeasons = useMemo(
    () =>
      [
        ...new Set(selected?.videos?.map((video) => video.season || 1) || []),
      ].sort((a, b) => a - b),
    [selected],
  );
  const selectedSeasonEpisodes = useMemo(
    () =>
      selected?.videos?.filter(
        (video) => (video.season || 1) === selectedSeason,
      ) || [],
    [selected, selectedSeason],
  );

  const authenticate = (nextAccount: Account) => {
    setAccount(nextAccount);
    setActiveProfile(null);
    localStorage.setItem(
      'veyra:session',
      JSON.stringify({ email: nextAccount.email }),
    );
  };
  const selectProfile = (profile: Profile) => {
    if (!account) return;
    setActiveProfile(profile);
    localStorage.setItem(
      'veyra:session',
      JSON.stringify({ email: account.email, profileId: profile.id }),
    );
  };
  const logout = () => {
    setAccount(null);
    setActiveProfile(null);
    setManifest(null);
    setCatalogs([]);
    didAutoConnect.current = false;
    localStorage.removeItem('veyra:session');
  };

  if (!hydrated)
    return (
      <main className="min-h-screen bg-background" aria-label="Yükleniyor" />
    );
  if (!account) return <AuthScreen onAuthenticated={authenticate} />;
  if (!activeProfile)
    return (
      <ProfilePicker
        account={account}
        onSelect={selectProfile}
        onUpdate={setAccount}
        onLogout={logout}
      />
    );

  return (
    <main className="min-h-screen bg-background text-foreground">
      <header className="sticky top-0 z-40 flex h-16 items-center gap-5 border-b border-white/8 bg-background/82 px-5 backdrop-blur-2xl md:px-9">
        <button
          className="flex items-center gap-2.5 font-semibold tracking-tight"
          onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
        >
          <span className="grid size-9 place-items-center rounded-xl bg-primary text-primary-foreground shadow-[0_0_28px_rgba(110,77,255,.35)]">
            <Clapperboard className="size-5" />
          </span>
          <span className="text-lg">Veyra</span>
        </button>
        <nav className="hidden items-center gap-5 text-sm text-muted-foreground md:flex">
          <a className="text-foreground" href="#top">
            Keşfet
          </a>
          <a href="#catalogs">Kataloglar</a>
          <a href="#manifest">Manifest</a>
        </nav>
        <div className="relative ml-auto hidden w-full max-w-sm md:block">
          <Input
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            aria-label="İçerik ara"
            className="h-10 border-white/10 bg-white/5 pl-10"
            placeholder="Film, dizi veya katalog ara"
          />
          <Search className="pointer-events-none absolute left-3 top-3 size-4 text-muted-foreground" />
        </div>
        {manifest ? (
          <Button
            variant="outline"
            className="h-10 border-emerald-400/20 bg-emerald-400/8 text-emerald-300"
            onClick={disconnect}
          >
            <Check /> {manifest.name}
          </Button>
        ) : (
          <Button
            variant="outline"
            className="h-10 border-white/10 bg-white/5"
            onClick={() =>
              document
                .getElementById('manifest')
                ?.scrollIntoView({ behavior: 'smooth' })
            }
          >
            <Plus /> Manifest ekle
          </Button>
        )}
        <Button
          variant="ghost"
          size="icon"
          aria-label="Ayarlar"
          onClick={() =>
            document
              .getElementById('manifest')
              ?.scrollIntoView({ behavior: 'smooth' })
          }
        >
          <Settings2 />
        </Button>
        <div className="relative">
          <button
            className="flex items-center gap-2 rounded-xl border border-white/8 bg-white/5 p-1.5 pr-3 text-sm transition hover:bg-white/9"
            onClick={() => setShowProfileMenu((value) => !value)}
            aria-label="Profil menüsü"
          >
            <span
              className="grid size-8 place-items-center rounded-lg font-semibold text-white"
              style={{ background: activeProfile.color }}
            >
              {activeProfile.name.slice(0, 1).toLocaleUpperCase('tr')}
            </span>
            <span className="hidden max-w-24 truncate sm:block">
              {activeProfile.name}
            </span>
          </button>
          {showProfileMenu && (
            <div className="absolute right-0 top-12 z-50 w-56 rounded-2xl border border-white/10 bg-[#151620]/98 p-2 shadow-2xl backdrop-blur-xl">
              <div className="px-3 py-2">
                <p className="truncate text-sm font-medium">
                  {activeProfile.name}
                </p>
                <p className="mt-0.5 truncate text-xs text-white/38">
                  {account.email}
                </p>
              </div>
              <button
                className="subtitle-option"
                onClick={() => {
                  setActiveProfile(null);
                  setShowProfileMenu(false);
                }}
              >
                <span className="flex items-center gap-2">
                  <UserRound className="size-4" /> Profil değiştir
                </span>
              </button>
              <button className="subtitle-option" onClick={logout}>
                <span className="flex items-center gap-2">
                  <LogOut className="size-4" /> Çıkış yap
                </span>
              </button>
            </div>
          )}
        </div>
      </header>

      {featured ? (
        <section
          id="top"
          className="relative isolate min-h-[540px] overflow-hidden px-5 pb-16 pt-24 md:px-9"
        >
          {featuredImage && (
            <img
              key={featuredImage}
              className="absolute inset-0 -z-20 h-full w-full object-cover opacity-45"
              src={featuredImage}
              alt=""
            />
          )}
          <div className="absolute inset-0 -z-10 bg-[linear-gradient(90deg,#090a10_4%,rgba(9,10,16,.88)_36%,rgba(9,10,16,.18)_75%),linear-gradient(0deg,#090a10_0%,transparent_58%)]" />
          <div className="max-w-2xl">
            <span className="mb-5 inline-flex rounded-full border border-white/15 bg-white/8 px-3 py-1 text-xs font-medium text-white/78 backdrop-blur">
              {manifest?.name.toLocaleUpperCase('tr')} ·{' '}
              {featured.type === 'series' ? 'DİZİ' : 'FİLM'}
            </span>
            <h1 className="text-5xl font-semibold tracking-[-.045em] md:text-7xl">
              {featured.name}
            </h1>
            <p className="mt-4 text-sm text-white/55">
              {featured.releaseInfo || featured.year}{' '}
              {featured.genres?.length
                ? `· ${featured.genres.join(' · ')}`
                : ''}
            </p>
            <p className="mt-5 max-w-xl text-base leading-7 text-white/67 md:text-lg">
              {featured.description ||
                'Manifestinizdeki katalogları tek bir sinematik arayüzde keşfedin; kaynağınızı seçin ve izlemeye başlayın.'}
            </p>
            <div className="mt-7 flex gap-3">
              <Button
                className="h-12 rounded-xl px-5 text-base"
                onClick={() => openDetail(featured)}
              >
                <Play className="fill-current" /> İzlemeye başla
              </Button>
              <Button
                variant="outline"
                className="h-12 rounded-xl border-white/15 bg-white/8 px-5 text-base backdrop-blur"
                onClick={() => openDetail(featured)}
              >
                <Info /> Detaylar
              </Button>
            </div>
          </div>
        </section>
      ) : (
        <section
          id="top"
          className="grid min-h-[430px] place-items-center px-5 pt-16 text-center"
        >
          <div>
            <LoaderCircle className="mx-auto size-8 animate-spin text-primary" />
            <h1 className="mt-5 text-3xl font-semibold">
              Kataloğun hazırlanıyor
            </h1>
            <p className="mt-2 text-sm text-white/45">
              Manifest içeriği güvenli biçimde yükleniyor.
            </p>
          </div>
        </section>
      )}

      <section
        className="relative z-10 -mt-9 space-y-10 px-5 pb-24 md:px-9"
        id="catalogs"
      >
        <div
          id="manifest"
          className="scroll-mt-24 rounded-2xl border border-white/10 bg-[#11131c]/90 p-4 shadow-2xl backdrop-blur-xl md:flex md:items-center md:gap-4"
        >
          <div className="min-w-56">
            <p className="font-medium">
              {manifest ? 'Manifest bağlı' : 'Manifestinizi bağlayın'}
            </p>
            <p className="mt-1 text-xs text-muted-foreground">
              Stremio uyumlu manifest.json URL’si
            </p>
          </div>
          <Input
            value={manifestInput}
            onChange={(e) => setManifestInput(e.target.value)}
            onKeyDown={(e) => e.key === 'Enter' && connectManifest()}
            className="mt-3 h-11 flex-1 border-white/10 bg-black/20 md:mt-0"
            placeholder="https://ornek.com/manifest.json"
          />
          <Button
            disabled={loading || !manifestInput.trim()}
            onClick={() => connectManifest()}
            className="mt-3 h-11 w-full px-5 md:mt-0 md:w-auto"
          >
            {loading ? <LoaderCircle className="animate-spin" /> : <Unplug />}{' '}
            {loading ? 'Bağlanıyor' : 'Katalogları getir'}
          </Button>
        </div>
        {error && (
          <div
            role="alert"
            className="rounded-xl border border-red-400/20 bg-red-400/8 px-4 py-3 text-sm text-red-200"
          >
            {error}
          </div>
        )}
        <div className="relative md:hidden">
          <Input
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            aria-label="İçerik ara"
            className="h-11 border-white/10 bg-white/5 pl-10"
            placeholder="Kataloglarda ara"
          />
          <Search className="pointer-events-none absolute left-3 top-3.5 size-4 text-muted-foreground" />
        </div>
        {continueWatching.length > 0 && (
          <section className="rounded-3xl border border-white/8 bg-white/[.025] p-4 md:p-5">
            <div className="mb-4 flex items-end justify-between">
              <div>
                <p className="text-xs font-medium uppercase tracking-[.18em] text-primary">
                  Sana özel
                </p>
                <h2 className="mt-1 text-2xl font-semibold tracking-tight">
                  İzlemeye devam et
                </h2>
              </div>
              <span className="hidden text-xs text-muted-foreground sm:block">
                Kaldığın yer bu cihazda saklanır
              </span>
            </div>
            <div className="catalog-row">
              {continueWatching.map((entry) => {
                const item =
                  catalogs
                    .flatMap((catalog) => catalog.items)
                    .find((candidate) => candidate.id === entry.metaId) ||
                  ({
                    id: entry.metaId,
                    type: entry.season ? 'series' : 'movie',
                    name: entry.metaName,
                    poster: entry.poster,
                    background: entry.background,
                  } as Meta);
                const percentage = Math.min(
                  100,
                  Math.max(0, (entry.currentTime / entry.duration) * 100),
                );
                return (
                  <button
                    key={entry.key}
                    className="group w-[280px] shrink-0 text-left sm:w-[340px]"
                    onClick={async () => {
                      const resolved = await openDetail(item, entry.videoId);
                      if (resolved && resolved.streams.some(isPlayableStream))
                        setPlayer({ detail: resolved, index: 0 });
                    }}
                  >
                    <div className="relative aspect-[16/9] overflow-hidden rounded-2xl bg-muted">
                      {entry.thumbnail || entry.background || entry.poster ? (
                        <img
                          src={
                            entry.thumbnail || entry.background || entry.poster
                          }
                          alt=""
                          className="h-full w-full object-cover transition duration-500 group-hover:scale-105"
                        />
                      ) : (
                        <span className="grid h-full place-items-center bg-gradient-to-br from-primary/25 to-black">
                          <Clapperboard className="size-8 text-white/30" />
                        </span>
                      )}
                      <div className="absolute inset-0 bg-gradient-to-t from-black/85 via-black/15 to-transparent" />
                      <span className="absolute bottom-4 left-4 grid size-10 place-items-center rounded-full bg-white text-black shadow-xl transition group-hover:scale-105">
                        <Play className="size-4 fill-current" />
                      </span>
                      <span className="absolute bottom-5 right-4 text-xs font-medium tabular-nums text-white/80">
                        {formatTime(entry.duration - entry.currentTime)} kaldı
                      </span>
                      <span className="absolute inset-x-0 bottom-0 h-1 bg-white/15">
                        <span
                          className="block h-full bg-primary"
                          style={{ width: `${percentage}%` }}
                        />
                      </span>
                    </div>
                    <h3 className="mt-3 truncate font-medium">
                      {entry.metaName}
                    </h3>
                    <p className="mt-1 truncate text-sm text-muted-foreground">
                      {entry.season
                        ? `S${entry.season} · B${entry.episode || 1} · ${entry.episodeTitle || 'Bölüm'}`
                        : 'Film'}
                    </p>
                  </button>
                );
              })}
            </div>
          </section>
        )}
        {loading ? (
          <div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
            {Array.from({ length: 8 }).map((_, index) => (
              <div
                key={index}
                className="aspect-[16/10] animate-pulse rounded-2xl bg-white/6"
              />
            ))}
          </div>
        ) : (
          filteredCatalogs.map((catalog) => (
            <section key={`${catalog.def.type}:${catalog.def.id}`}>
              <div className="mb-4 flex items-end justify-between">
                <div>
                  <p className="text-xs font-medium uppercase tracking-[.18em] text-primary">
                    {catalog.def.type}
                  </p>
                  <h2 className="mt-1 text-2xl font-semibold tracking-tight">
                    {catalog.def.name || catalog.def.id}
                  </h2>
                </div>
                <span className="text-sm text-muted-foreground">
                  {catalog.items.length} içerik
                </span>
              </div>
              {catalog.error ? (
                <p className="rounded-xl border border-amber-300/15 bg-amber-300/5 p-4 text-sm text-amber-100/70">
                  {catalog.error}
                </p>
              ) : catalog.items.length ? (
                <div className="catalog-row">
                  {catalog.items.map((item) => (
                    <article
                      key={`${catalog.def.id}:${item.id}`}
                      className="group w-[230px] shrink-0 cursor-pointer sm:w-[270px]"
                      onClick={() => openDetail(item)}
                    >
                      <div className="relative aspect-[16/10] overflow-hidden rounded-2xl bg-muted">
                        {item.poster || item.background ? (
                          <img
                            src={item.poster || item.background}
                            alt=""
                            className="h-full w-full object-cover transition duration-500 group-hover:scale-105"
                          />
                        ) : (
                          <span className="grid h-full place-items-center bg-gradient-to-br from-primary/20 to-black">
                            <Clapperboard className="size-7 text-white/30" />
                          </span>
                        )}
                        <div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent" />
                        <button
                          aria-label={`${item.name} oynat`}
                          className="absolute bottom-3 right-3 grid size-10 translate-y-2 place-items-center rounded-full bg-white text-black opacity-0 shadow-xl transition group-hover:translate-y-0 group-hover:opacity-100"
                        >
                          <Play className="size-4 fill-current" />
                        </button>
                        <span className="absolute left-3 top-3 rounded-md bg-black/55 px-2 py-1 text-[10px] font-semibold text-white backdrop-blur">
                          {item.type === 'series' ? 'DİZİ' : 'HD'}
                        </span>
                      </div>
                      <h3 className="mt-3 truncate font-medium">{item.name}</h3>
                      <p className="mt-1 truncate text-sm text-muted-foreground">
                        {item.releaseInfo || item.year || item.type}
                        {item.genres?.length
                          ? ` · ${item.genres.slice(0, 2).join(', ')}`
                          : ''}
                      </p>
                    </article>
                  ))}
                </div>
              ) : (
                <p className="rounded-xl border border-white/8 bg-white/3 p-4 text-sm text-muted-foreground">
                  Bu katalogdan içerik dönmedi.
                </p>
              )}
            </section>
          ))
        )}
        {!loading &&
          filteredCatalogs.every((catalog) => catalog.items.length === 0) &&
          query && (
            <div className="py-16 text-center">
              <Search className="mx-auto mb-3 size-8 text-muted-foreground" />
              <p className="font-medium">“{query}” için sonuç yok</p>
              <p className="mt-1 text-sm text-muted-foreground">
                Başka bir ad veya tür deneyin.
              </p>
            </div>
          )}
      </section>

      <footer className="border-t border-white/8 px-5 py-8 text-sm text-muted-foreground md:flex md:items-center md:justify-between md:px-9">
        <p>
          Veyra yalnızca kullanıcı tarafından sağlanan manifest ve yayın
          adreslerini işler; içerik barındırmaz.
        </p>
        <p className="mt-2 md:mt-0">Stremio Addon Protocol uyumlu</p>
      </footer>

      {selected && (
        <div
          className="fixed inset-0 z-50 grid place-items-center p-4"
          role="dialog"
          aria-modal="true"
          aria-label={`${selected.name} detayı`}
        >
          <button
            className="absolute inset-0 bg-black/75 backdrop-blur-sm"
            aria-label="Detay penceresini kapat"
            onClick={() => {
              setSelected(null);
              setDetail(null);
              detailRequest.current++;
            }}
          />
          <section className="relative z-10 max-h-[94vh] w-full max-w-6xl overflow-y-auto rounded-2xl border border-white/10 bg-[#11131c] shadow-2xl">
            <button
              className="absolute right-3 top-3 z-20 grid size-9 place-items-center rounded-full bg-black/35 text-white backdrop-blur hover:bg-white/15"
              aria-label="Kapat"
              onClick={() => {
                setSelected(null);
                setDetail(null);
                detailRequest.current++;
              }}
            >
              <X className="size-4" />
            </button>
            {selected && (
              <div>
                <div className="relative min-h-72 overflow-hidden rounded-t-xl p-6 pt-24">
                  {selected.background || selected.poster ? (
                    <img
                      src={selected.background || selected.poster}
                      alt=""
                      className="absolute inset-0 h-full w-full object-cover opacity-45"
                    />
                  ) : null}
                  <div className="absolute inset-0 bg-gradient-to-t from-[#11131c] via-[#11131c]/55 to-black/15" />
                  <div className="relative">
                    <p className="text-xs font-semibold uppercase tracking-[.16em] text-primary">
                      {selected.type === 'series' ? 'Dizi' : 'Film'}
                    </p>
                    <h2 className="mt-2 text-4xl font-semibold tracking-tight">
                      {selected.name}
                    </h2>
                    <p className="mt-3 text-sm text-white/60">
                      {selected.releaseInfo || selected.year}{' '}
                      {selected.runtime ? `· ${selected.runtime}` : ''}{' '}
                      {selected.genres?.length
                        ? `· ${selected.genres.join(' · ')}`
                        : ''}
                    </p>
                  </div>
                </div>
                <div className="space-y-6 p-6">
                  <p className="max-w-3xl leading-7 text-white/68">
                    {selected.description ||
                      'Bu içerik için açıklama sağlanmadı.'}
                  </p>
                  {selected.videos?.length ? (
                    <div>
                      <div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
                        <div>
                          <h3 className="font-semibold">
                            Sezonlar ve bölümler
                          </h3>
                          <p className="mt-1 text-xs text-muted-foreground">
                            {selected.videos.length} bölüm ·{' '}
                            {selectedSeasons.length} sezon
                          </p>
                        </div>
                        <div className="flex gap-2 overflow-x-auto pb-1">
                          {selectedSeasons.map((season) => (
                            <button
                              key={season}
                              className={`shrink-0 rounded-lg px-3 py-2 text-sm font-medium transition ${selectedSeason === season ? 'bg-white text-black' : 'bg-white/6 text-white/65 hover:bg-white/10 hover:text-white'}`}
                              onClick={() => setSelectedSeason(season)}
                            >
                              {season}. Sezon
                            </button>
                          ))}
                        </div>
                      </div>
                      <div className="grid max-h-[430px] gap-2 overflow-y-auto pr-1 sm:grid-cols-2">
                        {selectedSeasonEpisodes.map((video) => {
                          const episodeProgress =
                            watchProgress[progressKey(selected.id, video.id)];
                          const percentage = episodeProgress
                            ? Math.min(
                                100,
                                (episodeProgress.currentTime /
                                  episodeProgress.duration) *
                                  100,
                              )
                            : 0;
                          return (
                            <button
                              key={video.id}
                              onClick={() => chooseEpisode(video)}
                              className={`group relative overflow-hidden rounded-xl border p-3 text-left transition ${selectedVideo === video.id ? 'border-primary/55 bg-primary/10' : 'border-white/8 bg-white/4 hover:border-white/15 hover:bg-white/7'}`}
                            >
                              <div className="flex gap-3">
                                <span className="relative grid aspect-video w-24 shrink-0 place-items-center overflow-hidden rounded-lg bg-black/35">
                                  {video.thumbnail ? (
                                    <img
                                      src={video.thumbnail}
                                      alt=""
                                      className="h-full w-full object-cover opacity-75"
                                    />
                                  ) : (
                                    <Play className="size-4 text-white/70" />
                                  )}
                                  <span className="absolute inset-0 grid place-items-center opacity-0 transition group-hover:opacity-100">
                                    <span className="grid size-8 place-items-center rounded-full bg-white text-black">
                                      <Play className="size-3 fill-current" />
                                    </span>
                                  </span>
                                </span>
                                <span className="min-w-0 flex-1">
                                  <span className="text-[11px] font-medium uppercase tracking-wider text-primary">
                                    Bölüm {video.episode || 1}
                                  </span>
                                  <span className="mt-1 line-clamp-2 text-sm font-medium leading-5">
                                    {video.title ||
                                      `Bölüm ${video.episode || 1}`}
                                  </span>
                                  {episodeProgress &&
                                    !episodeProgress.completed && (
                                      <span className="mt-1.5 flex items-center gap-1 text-[11px] text-white/45">
                                        <Clock3 className="size-3" />
                                        {formatTime(
                                          episodeProgress.duration -
                                            episodeProgress.currentTime,
                                        )}{' '}
                                        kaldı
                                      </span>
                                    )}
                                </span>
                              </div>
                              {percentage > 0 && (
                                <span className="absolute inset-x-0 bottom-0 h-0.5 bg-white/10">
                                  <span
                                    className="block h-full bg-primary"
                                    style={{ width: `${percentage}%` }}
                                  />
                                </span>
                              )}
                            </button>
                          );
                        })}
                      </div>
                    </div>
                  ) : null}
                  <div>
                    <div className="mb-3 flex items-center justify-between">
                      <h3 className="font-semibold">Yayın kaynakları</h3>
                      <span className="text-xs text-muted-foreground">
                        {detail?.streams.length || 0} kaynak ·{' '}
                        {detail?.subtitles.length || 0} altyazı
                      </span>
                    </div>
                    {detail?.loading ? (
                      <div className="flex items-center gap-2 rounded-xl bg-white/4 p-4 text-sm text-muted-foreground">
                        <LoaderCircle className="animate-spin" /> Kaynaklar
                        çözülüyor…
                      </div>
                    ) : detail?.streams.length ? (
                      <div className="space-y-2">
                        {detail.streams.map((stream, index) => {
                          const playable = isPlayableStream(stream);
                          return (
                            <button
                              key={`${stream.url || stream.externalUrl || stream.infoHash || stream.ytId}-${index}`}
                              disabled={!playable}
                              onClick={() =>
                                playable &&
                                setPlayer({
                                  detail,
                                  index: detail.streams
                                    .filter(isPlayableStream)
                                    .findIndex((item) => item === stream),
                                })
                              }
                              className="flex w-full items-center gap-4 rounded-xl border border-white/8 bg-white/4 p-3 text-left transition hover:border-primary/30 hover:bg-white/7 disabled:cursor-not-allowed disabled:opacity-45"
                            >
                              <span className="grid size-10 shrink-0 place-items-center rounded-lg bg-primary/14 text-primary">
                                {playable ? (
                                  <Play className="size-4 fill-current" />
                                ) : (
                                  <Gauge className="size-4" />
                                )}
                              </span>
                              <span className="min-w-0 flex-1">
                                <span className="block truncate text-sm font-medium">
                                  {streamLabel(stream, index)}
                                </span>
                                <span className="mt-1 block text-xs text-muted-foreground">
                                  {playable
                                    ? stream.externalUrl && !stream.url
                                      ? 'Sağlayıcının gömülü web oynatıcısı'
                                      : 'Tarayıcıda oynatılabilir'
                                    : stream.infoHash
                                      ? 'Torrent kaynağı · yerel streaming engine gerekir'
                                      : stream.ytId
                                        ? 'YouTube kaynağı · harici oynatıcı gerekir'
                                        : 'Web oynatımına uygun değil'}
                                </span>
                              </span>
                              <ChevronRight className="size-4 text-muted-foreground" />
                            </button>
                          );
                        })}
                      </div>
                    ) : (
                      <div className="rounded-xl border border-white/8 bg-white/3 p-4 text-sm text-muted-foreground">
                        {detail?.error ||
                          'Bu içerik için yayın kaynağı bulunamadı.'}
                      </div>
                    )}
                  </div>
                  <div className="flex items-center gap-3 border-t border-white/8 pt-5">
                    <Button
                      onClick={() =>
                        detail &&
                        detail.streams.some(isPlayableStream) &&
                        setPlayer({ detail, index: 0 })
                      }
                      disabled={!detail?.streams.some(isPlayableStream)}
                      className="h-11 px-5"
                    >
                      <Play className="fill-current" />{' '}
                      {detail &&
                      watchProgress[progressKey(detail.meta.id, detail.videoId)]
                        ?.currentTime > 5
                        ? `Kaldığın yerden devam et · ${formatTime(
                            watchProgress[
                              progressKey(detail.meta.id, detail.videoId)
                            ].currentTime,
                          )}`
                        : 'Oynat'}
                    </Button>
                    <Button
                      variant="outline"
                      className="h-11 border-white/10 bg-white/4"
                      onClick={() => toggleFavorite(selected.id)}
                    >
                      {favorites.includes(selected.id) ? (
                        <Heart className="fill-current text-primary" />
                      ) : (
                        <Plus />
                      )}{' '}
                      {favorites.includes(selected.id)
                        ? 'Listemde'
                        : 'Listeme ekle'}
                    </Button>
                  </div>
                </div>
              </div>
            )}
          </section>
        </div>
      )}
      {player && (
        <Player
          detail={player.detail}
          initialIndex={player.index}
          progress={
            watchProgress[
              progressKey(player.detail.meta.id, player.detail.videoId)
            ]
          }
          onProgress={saveProgress}
          onClose={() => setPlayer(null)}
        />
      )}
    </main>
  );
}
