import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ArrowLeft, Ban, Check, ExternalLink, Loader2, Plus, RefreshCw } from "lucide-react";
import Avatar from "./Avatar";
import Feed from "./Feed";
import { useConfirm } from "./ConfirmProvider";
import { notify } from "../lib/notifications";
import { notifyYouTubeActionError } from "../lib/youtubeErrors";
import { api, type FeedFilters, type Me } from "../lib/api";
import { channelYouTubeUrl, formatViews } from "../lib/format";

// --- About-tab metadata helpers ---
// ISO 3166-1 alpha-2 code → 🇺🇸 regional-indicator flag emoji.
function countryFlag(code: string): string {
  if (!/^[A-Za-z]{2}$/.test(code)) return "";
  return String.fromCodePoint(...[...code.toUpperCase()].map((c) => 0x1f1e6 + c.charCodeAt(0) - 65));
}
function displayName(code: string, lang: string, type: "region" | "language"): string {
  try {
    return new Intl.DisplayNames([lang], { type }).of(type === "region" ? code.toUpperCase() : code) ?? code;
  } catch {
    return code;
  }
}
// YouTube keywords are ONE space-separated string with multi-word tags quoted: gaming "let's play".
function parseKeywords(s: string): string[] {
  return (s.match(/"[^"]+"|\S+/g) ?? []).map((k) => k.replace(/^"|"$/g, "")).filter(Boolean);
}
// topicCategories are Wikipedia URLs (…/wiki/Music) → a readable label, de-duplicated.
function topicLabels(urls: string[]): string[] {
  const seen = new Set<string>();
  for (const url of urls) {
    const seg = url.split("/wiki/")[1] ?? url.split("/").pop() ?? url;
    let label = seg;
    try {
      label = decodeURIComponent(seg);
    } catch {
      /* keep raw */
    }
    label = label.replace(/_/g, " ").trim();
    if (label) seen.add(label);
  }
  return [...seen];
}

// A dedicated channel page: an "About"-style header (banner, avatar, stats, subscribe) over the
// channel's videos (the catalog filtered to this channel). For an un-subscribed channel it
// auto-ingests recent uploads in the background ("explore") so they're browsable immediately,
// and offers "load more" to page deeper — without permanently polluting everyone's library
// (the backend keeps explored videos private to this user until they subscribe).
export default function ChannelPage({
  channelId,
  initialName,
  me,
  view,
  onBack,
  onOpenChannel,
}: {
  channelId: string;
  initialName?: string;
  me: Me;
  view: "grid" | "list";
  onBack: () => void;
  onOpenChannel: (id: string, name?: string) => void;
}) {
  const { t, i18n } = useTranslation();
  const qc = useQueryClient();
  const confirm = useConfirm();
  const [tab, setTab] = useState<"videos" | "about">("videos");
  // The banner URL is valid, but googleusercontent intermittently throttles it when the channel
  // page fires the banner + avatar + ~16 thumbnails in one burst — the request is dropped and the
  // browser then NEGATIVE-CACHES the miss, so the banner stays broken even though a retry would
  // succeed. So on error we retry a few times with a cache-buster (forcing a fresh request), and
  // only fall back to the blank bar if it keeps failing (a genuinely dead URL — rare).
  const MAX_BANNER_RETRIES = 3;
  const [bannerAttempt, setBannerAttempt] = useState(0);

  const detail = useQuery({
    queryKey: ["channel", channelId],
    queryFn: () => api.channelDetail(channelId),
  });
  const ch = detail.data;
  useEffect(() => setBannerAttempt(0), [ch?.banner_url]); // fresh channel/banner → try again

  // Background auto-ingest ("explore") of an un-subscribed channel's uploads.
  const [nextToken, setNextToken] = useState<string | null | undefined>(undefined);
  const [exploring, setExploring] = useState(false);
  const autoTried = useRef<string | null>(null);

  const runExplore = useCallback(
    (token?: string | null) => {
      setExploring(true);
      return api
        .exploreChannel(channelId, token ?? undefined)
        .then((r) => {
          setNextToken(r.next_page_token);
          qc.invalidateQueries({ queryKey: ["feed"] });
          // Refetch the detail so `explored` flips true → the "Exploring" badge shows on the
          // first visit (the GET that drove this render predated the explore that just ran).
          qc.invalidateQueries({ queryKey: ["channel", channelId] });
        })
        .finally(() => setExploring(false));
    },
    [channelId, qc]
  );

  // First visit of an un-subscribed channel → ingest its recent uploads. Skipped for the demo
  // account (explore spends shared quota) and when videos are already present (a revisit, or a
  // subscribed channel whose uploads the scheduler backfills).
  useEffect(() => {
    if (!ch || me.is_demo) return;
    if (autoTried.current === channelId) return;
    if (ch.subscribed || ch.blocked) return;
    if (ch.explored && ch.stored_videos > 0) return;
    autoTried.current = channelId;
    runExplore().catch(() => {});
  }, [ch, channelId, me.is_demo, runExplore]);

  const block = useMutation({
    mutationFn: () => (ch?.blocked ? api.unblockChannel(channelId) : api.blockChannel(channelId)),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["channel", channelId] });
      qc.invalidateQueries({ queryKey: ["feed"] });
      qc.invalidateQueries({ queryKey: ["blocked-channels"] });
    },
  });

  const subscribe = useMutation({
    mutationFn: () => api.subscribeChannel(channelId),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["channel", channelId] });
      qc.invalidateQueries({ queryKey: ["feed"] });
      qc.invalidateQueries({ queryKey: ["channels"] });
      // Match ChannelDiscovery: the channel leaves discovery and the manager's stats move.
      qc.invalidateQueries({ queryKey: ["my-status"] });
      qc.invalidateQueries({ queryKey: ["discovered-channels"] });
      notify({ level: "info", message: t("channel.subscribed", { name: ch?.title ?? "" }) });
    },
    // A 403 (missing write scope) otherwise fails silently on the channel page (no wizard handle
    // here, so no Connect button — but the user at least sees why it didn't work).
    onError: (err) => notifyYouTubeActionError(err, t("channels.discovery.subscribeFailed")),
  });
  const unsubscribe = useMutation({
    mutationFn: () => api.unsubscribeChannel(channelId),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["channel", channelId] });
      qc.invalidateQueries({ queryKey: ["feed"] });
      qc.invalidateQueries({ queryKey: ["channels"] });
    },
  });
  const onUnsubscribe = async () => {
    const ok = await confirm({
      title: t("channel.unsubTitle"),
      message: t("channel.unsubBody", { name: ch?.title ?? channelId }),
      confirmLabel: t("channel.unsubscribe"),
      danger: true,
    });
    if (ok) unsubscribe.mutate();
  };

  // Channel-scoped feed: the whole catalog filtered to this channel (scope=all + source=all so
  // the explored, per-user-private videos show — the backend gates them to this explorer).
  const [filters, setFilters] = useState<FeedFilters>(() => ({
    tags: [],
    tagMode: "or",
    q: "",
    sort: "newest",
    scope: "all",
    librarySource: "all",
    includeNormal: true,
    includeShorts: false,
    includeLive: true,
    show: "all",
    channelId,
    channelName: initialName,
  }));

  const name = ch?.title ?? initialName ?? channelId;
  const ytUrl = channelYouTubeUrl(channelId, ch?.handle);
  const joined = ch?.published_at
    ? new Date(ch.published_at).toLocaleDateString(i18n.language, { year: "numeric", month: "short" })
    : null;
  // Handle + stats on one compact meta line under the name (saves the separate stats row).
  const metaParts = [
    ch?.handle ? `@${ch.handle.replace(/^@/, "")}` : null,
    ch?.subscriber_count != null ? t("channel.subscribers", { formatted: formatViews(ch.subscriber_count) }) : null,
    ch?.video_count != null ? t("channel.videoCount", { count: ch.video_count }) : null,
    ch?.total_view_count != null ? t("channel.totalViews", { formatted: formatViews(ch.total_view_count) }) : null,
    joined ? t("channel.joined", { date: joined }) : null,
  ].filter(Boolean) as string[];
  const topics = ch?.topic_categories?.length ? topicLabels(ch.topic_categories) : [];
  const keywords = ch?.keywords ? parseKeywords(ch.keywords) : [];
  const hasAboutDetails =
    !!ch?.country || !!ch?.default_language || topics.length > 0 || keywords.length > 0;
  const tabClass = (active: boolean) =>
    active
      ? "pb-2 text-sm font-medium border-b-2 border-fg text-fg"
      : "pb-2 text-sm text-muted hover:text-fg border-b-2 border-transparent";

  return (
    // scrollbar-gutter:stable reserves the scrollbar track on BOTH tabs, so switching between the
    // tall Videos tab (scrollbar) and the short About tab (no scrollbar) no longer shifts the
    // header/logo/buttons horizontally as the scrollbar appears/disappears.
    <main className="flex-1 min-w-0 overflow-y-auto [scrollbar-gutter:stable]">
      {/* Banner + back — OPTION C: inset, rounded card (not full-bleed) */}
      <div className="relative px-4 sm:px-6 pt-3">
        {ch?.banner_url && bannerAttempt <= MAX_BANNER_RETRIES ? (
          // Match YouTube's banner: the stored bannerExternalUrl is the full 16:9 template at a
          // low default size, so request a crisp wide version (=w1707) and object-cover the
          // desktop "safe area" — the centre 2560×423 (~6:1) band.
          <div
            className="w-full overflow-hidden rounded-2xl bg-surface"
            style={{ aspectRatio: "2560 / 423", maxHeight: "150px" }}
          >
            <img
              // key forces a fresh <img> per attempt; the ?r= cache-buster on a retry bypasses the
              // browser's negative cache (googleusercontent accepts an extra query param).
              key={bannerAttempt}
              src={`${ch.banner_url}=w1707${bannerAttempt ? `?r=${bannerAttempt}` : ""}`}
              alt=""
              className="w-full h-full object-cover object-center"
              onError={() => {
                if (bannerAttempt <= MAX_BANNER_RETRIES) {
                  window.setTimeout(() => setBannerAttempt((a) => a + 1), 500 * (bannerAttempt + 1));
                }
              }}
            />
          </div>
        ) : (
          // No banner (or a dead one): a neutral bar the SAME height as a real banner, so the
          // overlapping avatar + the Back button have the same room and don't collide (a short bar
          // let the -mt-8 avatar ride up into the Back button).
          <div
            className="w-full rounded-2xl bg-surface"
            style={{ aspectRatio: "2560 / 423", maxHeight: "150px" }}
          />
        )}
        <button
          onClick={onBack}
          className="absolute top-5 left-7 sm:left-9 z-10 inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm bg-black/45 text-white hover:bg-black/65 backdrop-blur-sm"
        >
          <ArrowLeft className="w-4 h-4" />
          {t("channel.back")}
        </button>
      </div>

      <div className="px-4 sm:px-6">
        {/* Identity + actions. relative+z-10 so the avatar that overlaps up into the banner
            paints ABOVE it (the banner's positioned container would otherwise cover it). */}
        <div className="relative z-10 flex items-end gap-4 -mt-8">
          <Avatar
            src={ch?.thumbnail_url ?? null}
            fallback={name}
            className="w-20 h-20 rounded-full border-4 border-bg shrink-0 bg-bg"
          />
          <div className="flex-1 min-w-0 pb-1">
            <div className="flex items-center gap-2 flex-wrap">
              <h1 className="text-xl font-semibold truncate">{name}</h1>
              {ch?.blocked ? (
                <span className="text-xs px-2 py-0.5 rounded-full bg-danger/15 text-danger">
                  {t("channel.blockedBadge")}
                </span>
              ) : (
                ch?.explored &&
                !ch?.subscribed && (
                  <span className="text-xs px-2 py-0.5 rounded-full bg-amber-500/15 text-amber-600 dark:text-amber-400">
                    {t("channel.exploringBadge")}
                  </span>
                )
              )}
            </div>
            <div className="text-sm text-muted mt-0.5 flex flex-wrap gap-x-1.5 gap-y-0.5">
              {metaParts.map((part, i) => (
                <span key={i}>
                  {i > 0 && <span className="mr-1.5">·</span>}
                  {part}
                </span>
              ))}
            </div>
          </div>
          <div className="flex items-center gap-2 shrink-0 pb-1">
            <a
              href={ytUrl}
              target="_blank"
              rel="noreferrer"
              title={t("channels.row.openOnYouTube")}
              aria-label={t("channels.row.openOnYouTube")}
              className="inline-flex items-center justify-center w-9 h-9 rounded-lg text-muted hover:text-fg border border-border"
            >
              <ExternalLink className="w-4 h-4" />
            </a>
            {!me.is_demo && (
              <button
                onClick={() => block.mutate()}
                disabled={block.isPending}
                title={ch?.blocked ? t("channel.unblock") : t("channel.block")}
                aria-label={ch?.blocked ? t("channel.unblock") : t("channel.block")}
                className={`inline-flex items-center justify-center w-9 h-9 rounded-lg border disabled:opacity-50 transition ${
                  ch?.blocked
                    ? "border-danger text-danger"
                    : "border-border text-muted hover:text-danger hover:border-danger"
                }`}
              >
                <Ban className="w-4 h-4" />
              </button>
            )}
            {!me.is_demo &&
              !ch?.blocked &&
              (ch?.subscribed ? (
                <button
                  onClick={onUnsubscribe}
                  disabled={unsubscribe.isPending}
                  title={t("channel.unsubscribe")}
                  className="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm border border-border text-muted hover:text-fg disabled:opacity-50"
                >
                  <Check className="w-4 h-4" />
                  {t("channel.subscribedState")}
                </button>
              ) : (
                <button
                  onClick={() => subscribe.mutate()}
                  disabled={subscribe.isPending}
                  className="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm bg-accent text-accent-fg hover:opacity-90 disabled:opacity-50"
                >
                  <Plus className="w-4 h-4" />
                  {t("channel.subscribe")}
                </button>
              ))}
          </div>
        </div>

        {exploring && (
          <div className="flex items-center gap-2 text-xs text-muted mt-2">
            <Loader2 className="w-3.5 h-3.5 animate-spin" />
            {t("channel.loadingVideos")}
          </div>
        )}

        {/* Tabs */}
        <div className="flex items-center gap-4 mt-3 border-b border-border">
          <button onClick={() => setTab("videos")} className={tabClass(tab === "videos")}>
            {t("channel.tabVideos")}
          </button>
          <button onClick={() => setTab("about")} className={tabClass(tab === "about")}>
            {t("channel.tabAbout")}
          </button>
        </div>
      </div>

      {/* Tab content */}
      {tab === "videos" ? (
        <>
          <Feed
            filters={filters}
            setFilters={setFilters}
            view={view}
            canRead={me.can_read}
            isDemo={me.is_demo}
            onOpenWizard={() => {}}
            ytSearch={null}
            onYtSearch={() => {}}
            onExitYtSearch={() => {}}
            onOpenChannel={onOpenChannel}
            channelScoped
          />
          {!ch?.subscribed && nextToken && (
            <div className="flex justify-center pb-6">
              <button
                onClick={() => runExplore(nextToken)}
                disabled={exploring}
                className="inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm border border-border hover:bg-surface disabled:opacity-50"
              >
                {exploring ? (
                  <Loader2 className="w-4 h-4 animate-spin" />
                ) : (
                  <RefreshCw className="w-4 h-4" />
                )}
                {t("channel.loadMore")}
              </button>
            </div>
          )}
        </>
      ) : (
        <div className="px-4 sm:px-6 py-4 max-w-3xl">
          {ch?.description ? (
            <p className="text-sm whitespace-pre-wrap break-words leading-relaxed">{ch.description}</p>
          ) : (
            <p className="text-sm text-muted">{t("channel.noDescription")}</p>
          )}
          {ch?.external_links && ch.external_links.length > 0 && (
            <div className="mt-4 flex flex-wrap gap-2">
              {ch.external_links.map((l, i) => (
                <a
                  key={i}
                  href={l.url}
                  target="_blank"
                  rel="noreferrer"
                  className="inline-flex items-center gap-1.5 text-sm text-accent hover:underline"
                >
                  <ExternalLink className="w-3.5 h-3.5" />
                  {l.title || l.url}
                </a>
              ))}
            </div>
          )}
          {hasAboutDetails && (
            <div className="mt-5 space-y-2.5 text-sm border-t border-border pt-4">
              {ch?.country && (
                <div className="flex gap-2">
                  <span className="text-muted w-24 shrink-0">{t("channel.country")}</span>
                  <span>
                    {countryFlag(ch.country)} {displayName(ch.country, i18n.language, "region")}
                  </span>
                </div>
              )}
              {ch?.default_language && (
                <div className="flex gap-2">
                  <span className="text-muted w-24 shrink-0">{t("channel.language")}</span>
                  <span>{displayName(ch.default_language, i18n.language, "language")}</span>
                </div>
              )}
              {topics.length > 0 && (
                <div className="flex gap-2">
                  <span className="text-muted w-24 shrink-0 pt-0.5">{t("channel.topics")}</span>
                  <div className="flex flex-wrap gap-1.5">
                    {topics.map((tp) => (
                      <span key={tp} className="glass-card px-2 py-0.5 rounded-full text-xs">
                        {tp}
                      </span>
                    ))}
                  </div>
                </div>
              )}
              {keywords.length > 0 && (
                <div className="flex gap-2">
                  <span className="text-muted w-24 shrink-0 pt-0.5">{t("channel.keywords")}</span>
                  <div className="flex flex-wrap gap-1.5">
                    {keywords.map((kw, i) => (
                      <span key={i} className="glass-card px-2 py-0.5 rounded-full text-xs">
                        {kw}
                      </span>
                    ))}
                  </div>
                </div>
              )}
            </div>
          )}
        </div>
      )}
    </main>
  );
}
