import { useState } from "react";
import { useTranslation } from "react-i18next";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
  Check,
  ChevronDown,
  ChevronLeft,
  Eye,
  EyeOff,
  GripVertical,
  Library,
  Pencil,
  RotateCcw,
  Share2,
  User,
  X,
} from "lucide-react";
import {
  closestCenter,
  DndContext,
  PointerSensor,
  useSensor,
  useSensors,
  type DragEndEvent,
} from "@dnd-kit/core";
import {
  arrayMove,
  SortableContext,
  useSortable,
  verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { api, type FeedFilters, type Tag } from "../lib/api";
import {
  DEFAULT_LAYOUT,
  type SidebarLayout,
  type WidgetId,
} from "../lib/sidebarLayout";
import { shareUrl } from "../lib/urlState";
import { notify } from "../lib/notifications";
import { useDebounced } from "../lib/useDebounced";
import TagManager from "./TagManager";
import SavedViewsWidget from "./SavedViewsWidget";
import CollapsedFilterRail from "./CollapsedFilterRail";

// 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" },
];

// Filter values owned by the sidebar (everything except the header search `q` and the
// `scope` mode, both preserved across a "Clear all").
const DEFAULT_SIDEBAR_FILTERS: Omit<FeedFilters, "q" | "scope"> = {
  tags: [],
  tagMode: "or",
  sort: "newest",
  includeNormal: true,
  includeShorts: false,
  includeLive: false,
  show: "unwatched",
};

function TagChip({
  tag,
  count,
  active,
  onClick,
}: {
  tag: Tag;
  count: number;
  active: boolean;
  onClick: () => void;
}) {
  const { t } = useTranslation();
  return (
    <button
      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({
  filters,
  setFilters,
  layout,
  setLayout,
  onFocusChannel,
  isDemo = false,
  collapsed,
  onToggleCollapse,
}: {
  filters: FeedFilters;
  setFilters: (f: FeedFilters) => void;
  layout: SidebarLayout;
  setLayout: (l: SidebarLayout) => void;
  onFocusChannel: (name: string) => void;
  isDemo?: boolean;
  // Full-height collapse (state owned by App, persisted to the user's preferences), mirroring
  // the left nav rail.
  collapsed: boolean;
  onToggleCollapse: () => void;
}) {
  const { t } = useTranslation();
  const tagsQuery = useQuery({ queryKey: ["tags"], queryFn: api.tags });
  const tags = tagsQuery.data ?? [];

  // Live per-tag channel counts for the current filter context (scope, channel, date,
  // search, watch state, other category's tags). Lets us show contextual counts and hide
  // chips that no longer match anything. Keyed on filters so it refetches as they change.
  // Debounce the search term (matching the feed) so typing doesn't refire facets per
  // keystroke, and keep the previous counts on screen during a refetch so chips don't flicker.
  const facetFilters: FeedFilters = { ...filters, q: useDebounced(filters.q, 300) };
  const facetsQuery = useQuery({
    queryKey: ["facets", facetFilters],
    queryFn: () => api.facets(facetFilters),
    staleTime: 30_000,
    placeholderData: keepPreviousData,
  });
  const facetsReady = !!facetsQuery.data;
  const facetCounts = facetsQuery.data?.counts ?? {};
  // Before facets load, fall back to the static global count so chips don't flash empty.
  const chipCount = (tag: Tag): number =>
    facetsReady ? facetCounts[String(tag.id)] ?? 0 : tag.channel_count;
  // Visible chips: hide zero-count ones once facets are in, but always keep selected ones
  // so an active filter can still be cleared. Sorted by count (largest first), then name —
  // so scanning top→bottom the smallest counts end up at the very bottom.
  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)
    );
  };

  // After a page refresh the channel name isn't in the URL (only the id is), so
  // filters.channelName is undefined and the chip would fall back to "This channel".
  // Resolve the title from the (cached) channel list keyed by id. Only fetch when a
  // channel filter is active but its name is missing.
  const needChannelName = !!filters.channelId && !filters.channelName;
  const channelsQuery = useQuery({
    queryKey: ["channels"],
    queryFn: api.channels,
    enabled: needChannelName,
    staleTime: 5 * 60_000,
  });
  const resolvedChannelName =
    filters.channelName ??
    channelsQuery.data?.find((c) => c.id === filters.channelId)?.title ??
    undefined;
  // Don't flash the misleading "This channel" fallback while the name is still resolving.
  const channelChipLabel =
    resolvedChannelName ??
    (needChannelName && channelsQuery.isLoading
      ? t("sidebar.loading")
      : t("sidebar.thisChannel"));
  const languages = tags.filter((t) => t.category === "language");
  const topics = tags.filter((t) => t.category === "topic");
  // The user's own (non-system) tags — those assigned to channels in the Channel manager.
  // Facets cover them too now (the "other" category), so they're contextual like the rest:
  // zero-count chips hide once facets load, counts reflect the current filter.
  const userTags = tags.filter((t) => !t.system);
  const [customDates, setCustomDates] = useState(false);
  const [tagManagerOpen, setTagManagerOpen] = useState(false);
  const [editing, setEditing] = useState(false);

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 4 } })
  );

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

  const dateActive = !!filters.maxAgeDays || !!filters.dateFrom || !!filters.dateTo;
  const contentChanged =
    !filters.includeNormal || filters.includeShorts || filters.includeLive;
  // Library (vs the default "my"/Mine) and a non-default provenance Source both narrow the feed,
  // so they count as active filters too (Source only applies in Library scope). The header search
  // `q` deliberately stays out — it has its own clear (✕) in the search box and isn't reset here.
  const scopeActive = filters.scope === "all";
  const sourceActive =
    filters.scope === "all" && !!filters.librarySource && filters.librarySource !== "organic";
  const activeCount =
    filters.tags.length +
    (filters.show !== "unwatched" ? 1 : 0) +
    // "relevance" is auto-applied while searching (no manual dropdown option), so it's a search
    // artifact, not a user-chosen sort filter — don't let typing a query inflate the count.
    (filters.sort !== "newest" && filters.sort !== "relevance" ? 1 : 0) +
    (contentChanged ? 1 : 0) +
    (filters.channelId ? 1 : 0) +
    (dateActive ? 1 : 0) +
    (scopeActive ? 1 : 0) +
    (sourceActive ? 1 : 0);
  const active = activeCount > 0;

  async function shareView() {
    try {
      await navigator.clipboard.writeText(shareUrl(filters));
      notify({ level: "success", message: t("sidebar.shareCopied") });
    } catch {
      notify({ level: "warning", message: t("sidebar.shareFailed") });
    }
  }

  function clearAll() {
    setCustomDates(false);
    // Reset every filter dimension to its default — including scope back to "my" (Mine) and the
    // provenance Source (dropped with the rest, so it falls back to "organic"). The search `q` is
    // intentionally preserved (it has its own ✕ in the search box).
    setFilters({ ...DEFAULT_SIDEBAR_FILTERS, q: filters.q, scope: "my" });
  }

  const available: Record<WidgetId, boolean> = {
    // Saved views are personal state; hide the widget for the shared demo account (the API
    // also rejects the writes via require_human).
    savedviews: !isDemo,
    date: true,
    language: languages.length > 0,
    topic: topics.length > 0,
    tags: userTags.length > 0,
  };

  function toggleCollapse(id: WidgetId) {
    setLayout({ ...layout, collapsed: { ...layout.collapsed, [id]: !layout.collapsed[id] } });
  }
  function toggleHidden(id: WidgetId) {
    setLayout({ ...layout, hidden: { ...layout.hidden, [id]: !layout.hidden[id] } });
  }
  function onDragEnd(e: DragEndEvent) {
    const { active: a, over } = e;
    if (!over || a.id === over.id) return;
    const oldIndex = layout.order.indexOf(a.id as WidgetId);
    const newIndex = layout.order.indexOf(over.id as WidgetId);
    if (oldIndex < 0 || newIndex < 0) return;
    setLayout({ ...layout, order: arrayMove(layout.order, oldIndex, newIndex) });
  }

  function widgetBody(id: WidgetId): React.ReactNode {
    switch (id) {
      case "savedviews":
        return <SavedViewsWidget filters={filters} onApply={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>
        );
      }
    }
  }

  const orderedAvailable = layout.order.filter((id) => available[id]);
  const renderedIds = editing
    ? orderedAvailable
    : orderedAvailable.filter((id) => !layout.hidden[id]);

  // Collapsed: a thin rail (mirroring the nav) — an expand control plus a filter icon that
  // carries the active-filter count so you can tell filters are on without opening it.
  if (collapsed) {
    return <CollapsedFilterRail activeCount={activeCount} onToggleCollapse={onToggleCollapse} />;
  }

  return (
    <aside className="hidden md:block w-64 shrink-0 border-r border-border bg-surface/40 overflow-y-auto p-4 space-y-3">
      {/* Scope: your own subscriptions (Mine) vs the shared Library — moved out of the top bar. */}
      <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>

      <div className="flex items-center justify-between">
        <div className="flex items-center gap-1.5 min-w-0">
          <button
            onClick={onToggleCollapse}
            title={t("sidebar.collapsePanel")}
            aria-label={t("sidebar.collapsePanel")}
            className="shrink-0 -ml-1 p-1 rounded-md text-muted hover:text-fg hover:bg-card transition"
          >
            <ChevronLeft className="w-4 h-4" />
          </button>
          <span className="text-sm font-semibold shrink-0">{t("sidebar.filters")}</span>
          {activeCount > 0 && (
            // Compact count pill (not "{n} active" text) so it never truncates or forces the
            // action buttons to wrap in a narrow / zoomed-in sidebar.
            <span
              title={t("sidebar.activeCount", { count: activeCount })}
              className="shrink-0 min-w-[18px] h-[18px] px-1.5 rounded-full bg-accent/15 text-accent text-[11px] font-semibold inline-flex items-center justify-center tabular-nums"
            >
              {activeCount}
            </span>
          )}
        </div>
        <div className="flex items-center gap-0.5 shrink-0">
          {!editing && (
            <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>
          )}
          {!editing && (
            <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>
          )}
          {editing && (
            <button
              onClick={() => setLayout({ ...DEFAULT_LAYOUT })}
              title={t("sidebar.resetDefaults")}
              className="p-1.5 rounded-lg text-muted hover:text-fg hover:bg-card transition"
            >
              <RotateCcw className="w-4 h-4" />
            </button>
          )}
          <button
            onClick={() => setEditing((e) => !e)}
            title={editing ? t("sidebar.done") : t("sidebar.customize")}
            className={`p-1.5 rounded-lg transition hover:bg-card ${
              editing ? "text-accent" : "text-muted hover:text-fg"
            }`}
          >
            {editing ? <Check className="w-4 h-4" /> : <Pencil className="w-4 h-4" />}
          </button>
        </div>
      </div>

      {editing && (
        <div className="text-[11px] text-muted -mt-1">{t("sidebar.editHint")}</div>
      )}

      {filters.channelId && !editing && (
        <div className="glass-card rounded-xl">
          <div className="px-3 pt-2 text-xs uppercase tracking-wide text-muted">
            {t("sidebar.channel")}
          </div>
          <div className="px-3 pb-3 pt-2">
            <button
              onClick={() =>
                setFilters({ ...filters, channelId: undefined, channelName: undefined })
              }
              className="w-full flex items-center justify-between gap-2 text-sm px-3 py-2 rounded-lg bg-accent text-accent-fg shadow-sm hover:opacity-90 transition"
            >
              <span className="truncate">{channelChipLabel}</span>
              <X className="w-4 h-4 shrink-0" />
            </button>
          </div>
        </div>
      )}

      <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
        <SortableContext items={renderedIds} strategy={verticalListSortingStrategy}>
          <div className="space-y-3">
            {renderedIds.map((id) => (
              <WidgetCard
                key={id}
                id={id}
                title={t("sidebar.widget." + id)}
                editing={editing}
                collapsed={!!layout.collapsed[id]}
                hidden={!!layout.hidden[id]}
                onToggleCollapse={() => toggleCollapse(id)}
                onToggleHidden={() => toggleHidden(id)}
              >
                {widgetBody(id)}
              </WidgetCard>
            ))}
          </div>
        </SortableContext>
      </DndContext>
      {tagManagerOpen && (
        <TagManager onClose={() => setTagManagerOpen(false)} onFocusChannel={onFocusChannel} />
      )}
    </aside>
  );
}

function WidgetCard({
  id,
  title,
  editing,
  collapsed,
  hidden,
  onToggleCollapse,
  onToggleHidden,
  children,
}: {
  id: WidgetId;
  title: string;
  editing: boolean;
  collapsed: boolean;
  hidden: boolean;
  onToggleCollapse: () => void;
  onToggleHidden: () => void;
  children: React.ReactNode;
}) {
  const { t } = useTranslation();
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
    id,
    disabled: !editing,
  });
  const style: React.CSSProperties = {
    transform: CSS.Transform.toString(transform),
    transition,
  };
  const showBody = !editing && !collapsed;

  return (
    <div
      ref={setNodeRef}
      style={style}
      className={`glass-card rounded-xl ${
        isDragging ? "relative z-10 shadow-2xl ring-1 ring-accent" : ""
      } ${hidden && editing ? "opacity-50" : ""}`}
    >
      <div className="flex items-center gap-1.5 px-3 py-2">
        {editing && (
          <button
            {...attributes}
            {...listeners}
            title={t("sidebar.dragToReorder")}
            className="-ml-1 cursor-grab active:cursor-grabbing text-muted hover:text-fg touch-none"
          >
            <GripVertical className="w-4 h-4" />
          </button>
        )}
        <div className="flex-1 select-none text-xs uppercase tracking-wide text-muted">
          {title}
        </div>
        {editing ? (
          <button
            onClick={onToggleHidden}
            title={hidden ? t("sidebar.showWidget") : t("sidebar.hideWidget")}
            className="text-muted hover:text-fg"
          >
            {hidden ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
          </button>
        ) : (
          <button
            onClick={onToggleCollapse}
            title={collapsed ? t("sidebar.expand") : t("sidebar.collapse")}
            className="text-muted hover:text-fg"
          >
            <ChevronDown
              className={`w-4 h-4 transition-transform ${collapsed ? "-rotate-90" : ""}`}
            />
          </button>
        )}
      </div>
      {showBody && <div className="px-3 pb-3">{children}</div>}
    </div>
  );
}

