import { useTranslation } from "react-i18next";
import { useQuery } from "@tanstack/react-query";
import { qk } from "./queryKeys";
import { api, type FeedFilters } from "./api";
import { parseSort } from "./feedSort";

/** One narrowing the user has applied, with the label to show and the way to undo just this one. */
export interface ActiveFilter {
  key: string;
  label: string;
  remove: () => void;
}

/** What the feed looks like with nothing applied — the baseline every "active" check reads against. */
const DEFAULT_SIDEBAR_FILTERS: Omit<FeedFilters, "q" | "scope"> = {
  tags: [],
  tagMode: "or",
  channelIds: [],
  sort: "newest",
  includeNormal: true,
  includeShorts: false,
  includeLive: false,
  show: "unwatched",
};

/**
 * The reset behind every "Clear all" (the panel's and the chip row's), so they can't drift apart.
 * The search term survives — clearing filters isn't the same as abandoning the search — and the
 * view returns to your own subscriptions.
 */
export function clearedFilters(current: FeedFilters): FeedFilters {
  return { ...DEFAULT_SIDEBAR_FILTERS, q: current.q, scope: "my" };
}

/**
 * The single source of truth for "what is currently narrowing the feed" — it backs BOTH the side
 * panel's active-count badge and the removable chip row above the feed, so the two can never
 * disagree. Tags/channels come from the shared react-query caches, so callers need not thread them.
 *
 * A dimension counts as active when it differs from the feed's default: show=unwatched,
 * sort=newest (relevance is search's implicit default, so it doesn't count either), normal-only
 * content, scope=my, source=organic, and no tags/channel/date.
 */
export function useActiveFilters(
  filters: FeedFilters,
  setFilters: (f: FeedFilters) => void,
): ActiveFilter[] {
  const { t } = useTranslation();
  const tagsQuery = useQuery({ queryKey: qk.tags(), queryFn: api.tags });
  const channelsQuery = useQuery({
    queryKey: qk.channels(),
    queryFn: api.channels,
    enabled: filters.channelIds.length > 0,
    staleTime: 5 * 60_000,
  });

  const out: ActiveFilter[] = [];

  // Tags: one chip each — removing one shouldn't drop the rest.
  for (const id of filters.tags) {
    const tag = (tagsQuery.data ?? []).find((x) => x.id === id);
    out.push({
      key: `tag:${id}`,
      label: tag?.name ?? String(id),
      remove: () => setFilters({ ...filters, tags: filters.tags.filter((x) => x !== id) }),
    });
  }

  if (filters.show !== "unwatched") {
    out.push({
      key: "show",
      label: t("sidebar.show." + filters.show),
      remove: () => setFilters({ ...filters, show: "unwatched" }),
    });
  }

  if (filters.sort !== "newest" && filters.sort !== "relevance") {
    out.push({
      key: "sort",
      label: t("feed.sortKey." + parseSort(filters.sort).key),
      remove: () => setFilters({ ...filters, sort: "newest", seed: undefined }),
    });
  }

  if (!filters.includeNormal || filters.includeShorts || filters.includeLive) {
    const on = [
      filters.includeNormal && t("sidebar.content.normal"),
      filters.includeShorts && t("sidebar.content.shorts"),
      filters.includeLive && t("sidebar.content.live"),
    ].filter(Boolean);
    out.push({
      key: "content",
      // Nothing selected is a legitimate (empty-feed) state — say so rather than render a bare label.
      label: on.length ? on.join(" + ") : t("feed.chips.contentNone"),
      remove: () =>
        setFilters({ ...filters, includeNormal: true, includeShorts: false, includeLive: false }),
    });
  }

  // One chip per channel — they OR together, so dropping one shouldn't drop the rest (same shape
  // as tags).
  for (const id of filters.channelIds) {
    out.push({
      key: `channel:${id}`,
      // Fall back to the id, not a generic label: several chips reading "This channel" while
      // the name cache loads would be indistinguishable, and each ✕ drops a different one.
      label: channelsQuery.data?.find((c) => c.id === id)?.title ?? id,
      remove: () =>
        setFilters({ ...filters, channelIds: filters.channelIds.filter((x) => x !== id) }),
    });
  }

  if (filters.maxAgeDays || filters.dateFrom || filters.dateTo) {
    const label = filters.maxAgeDays
      ? t("feed.chips.maxAge", { count: filters.maxAgeDays })
      : [filters.dateFrom, filters.dateTo].filter(Boolean).join(" – ");
    out.push({
      key: "date",
      label,
      remove: () =>
        setFilters({ ...filters, maxAgeDays: undefined, dateFrom: undefined, dateTo: undefined }),
    });
  }

  if (filters.scope === "all") {
    out.push({
      key: "scope",
      label: t("header.scope.all"),
      // Source is a library-only concept, so it goes back to the default alongside the scope —
      // otherwise it would linger as a chip with nothing to apply to.
      remove: () => setFilters({ ...filters, scope: "my", librarySource: "organic" }),
    });
  }

  if (filters.scope === "all" && filters.librarySource && filters.librarySource !== "organic") {
    // The wire values and the label keys don't line up 1:1 ("search" reads as "only").
    const LABEL = { all: "all", search: "only", plex: "plex" } as const;
    out.push({
      key: "source",
      label: t("feed.source." + LABEL[filters.librarySource]),
      remove: () => setFilters({ ...filters, librarySource: "organic" }),
    });
  }

  return out;
}
