import { type ReactNode, useState } from "react";
import { useTranslation } from "react-i18next";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { qk } from "../lib/queryKeys";
import { Library, Pencil, Share2, SlidersHorizontal, User } from "lucide-react";
import { api, type FeedFilters, type Tag } from "../lib/api";
import { defaultLayout, type PanelLayout } from "../lib/panelLayout";
import { shareUrl } from "../lib/urlState";
import { copyText } from "../lib/clipboard";
import { notify } from "../lib/notifications";
import { useDebounced } from "../lib/useDebounced";
import { clearedFilters, useActiveFilters } from "../lib/useActiveFilters";
import TagManager from "./TagManager";
import SavedViewsWidget from "./SavedViewsWidget";
import SidePanel from "./SidePanel";
import PanelGroups, { type PanelItem } from "./PanelGroups";
import ChannelPicker from "./ChannelPicker";
import { useFeedFilters, useFeedFiltersActions } from "./FeedFiltersProvider";
import { useChannelsActions } from "./ChannelsProvider";
import { useMe } from "../lib/useMe";

// Filter ids; display labels resolved at render time via t("sidebar.sort.<id>") etc.
const DATE_PRESETS: { days: number; key: string }[] = [
  { days: 1, key: "24h" },
  { days: 7, key: "1week" },
  { days: 30, key: "1month" },
  { days: 180, key: "6months" },
  { days: 365, key: "1year" },
];

function TagChip({
  tag,
  count,
  active,
  onClick,
}: {
  tag: Tag;
  count: number;
  active: boolean;
  onClick: () => void;
}) {
  const { t } = useTranslation();
  return (
    <button
      data-testid="filter-tag-chip"
      data-tag={tag.name}
      onClick={onClick}
      title={t("sidebar.channelCount", { count })}
      className={`inline-flex items-center gap-1.5 text-xs px-2.5 py-1 rounded-full border shadow-sm hover:shadow active:translate-y-px transition ${
        active
          ? "bg-accent text-accent-fg border-accent"
          : "bg-card border-border text-fg hover:border-accent"
      }`}
    >
      <span>{tag.name}</span>
      <span
        className={`tabular-nums text-[10px] leading-none px-1 py-0.5 rounded-full ${
          active ? "bg-accent-fg/20 text-accent-fg" : "bg-muted/15 text-muted"
        }`}
      >
        {count}
      </span>
    </button>
  );
}

export default function Sidebar({
  layout,
  setLayout,
  collapsed,
  onToggleCollapse,
}: {
  layout: PanelLayout;
  setLayout: (l: PanelLayout) => void;
  collapsed: boolean;
  onToggleCollapse: () => void;
}) {
  const { t } = useTranslation();
  const isDemo = useMe().is_demo;
  const filters = useFeedFilters();
  const { setFilters } = useFeedFiltersActions();
  // "Focus this channel in the manager" (from the tag manager here) lives in ChannelsProvider.
  const { focusChannel: onFocusChannel } = useChannelsActions();
  const tagsQuery = useQuery({ queryKey: qk.tags(), queryFn: api.tags });
  const tags = tagsQuery.data ?? [];

  // Live per-tag channel counts for the current filter context. Debounce the search term so
  // typing doesn't refire facets per keystroke, and keep previous counts during a refetch.
  const facetFilters: FeedFilters = { ...filters, q: useDebounced(filters.q, 300) };
  const facetsQuery = useQuery({
    queryKey: qk.facets(facetFilters),
    queryFn: () => api.facets(facetFilters),
    staleTime: 30_000,
    placeholderData: keepPreviousData,
  });
  const facetsReady = !!facetsQuery.data;
  const facetCounts = facetsQuery.data?.counts ?? {};
  const chipCount = (tag: Tag): number =>
    facetsReady ? (facetCounts[String(tag.id)] ?? 0) : tag.channel_count;
  const visibleChips = (list: Tag[]): Tag[] => {
    const shown = facetsReady
      ? list.filter((tg) => chipCount(tg) > 0 || filters.tags.includes(tg.id))
      : list;
    return [...shown].sort((a, b) => chipCount(b) - chipCount(a) || a.name.localeCompare(b.name));
  };

  const languages = tags.filter((t) => t.category === "language");
  const topics = tags.filter((t) => t.category === "topic");
  const userTags = tags.filter((t) => !t.system);
  const [customDates, setCustomDates] = useState(false);
  const [tagManagerOpen, setTagManagerOpen] = useState(false);
  const [editing, setEditing] = useState(false);

  function toggleTag(id: number) {
    const has = filters.tags.includes(id);
    setFilters({
      ...filters,
      tags: has ? filters.tags.filter((t) => t !== id) : [...filters.tags, id],
    });
  }

  // The badge counts exactly what the chip row above the feed shows — same list, so they can't
  // disagree about what's applied.
  const activeCount = useActiveFilters(filters, setFilters).length;
  const active = activeCount > 0;

  async function shareView() {
    if (await copyText(shareUrl(filters))) {
      notify({ level: "success", message: t("sidebar.shareCopied") });
    } else {
      notify({ level: "warning", message: t("sidebar.shareFailed") });
    }
  }

  function clearAll() {
    setCustomDates(false);
    setFilters(clearedFilters(filters));
  }

  const available: Record<string, boolean> = {
    savedviews: !isDemo,
    date: true,
    channel: true,
    language: languages.length > 0,
    topic: topics.length > 0,
    tags: userTags.length > 0,
  };

  function widgetBody(id: string): ReactNode {
    switch (id) {
      case "savedviews":
        return <SavedViewsWidget filters={filters} onApply={setFilters} />;
      case "channel":
        return <ChannelPicker filters={filters} setFilters={setFilters} />;
      case "date":
        return (
          <>
            <div className="flex flex-wrap gap-1.5">
              {DATE_PRESETS.map((p) => {
                const activePreset = filters.maxAgeDays === p.days && !customDates;
                return (
                  <button
                    key={p.days}
                    onClick={() =>
                      setFilters({
                        ...filters,
                        maxAgeDays: activePreset ? undefined : p.days,
                        dateFrom: undefined,
                        dateTo: undefined,
                      })
                    }
                    className={`text-xs px-2.5 py-1 rounded-full border shadow-sm active:translate-y-px transition ${
                      activePreset
                        ? "bg-accent text-accent-fg border-accent"
                        : "bg-card border-border hover:border-accent"
                    }`}
                  >
                    {t("sidebar.datePreset." + p.key)}
                  </button>
                );
              })}
              <button
                onClick={() => {
                  const next = !customDates;
                  setCustomDates(next);
                  if (next) setFilters({ ...filters, maxAgeDays: undefined });
                }}
                className={`text-xs px-2.5 py-1 rounded-full border shadow-sm active:translate-y-px transition ${
                  customDates || filters.dateFrom || filters.dateTo
                    ? "bg-accent text-accent-fg border-accent"
                    : "bg-card border-border hover:border-accent"
                }`}
              >
                {t("sidebar.custom")}
              </button>
            </div>

            {(customDates || filters.dateFrom || filters.dateTo) && (
              <div className="flex flex-col gap-1.5 mt-2">
                <label className="flex items-center justify-between gap-2 text-xs">
                  <span className="text-muted">{t("sidebar.from")}</span>
                  <input
                    type="date"
                    value={filters.dateFrom ?? ""}
                    onChange={(e) =>
                      setFilters({
                        ...filters,
                        dateFrom: e.target.value || undefined,
                        maxAgeDays: undefined,
                      })
                    }
                    className="bg-card border border-border rounded-md px-2 py-1 text-xs outline-none focus:border-accent"
                  />
                </label>
                <label className="flex items-center justify-between gap-2 text-xs">
                  <span className="text-muted">{t("sidebar.to")}</span>
                  <input
                    type="date"
                    value={filters.dateTo ?? ""}
                    onChange={(e) =>
                      setFilters({
                        ...filters,
                        dateTo: e.target.value || undefined,
                        maxAgeDays: undefined,
                      })
                    }
                    className="bg-card border border-border rounded-md px-2 py-1 text-xs outline-none focus:border-accent"
                  />
                </label>
                {(filters.dateFrom || filters.dateTo) && (
                  <button
                    onClick={() =>
                      setFilters({ ...filters, dateFrom: undefined, dateTo: undefined })
                    }
                    className="text-[11px] text-muted hover:text-accent self-end"
                  >
                    {t("sidebar.clearDates")}
                  </button>
                )}
              </div>
            )}
          </>
        );
      case "language": {
        const chips = visibleChips(languages);
        return (
          <div className="flex flex-wrap gap-1.5">
            {chips.map((tg) => (
              <TagChip
                key={tg.id}
                tag={tg}
                count={chipCount(tg)}
                active={filters.tags.includes(tg.id)}
                onClick={() => toggleTag(tg.id)}
              />
            ))}
            {facetsReady && chips.length === 0 && (
              <span className="text-[11px] text-muted">{t("sidebar.noMatchingTags")}</span>
            )}
          </div>
        );
      }
      case "topic": {
        const chips = visibleChips(topics);
        return (
          <>
            <div
              className="flex items-center justify-between mb-1.5"
              title={t("sidebar.tagModeTooltip")}
            >
              <span className="text-[10px] uppercase tracking-wide text-muted">
                {t("sidebar.match")}
              </span>
              <div
                className="flex items-center rounded-full border border-border bg-card p-0.5 text-[10px]"
                role="group"
                aria-label={t("sidebar.match")}
              >
                {(["or", "and"] as const).map((m) => (
                  <button
                    key={m}
                    onClick={() => setFilters({ ...filters, tagMode: m })}
                    aria-pressed={filters.tagMode === m}
                    className={`px-2 py-0.5 rounded-full transition ${
                      filters.tagMode === m
                        ? "bg-accent text-accent-fg"
                        : "text-muted hover:text-fg"
                    }`}
                  >
                    {m === "or" ? t("sidebar.any") : t("sidebar.all")}
                  </button>
                ))}
              </div>
            </div>
            <div className="flex flex-wrap gap-1.5">
              {chips.map((tg) => (
                <TagChip
                  key={tg.id}
                  tag={tg}
                  count={chipCount(tg)}
                  active={filters.tags.includes(tg.id)}
                  onClick={() => toggleTag(tg.id)}
                />
              ))}
              {facetsReady && chips.length === 0 && (
                <span className="text-[11px] text-muted">{t("sidebar.noMatchingTags")}</span>
              )}
            </div>
          </>
        );
      }
      case "tags": {
        const tagChips = visibleChips(userTags);
        return (
          <div className="flex flex-wrap items-center gap-1.5">
            {tagChips.map((tg) => (
              <TagChip
                key={tg.id}
                tag={tg}
                count={chipCount(tg)}
                active={filters.tags.includes(tg.id)}
                onClick={() => toggleTag(tg.id)}
              />
            ))}
            {facetsReady && tagChips.length === 0 && (
              <span className="text-[11px] text-muted">{t("sidebar.noMatchingTags")}</span>
            )}
            <button
              onClick={() => setTagManagerOpen(true)}
              title={t("sidebar.manageTags")}
              className="inline-flex items-center gap-1 text-xs px-2 py-1 rounded-full border border-dashed border-border text-muted hover:text-accent hover:border-accent transition"
            >
              <Pencil className="w-3 h-3" />
              {t("sidebar.manageTags")}
            </button>
          </div>
        );
      }
      default:
        return null;
    }
  }

  // Group ids come from the shared default order (panelLayout) so there's one source of truth;
  // the actual render order is the user's saved layout (PanelGroups orders by it).
  const items: PanelItem[] = defaultLayout("feed")
    .order.filter((id) => available[id])
    .map((id) => ({ id, title: t("sidebar.widget." + id), body: widgetBody(id) }));

  const actions = (
    <>
      <button
        onClick={clearAll}
        disabled={!active}
        className="text-xs text-muted enabled:hover:text-accent disabled:opacity-40 transition px-1 whitespace-nowrap"
      >
        {t("sidebar.clearAll")}
      </button>
      <button
        onClick={shareView}
        title={t("sidebar.shareView")}
        aria-label={t("sidebar.shareView")}
        className="p-1.5 rounded-lg text-muted hover:text-fg hover:bg-card transition"
      >
        <Share2 className="w-4 h-4" />
      </button>
    </>
  );

  return (
    <SidePanel
      title={t("sidebar.filters")}
      icon={<SlidersHorizontal className="w-4 h-4" />}
      count={activeCount}
      collapsed={collapsed}
      onToggleCollapse={onToggleCollapse}
      editing={editing}
      onToggleEditing={() => setEditing((e) => !e)}
      onReset={() => setLayout(defaultLayout("feed"))}
      editHint={t("sidebar.editHint")}
      actions={actions}
    >
      {/* Scope: your own subscriptions (Mine) vs the shared Library. */}
      <div
        className="flex items-center rounded-full border border-border bg-card p-0.5 text-xs"
        role="group"
        aria-label={t("header.scope.label")}
      >
        {(["my", "all"] as const).map((s) => (
          <button
            key={s}
            onClick={() => setFilters({ ...filters, scope: s })}
            title={t("header.scope." + s + "Tip")}
            aria-pressed={filters.scope === s}
            className={`flex-1 inline-flex items-center justify-center gap-1 px-2.5 py-1 rounded-full transition ${
              filters.scope === s ? "bg-accent text-accent-fg" : "text-muted hover:text-fg"
            }`}
          >
            {s === "my" ? <User className="w-3.5 h-3.5" /> : <Library className="w-3.5 h-3.5" />}
            <span>{t("header.scope." + s)}</span>
          </button>
        ))}
      </div>

      <PanelGroups items={items} layout={layout} setLayout={setLayout} editing={editing} />

      {tagManagerOpen && (
        <TagManager onClose={() => setTagManagerOpen(false)} onFocusChannel={onFocusChannel} />
      )}
    </SidePanel>
  );
}
