import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { qk } from "../lib/queryKeys";
import { UserPlus } from "lucide-react";
import { api, type DiscoveredChannel } from "../lib/api";
import { accountKey, LS, readAccount, writeAccount } from "../lib/storage";
import { formatCountOrDash } from "../lib/format";
import { parseSortState, sortRows, type SortState } from "../lib/columnSort";
import { useCardPager } from "../lib/useCardPager";
import { subsColumn } from "./channelColumns";
import { notify } from "../lib/notifications";
import { notifyYouTubeActionError } from "../lib/youtubeErrors";
import Tooltip from "./Tooltip";
import ChannelLink from "./ChannelLink";
import AboutCell from "./AboutCell";
import DataTable, { type Column } from "./DataTable";
import ChannelLayoutGrid from "./ChannelLayoutGrid";
import { LoadingState, StateMessage } from "./QueryState";
import ColumnSortControl from "./ColumnSortControl";
import Pager from "./Pager";
import { PageToolbar } from "./PageShell";
import { useConfirm } from "./ConfirmProvider";
import { useChannelLayout } from "./ChannelsProvider";

// The Channel manager's "Discovery" tab: channels that turn up in the user's playlists but
// that they don't subscribe to. Subscribing here only creates the subscription + enriches
// the channel's metadata — the scheduler pulls its videos on its next run (see backend).
export default function ChannelDiscovery({
  canWrite,
  search,
  onOpenWizard,
  onViewChannel,
}: {
  canWrite: boolean;
  // Client-side name filter from the shared top SearchBar (matches title + handle).
  search: string;
  onOpenWizard: () => void;
  onViewChannel: (id: string, name: string) => void;
}) {
  const { t } = useTranslation();
  const qc = useQueryClient();
  const confirm = useConfirm();
  // The table/cards density is shared with the Subscriptions tab (ChannelsProvider). The sort is
  // this tab's own (its columns differ), lifted out of the table so the card layout shares it.
  const layout = useChannelLayout();
  const [sort, setSortState] = useState<SortState>(() =>
    parseSortState(readAccount(LS.channelDiscoverySort, null)),
  );
  const setSort = (s: SortState) => {
    setSortState(s);
    writeAccount(LS.channelDiscoverySort, s);
  };

  const query = useQuery({
    queryKey: qk.discoveredChannels(),
    queryFn: api.discoveredChannels,
  });

  const subscribe = useMutation({
    mutationFn: (c: DiscoveredChannel) => api.subscribeChannel(c.id),
    onSuccess: (_data, c) => {
      // The channel moves from "discovery" to "subscribed", and its videos will start
      // arriving — refresh both lists plus the per-user status.
      qc.invalidateQueries({ queryKey: qk.discoveredChannels() });
      qc.invalidateQueries({ queryKey: qk.channels() });
      qc.invalidateQueries({ queryKey: qk.myStatus() });
      const name = c.title ?? c.id;
      // Name the channel and ship a typed payload so the inbox can offer "open in the
      // Channel manager" / "open on YouTube" links — kept after reload, when live
      // callbacks are gone (see ChannelSubscribedMeta).
      notify({
        level: "success",
        title: t("channels.discovery.subscribedTitle"),
        message: t("channels.discovery.subscribedBody", { name }),
        meta: { kind: "channel-subscribed", channelId: c.id, channelName: name },
      });
    },
    onError: (err: unknown) =>
      notifyYouTubeActionError(err, t("channels.discovery.subscribeFailed"), onOpenWizard),
  });

  // Subscribing changes the user's real YouTube account and spends a little quota — confirm
  // first (mirrors the unsubscribe guard in the Subscriptions tab).
  const onSubscribe = async (c: DiscoveredChannel) => {
    const ok = await confirm({
      title: t("channels.discovery.subscribe"),
      message: t("channels.discovery.confirmSubscribe", { name: c.title ?? c.id }),
      confirmLabel: t("channels.discovery.subscribe"),
    });
    if (ok) subscribe.mutate(c);
  };

  const q = search.trim().toLowerCase();
  const rows = (query.data ?? []).filter(
    (c) => !q || `${c.title ?? ""} ${c.handle ?? ""}`.toLowerCase().includes(q),
  );

  const columns: Column<DiscoveredChannel>[] = [
    // Actions first, mirroring the Subscriptions table (where it sits right after Prio) so the two
    // tabs' action clusters line up in the same place.
    {
      key: "actions",
      header: t("channels.cols.actions"),
      align: "left",
      nowrap: true,
      width: "120px",
      cardLabel: false,
      render: (c) => (
        <Tooltip
          hint={
            canWrite ? t("channels.discovery.subscribeHint") : t("channels.discovery.needWriteHint")
          }
        >
          <button
            onClick={() => onSubscribe(c)}
            disabled={!canWrite || subscribe.isPending}
            className="glass-card glass-hover inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs disabled:opacity-50 transition"
          >
            <UserPlus className="w-3.5 h-3.5" />
            {t("channels.discovery.subscribe")}
          </button>
        </Tooltip>
      ),
    },
    {
      key: "channel",
      header: t("channels.cols.channel"),
      sortable: true,
      sortValue: (c) => (c.title ?? c.id).toLowerCase(),
      filter: { kind: "text", get: (c) => `${c.title ?? ""} ${c.handle ?? ""}` },
      cardPrimary: true,
      // Cap the width (a max-w element inside the cell; table-layout:auto ignores a cell max-width) so
      // a long-titled channel can't stretch the column and overflow the centered table.
      render: (c) => (
        <div className="max-w-[18rem]">
          <ChannelLink
            id={c.id}
            title={c.title}
            handle={c.handle}
            thumbnailUrl={c.thumbnail_url}
            onView={() => onViewChannel(c.id, c.title ?? c.id)}
          />
        </div>
      ),
    },
    {
      key: "about",
      header: t("channels.cols.about"),
      hideInCard: true,
      render: (c) => <AboutCell text={c.description} channelId={c.id} />,
    },
    subsColumn<DiscoveredChannel>(t),
    {
      key: "videos",
      header: t("channels.discovery.cols.totalVideos"),
      align: "right",
      nowrap: true,
      sortable: true,
      sortValue: (c) => c.video_count ?? -1,
      render: (c) => (
        <span className="text-muted tabular-nums">{formatCountOrDash(c.video_count)}</span>
      ),
    },
    {
      key: "inPlaylists",
      header: t("channels.discovery.cols.inPlaylists"),
      align: "right",
      nowrap: true,
      sortable: true,
      sortValue: (c) => c.playlist_video_count,
      render: (c) => (
        <Tooltip
          hint={t("channels.discovery.cols.inPlaylistsHint", {
            videos: c.playlist_video_count,
            playlists: c.playlist_count,
          })}
        >
          <span className="text-muted tabular-nums cursor-help">
            {c.playlist_video_count} / {c.playlist_count}
          </span>
        </Tooltip>
      ),
    },
  ];

  // Card layout paginates like the table (slice the sorted rows; the Pager reports changes). Only
  // computed for cards — DataTable sorts and slices internally, so doing it in table mode too would
  // copy+sort the whole list every render for a result nothing reads.
  const isCards = layout === "cards";
  const sortedRows = isCards ? sortRows(rows, columns, sort) : rows;
  const {
    paged: pagedRows,
    pagerProps,
    resetPage,
  } = useCardPager(sortedRows, LS.channelDiscoveryCardSize, isCards);
  // Changing what you're looking at starts over at the first page. NOT keyed on the row count:
  // subscribing drops a row from this tab, and being thrown to page 1 for it would punish the very
  // action you just took — a list that shrank past your page is handled by the hook's clamp.
  useEffect(() => {
    resetPage();
  }, [search, sort, resetPage]);

  return (
    <>
      {/* Intro + the table controls are fixed chrome; only the table rows scroll (sticky header). */}
      <PageToolbar>
        <div className="px-4 pt-3 max-w-[96rem] mx-auto flex items-start justify-between gap-4">
          <p className="text-xs text-muted leading-relaxed">{t("channels.discovery.intro")}</p>
          {/* Cards has no column headers, so a dropdown drives the sort + a pager (table uses its own). */}
          {layout === "cards" && (
            <div className="flex items-center gap-3 shrink-0">
              <Pager {...pagerProps} />
              <ColumnSortControl
                columns={columns}
                sort={sort}
                onChange={setSort}
                label={t("channels.sort.label")}
                defaultLabel={t("channels.sort.default")}
                ascLabel={t("channels.sort.asc")}
                descLabel={t("channels.sort.desc")}
              />
            </div>
          )}
        </div>
      </PageToolbar>
      <div className="px-4 pb-4 max-w-[96rem] mx-auto">
        {query.isError && !query.data ? (
          <StateMessage tone="error" onRetry={() => query.refetch()}>
            {t("common.loadError")}
          </StateMessage>
        ) : query.isLoading ? (
          <LoadingState label={t("channels.discovery.loading")} />
        ) : layout === "table" ? (
          <DataTable
            rows={rows}
            columns={columns}
            rowKey={(c) => c.id}
            persistKey={accountKey(LS.channelDiscoveryTable) ?? undefined}
            controlsPosition="both"
            controlsInBand
            controlsBandClassName="px-4 max-w-[96rem] mx-auto"
            sort={sort}
            onSortChange={setSort}
            emptyText={t("channels.discovery.empty")}
          />
        ) : (
          <ChannelLayoutGrid
            rows={pagedRows}
            columns={columns}
            rowKey={(c) => c.id}
            aboutKey="about"
            actionsKey="actions"
            emptyText={t("channels.discovery.empty")}
          />
        )}
      </div>
    </>
  );
}
