import { useState, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { ChevronLeft, Film, Layers, ListMusic, Plus, Tv2, X } from "lucide-react";
import CollapsedFilterRail from "./CollapsedFilterRail";
import { api, EMPTY_PLEX_FILTERS, plexFilterCount, type PlexFilters } from "../lib/api";
import { useDebounced } from "../lib/useDebounced";

// The Plex module's left filter column (mirrors the feed Sidebar's shell). Movie libraries get the
// full metadata filter set (genre / rating / year / duration / added / content rating + the
// director/actor/studio chips set by clicking the info page); shows keep library + sort only. State
// is owned by App (per-account persisted). Facets (available genres/ratings + bounds) come from the
// backend so the sidebar only offers what the library actually contains.

type Props = {
  scope: string; // movie | show | both (unified cross-library scope)
  setScope: (v: string) => void;
  show: string;
  setShow: (v: string) => void;
  sort: string;
  setSort: (v: string) => void;
  filters: PlexFilters;
  setFilters: (f: PlexFilters) => void;
  onOpenPlaylist: (id: number) => void;
  collapsed: boolean;
  onToggleCollapse: () => void;
};

const SHOW_OPTS = ["all", "unwatched", "in_progress", "watched"] as const;
const MOVIE_SORTS = ["added", "release", "year", "rating", "duration", "title"] as const;
// Shows carry the same metadata now (0052) — same sorts minus duration (a show isn't one runtime).
const SHOW_SORTS = ["added", "release", "year", "rating", "title"] as const;
const RATING_STEPS = [5, 6, 7, 8, 9];
const ADDED_OPTS = ["24h", "1w", "1m", "6m", "1y"] as const;
// Duration buckets → [minSeconds|null, maxSeconds|null].
const DURATION_BUCKETS: { key: string; min: number | null; max: number | null }[] = [
  { key: "short", min: null, max: 90 * 60 },
  { key: "mid", min: 90 * 60, max: 120 * 60 },
  { key: "long", min: 120 * 60, max: null },
];

export default function PlexSidebar({
  scope,
  setScope,
  show,
  setShow,
  sort,
  setSort,
  filters,
  setFilters,
  onOpenPlaylist,
  collapsed,
  onToggleCollapse,
}: Props) {
  const { t } = useTranslation();
  const playlistsQ = useQuery({ queryKey: ["plex-playlists"], queryFn: () => api.plexPlaylists() });
  const [newPlaylist, setNewPlaylist] = useState("");
  const qc = useQueryClient();
  async function createPlaylist() {
    const title = newPlaylist.trim();
    if (!title) return;
    const pl = await api.plexCreatePlaylist(title);
    setNewPlaylist("");
    qc.invalidateQueries({ queryKey: ["plex-playlists"] });
    onOpenPlaylist(pl.id);
  }
  // Facet values (genres/ratings/bounds) for the current scope, ADAPTED to the active filters
  // (faceted search) — so picking one filter narrows what the others offer. Refetches on any change.
  // Key only on the fields plexFacets actually sends — sortDir/collectionTitle don't reach /facets,
  // so keying on the whole `filters` object would re-run the heavy facet computation on every
  // sort-direction toggle (or chip-title change) for an identical request.
  const { sortDir: _sd, collectionTitle: _ct, ...facetKey } = filters;
  const facetsQ = useQuery({
    queryKey: ["plex-facets", scope, show, facetKey],
    queryFn: () => api.plexFacets(scope, filters, show),
    enabled: !!scope,
    placeholderData: (prev) => prev, // keep the old chips visible during the refetch (no flicker)
  });
  const facets = facetsQ.data;

  // Collections picker (searchable; library-agnostic UNION across all enabled libraries, since the
  // unified scope has no single library). Only fetched while no collection is active (else the chip
  // shows instead). Applying one sets the collection filter; the actual filtering already flows through
  // /library + /facets by rating_key.
  const [collSearch, setCollSearch] = useState("");
  const collSearchDeb = useDebounced(collSearch.trim(), 300);
  const collectionsQ = useQuery({
    // "union" disambiguates from the editor's per-library ["plex-collections", <plex_key>] key (a search
    // term equal to a plex_key would otherwise collide); the shared prefix keeps editor invalidation working.
    queryKey: ["plex-collections", "union", collSearchDeb],
    queryFn: () => api.plexCollections(undefined, collSearchDeb || undefined),
    enabled: !filters.collection,
  });
  const collections = collectionsQ.data?.collections ?? [];

  const fCount = plexFilterCount(filters);
  const activeCount = fCount + (show !== "all" ? 1 : 0) + (sort !== "title" ? 1 : 0);
  const anyActive = activeCount > 0;

  const patch = (p: Partial<PlexFilters>) => setFilters({ ...filters, ...p });
  const toggleIn = (arr: string[], v: string) =>
    arr.includes(v) ? arr.filter((x) => x !== v) : [...arr, v];
  const clearAll = () => {
    setFilters(EMPTY_PLEX_FILTERS);
    setShow("all");
    setSort("title");
  };

  // Movie-only scope offers the duration sort; mixed/show scopes drop it (a show has no runtime).
  // Ordered alphabetically by their localized label.
  const sorts = [...(scope === "movie" ? MOVIE_SORTS : SHOW_SORTS)].sort((a, b) =>
    t(`plex.sort.${a}`).localeCompare(t(`plex.sort.${b}`)),
  );
  const durBucketKey = DURATION_BUCKETS.find(
    (b) => (filters.durationMin ?? null) === b.min && (filters.durationMax ?? null) === b.max,
  )?.key;

  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-4">
      {/* Collapse control + active-filter count — mirrors the feed Sidebar so the Plex filter panel
          can be collapsed from the Plex page too (the collapsed rail + shared state already existed). */}
      <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 && (
          <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>

      {/* Scope — one unified library, filtered to movies, shows, or both. */}
      <Section label={t("plex.filter.scope")}>
        <div className="flex gap-1">
          {(["both", "movie", "show"] as const).map((s) => (
            <button
              key={s}
              onClick={() => setScope(s)}
              className={`inline-flex flex-1 items-center justify-center gap-1.5 px-2 py-1.5 rounded-lg text-sm transition ${
                s === scope ? "bg-accent text-accent-fg" : "glass-card glass-hover"
              }`}
            >
              {s === "movie" ? <Film className="w-4 h-4" /> : s === "show" ? <Tv2 className="w-4 h-4" /> : <Layers className="w-4 h-4" />}
              <span className="truncate">{t(`plex.filter.scopeOpt.${s}`)}</span>
            </button>
          ))}
        </div>
      </Section>

      {/* Playlists — per-user, Siftlode-native ordered watch-lists. Near the top: it's navigation, not
          a filter. Available in any library. */}
      <Section label={t("plex.playlist.section")}>
        <div className="mb-1.5 flex gap-1.5">
          <input
            value={newPlaylist}
            onChange={(e) => setNewPlaylist(e.target.value)}
            onKeyDown={(e) => e.key === "Enter" && createPlaylist()}
            placeholder={t("plex.playlist.newPlaceholder")}
            className="glass-card min-w-0 flex-1 rounded-lg px-2 py-1 text-xs outline-none focus:border-accent"
          />
          <button
            onClick={createPlaylist}
            disabled={!newPlaylist.trim()}
            title={t("plex.playlist.create")}
            className="glass-card glass-hover shrink-0 rounded-lg p-1.5 disabled:opacity-50"
          >
            <Plus className="h-3.5 w-3.5" />
          </button>
        </div>
        {(playlistsQ.data?.playlists ?? []).length === 0 ? (
          <p className="text-xs text-muted">{t("plex.playlist.none")}</p>
        ) : (
          <div className="space-y-0.5">
            {playlistsQ.data!.playlists.map((p) => (
              <button
                key={p.id}
                onClick={() => onOpenPlaylist(p.id)}
                className="glass-hover flex w-full items-center gap-2 rounded px-1.5 py-1 text-left text-sm"
              >
                <ListMusic className="h-3.5 w-3.5 shrink-0 text-muted" />
                <span className="min-w-0 flex-1 truncate">{p.title}</span>
                <span className="shrink-0 text-xs text-muted">{p.item_count}</span>
              </button>
            ))}
          </div>
        )}
      </Section>

      {anyActive && (
        <button
          onClick={clearAll}
          className="w-full inline-flex items-center justify-center gap-1.5 rounded-lg border border-border px-2.5 py-1.5 text-xs text-muted hover:border-accent hover:text-accent transition"
        >
          <X className="w-3.5 h-3.5" />
          {t("plex.filter.clearAll")} ({activeCount})
        </button>
      )}

      {/* Watch state — movies per-title, shows aggregated across their episodes (0052) */}
      <Section label={t("plex.filter.show")}>
        <ChipRow>
          {SHOW_OPTS.map((s) => (
            <Chip key={s} active={show === s} onClick={() => setShow(s)}>
              {t(`plex.filter.showOpt.${s}`)}
            </Chip>
          ))}
        </ChipRow>
      </Section>

      {/* Sort + direction */}
      <Section label={t("plex.filter.sort")}>
        <ChipRow>
          {sorts.map((s) => (
            <Chip key={s} active={sort === s} onClick={() => setSort(s)}>
              {t(`plex.sort.${s}`)}
            </Chip>
          ))}
        </ChipRow>
        <div className="mt-1.5 flex gap-1">
          {(["asc", "desc"] as const).map((d) => (
            <Chip key={d} active={(filters.sortDir ?? "asc") === d} onClick={() => patch({ sortDir: d })}>
              {t(`plex.filter.dir.${d}`)}
            </Chip>
          ))}
        </div>
      </Section>

      {/* Metadata filters — movie AND show libraries (0052); the duration bucket is movie-only. */}
      {(
        <>
          {/* Active people / studios — set by clicking the info page (stackable). */}
          {filters.directors.length + filters.actors.length + filters.studios.length > 0 && (
            <Section label={t("plex.filter.active")}>
              <div className="flex flex-wrap gap-1.5">
                {filters.directors.map((d) => (
                  <RemovableChip
                    key={`d:${d}`}
                    label={d}
                    onRemove={() => patch({ directors: filters.directors.filter((x) => x !== d) })}
                  />
                ))}
                {filters.actors.map((a) => (
                  <RemovableChip
                    key={`a:${a}`}
                    label={a}
                    onRemove={() => patch({ actors: filters.actors.filter((x) => x !== a) })}
                  />
                ))}
                {filters.studios.map((s) => (
                  <RemovableChip
                    key={`s:${s}`}
                    label={s}
                    onRemove={() => patch({ studios: filters.studios.filter((x) => x !== s) })}
                  />
                ))}
              </div>
            </Section>
          )}

          {/* Collection — the active one shows as a chip; otherwise a searchable picker over the union
              of all enabled libraries' collections (also settable from an item info page's "Browse
              collection"). */}
          <Section label={t("plex.filter.collection")}>
            {filters.collection ? (
              <div className="flex flex-wrap gap-1.5">
                <RemovableChip
                  label={filters.collectionTitle || filters.collection}
                  onRemove={() => patch({ collection: null, collectionTitle: null })}
                />
              </div>
            ) : (
              <div>
                <input
                  value={collSearch}
                  onChange={(e) => setCollSearch(e.target.value)}
                  placeholder={t("plex.filter.collectionSearch")}
                  className="mb-1.5 w-full rounded-lg border border-border bg-card px-2 py-1 text-sm focus:border-accent focus:outline-none"
                />
                <div className="flex max-h-56 flex-col gap-0.5 overflow-y-auto">
                  {collections.map((c) => (
                    <button
                      key={c.id}
                      onClick={() => patch({ collection: c.id, collectionTitle: c.title })}
                      className="glass-hover flex items-center gap-2 rounded-lg px-2 py-1 text-left text-sm"
                    >
                      <Layers className="w-3.5 h-3.5 shrink-0 text-muted" />
                      <span className="flex-1 truncate">{c.title}</span>
                      <span className="text-xs text-muted">{c.child_count}</span>
                    </button>
                  ))}
                  {collections.length === 0 && (
                    <span className="px-2 py-1 text-xs text-muted">{t("plex.filter.collectionEmpty")}</span>
                  )}
                </div>
              </div>
            )}
          </Section>

          {/* IMDb rating min */}
          <Section label={t("plex.filter.rating")}>
            <ChipRow>
              <Chip active={filters.ratingMin == null} onClick={() => patch({ ratingMin: null })}>
                {t("plex.filter.any")}
              </Chip>
              {RATING_STEPS.map((r) => (
                <Chip key={r} active={filters.ratingMin === r} onClick={() => patch({ ratingMin: r })}>
                  {r}+
                </Chip>
              ))}
            </ChipRow>
          </Section>

          {/* Genres (from facets) + Any/All when more than one is picked */}
          {facets && facets.genres.length > 0 && (
            <Section label={t("plex.filter.genre")}>
              {filters.genres.length > 1 && (
                <div className="mb-1.5 inline-flex overflow-hidden rounded-lg border border-border text-[11px]">
                  {(["any", "all"] as const).map((m) => (
                    <button
                      key={m}
                      onClick={() => patch({ genreMode: m })}
                      className={`px-2 py-0.5 transition ${
                        (filters.genreMode ?? "any") === m
                          ? "bg-accent text-accent-fg"
                          : "text-muted hover:bg-surface"
                      }`}
                    >
                      {t(`plex.filter.match.${m}`)}
                    </button>
                  ))}
                </div>
              )}
              <ChipRow>
                {facets.genres.map((g) => (
                  <Chip
                    key={g.value}
                    active={filters.genres.includes(g.value)}
                    onClick={() => patch({ genres: toggleIn(filters.genres, g.value) })}
                  >
                    {g.value}
                  </Chip>
                ))}
              </ChipRow>
            </Section>
          )}

          {/* Year range */}
          <Section label={t("plex.filter.year")}>
            <div className="flex items-center gap-2">
              <NumberInput
                value={filters.yearMin ?? null}
                placeholder={facets?.year_min ?? undefined}
                onChange={(v) => patch({ yearMin: v })}
              />
              <span className="text-muted text-xs">–</span>
              <NumberInput
                value={filters.yearMax ?? null}
                placeholder={facets?.year_max ?? undefined}
                onChange={(v) => patch({ yearMax: v })}
              />
            </div>
          </Section>

          {/* Duration buckets — movie-only (a show has no single runtime) */}
          {scope === "movie" && (
            <Section label={t("plex.filter.duration")}>
              <ChipRow>
                <Chip
                  active={!durBucketKey && filters.durationMin == null && filters.durationMax == null}
                  onClick={() => patch({ durationMin: null, durationMax: null })}
                >
                  {t("plex.filter.any")}
                </Chip>
                {DURATION_BUCKETS.map((b) => (
                  <Chip
                    key={b.key}
                    active={durBucketKey === b.key}
                    onClick={() => patch({ durationMin: b.min, durationMax: b.max })}
                  >
                    {t(`plex.filter.durationOpt.${b.key}`)}
                  </Chip>
                ))}
              </ChipRow>
            </Section>
          )}

          {/* Added to Plex */}
          <Section label={t("plex.filter.added")}>
            <ChipRow>
              <Chip active={!filters.addedWithin} onClick={() => patch({ addedWithin: "" })}>
                {t("plex.filter.any")}
              </Chip>
              {ADDED_OPTS.map((a) => (
                <Chip key={a} active={filters.addedWithin === a} onClick={() => patch({ addedWithin: a })}>
                  {t(`plex.filter.addedOpt.${a}`)}
                </Chip>
              ))}
            </ChipRow>
          </Section>

          {/* Content rating (from facets) */}
          {facets && facets.content_ratings.length > 0 && (
            <Section label={t("plex.filter.contentRating")}>
              <ChipRow>
                {facets.content_ratings.map((c) => (
                  <Chip
                    key={c.value}
                    active={filters.contentRatings.includes(c.value)}
                    onClick={() => patch({ contentRatings: toggleIn(filters.contentRatings, c.value) })}
                  >
                    {c.value}
                  </Chip>
                ))}
              </ChipRow>
            </Section>
          )}
        </>
      )}
    </aside>
  );
}

function Section({ label, children }: { label: string; children: ReactNode }) {
  return (
    <div>
      <div className="text-[11px] uppercase tracking-wide text-muted mb-1.5">{label}</div>
      {children}
    </div>
  );
}

function ChipRow({ children }: { children: ReactNode }) {
  return <div className="flex flex-wrap gap-1">{children}</div>;
}

function Chip({
  active,
  onClick,
  children,
}: {
  active: boolean;
  onClick: () => void;
  children: ReactNode;
}) {
  return (
    <button
      onClick={onClick}
      className={`px-2.5 py-1 rounded-full text-xs transition ${
        active ? "bg-accent text-accent-fg" : "glass-card glass-hover"
      }`}
    >
      {children}
    </button>
  );
}

function RemovableChip({ label, onRemove }: { label: string; onRemove: () => void }) {
  return (
    <span className="inline-flex items-center gap-1 rounded-full bg-accent/15 text-accent px-2 py-1 text-xs">
      <span className="truncate max-w-[9rem]">{label}</span>
      <button onClick={onRemove} className="hover:opacity-70" aria-label="remove">
        <X className="w-3 h-3" />
      </button>
    </span>
  );
}

function NumberInput({
  value,
  placeholder,
  onChange,
}: {
  value: number | null;
  placeholder?: number;
  onChange: (v: number | null) => void;
}) {
  return (
    <input
      type="number"
      inputMode="numeric"
      value={value ?? ""}
      placeholder={placeholder != null ? String(placeholder) : ""}
      onChange={(e) => {
        const n = e.target.value === "" ? null : Number(e.target.value);
        onChange(n != null && Number.isFinite(n) ? n : null);
      }}
      className="w-20 rounded-lg border border-border bg-card px-2 py-1 text-sm tabular-nums focus:border-accent focus:outline-none"
    />
  );
}
