import { useState, type ReactNode } from "react";
import { qk } from "../lib/queryKeys";
import { useTranslation } from "react-i18next";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { Layers, ListMusic, Plus, SlidersHorizontal, X } from "lucide-react";
import { api, EMPTY_PLEX_FILTERS, plexFilterCount, type PlexFilters } from "../lib/api";
import { defaultLayout, type PanelLayout } from "../lib/panelLayout";
import { useDebounced } from "../lib/useDebounced";
import { useScrollFade } from "../lib/useScrollFade";
import SidePanel from "./SidePanel";
import PanelGroups, { type PanelItem } from "./PanelGroups";
import { usePlex, usePlexActions } from "./PlexProvider";

// The Plex module's left filter column — now the shared floating SidePanel with its filter groups
// as reorderable "islands" (same system as the feed rail). Movie libraries get the full metadata
// set; shows keep library + sort. State is owned by App (per-account persisted); facets come from
// the backend so the panel only offers what the library actually contains.

type Props = {
  layout: PanelLayout;
  setLayout: (l: PanelLayout) => 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;
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;
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 },
];

// The any/all combine toggle for a multi-value filter (genres, actors). Same segmented-pill look as
// the feed rail's tag MATCH toggle (rounded-full track + inset active pill), not a boxed button row.
function MatchToggle({
  mode,
  onChange,
}: {
  mode: "any" | "all";
  onChange: (m: "any" | "all") => void;
}) {
  const { t } = useTranslation();
  return (
    <div className="mb-1.5 flex items-center justify-between">
      <span className="text-[10px] uppercase tracking-wide text-muted">
        {t("plex.filter.matchLabel")}
      </span>
      <div
        className="flex items-center rounded-full border border-border bg-card p-0.5 text-[10px]"
        role="group"
        aria-label={t("plex.filter.matchLabel")}
      >
        {(["any", "all"] as const).map((m) => (
          <button
            key={m}
            onClick={() => onChange(m)}
            aria-pressed={mode === m}
            className={`rounded-full px-2 py-0.5 transition ${
              mode === m ? "bg-accent text-accent-fg" : "text-muted hover:text-fg"
            }`}
          >
            {t(`plex.filter.match.${m}`)}
          </button>
        ))}
      </div>
    </div>
  );
}

export default function PlexSidebar({ layout, setLayout, collapsed, onToggleCollapse }: Props) {
  const { t } = useTranslation();
  // Scope / watch-state / sort / expanded filters live in PlexProvider (shared with PlexBrowse and
  // the header search box); this rail is the primary editor of them. Opening a playlist is a
  // hand-off to PlexBrowse via the shared `playlistOpen` slot.
  const { scope, show, sort, filters } = usePlex();
  const {
    setScope,
    setShow,
    setSort,
    setFilters,
    setPlaylistOpen: onOpenPlaylist,
  } = usePlexActions();
  const collFade = useScrollFade();
  const playlistsQ = useQuery({ queryKey: qk.plexPlaylists(), queryFn: () => api.plexPlaylists() });
  const [newPlaylist, setNewPlaylist] = useState("");
  const [editing, setEditing] = useState(false);
  const qc = useQueryClient();
  async function createPlaylist() {
    const title = newPlaylist.trim();
    if (!title) return;
    const pl = await api.plexCreatePlaylist(title);
    setNewPlaylist("");
    qc.invalidateQueries({ queryKey: qk.plexPlaylists() });
    onOpenPlaylist(pl.id);
  }
  const { sortDir: _sd, collectionTitle: _ct, ...facetKey } = filters;
  const facetsQ = useQuery({
    queryKey: qk.plexFacets(scope, show, facetKey),
    queryFn: () => api.plexFacets(scope, filters, show),
    enabled: !!scope,
    placeholderData: (prev) => prev,
  });
  const facets = facetsQ.data;

  const [collSearch, setCollSearch] = useState("");
  const collSearchDeb = useDebounced(collSearch.trim(), 300);
  const collectionsQ = useQuery({
    queryKey: qk.plexCollections("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");
  };

  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;
  const peopleActive =
    filters.directors.length + filters.actors.length + filters.studios.length > 0;

  // Group bodies (no title — the PanelGroup card supplies it). Only available groups are pushed,
  // so facet-gated / scope-gated sections drop out cleanly; PanelGroups orders them by the saved
  // layout and lets the user reorder / hide / collapse them.
  const items: PanelItem[] = [
    {
      id: "scope",
      title: t("plex.filter.scope"),
      body: (
        <div className="grid grid-cols-3 gap-1">
          {(["both", "movie", "show"] as const).map((s) => (
            <button
              key={s}
              onClick={() => setScope(s)}
              className={`min-w-0 px-1.5 py-1.5 rounded-lg text-xs font-medium truncate transition ${
                s === scope ? "bg-accent text-accent-fg" : "glass-card glass-hover"
              }`}
            >
              {t(`plex.filter.scopeOpt.${s}`)}
            </button>
          ))}
        </div>
      ),
    },
    {
      id: "playlists",
      title: t("plex.playlist.section"),
      body: (
        <>
          <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>
          )}
        </>
      ),
    },
    {
      id: "watch",
      title: t("plex.filter.show"),
      body: (
        <ChipRow>
          {SHOW_OPTS.map((s) => (
            <Chip key={s} active={show === s} onClick={() => setShow(s)}>
              {t(`plex.filter.showOpt.${s}`)}
            </Chip>
          ))}
        </ChipRow>
      ),
    },
    {
      id: "sort",
      title: t("plex.filter.sort"),
      body: (
        <>
          <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>
        </>
      ),
    },
    ...(peopleActive
      ? [
          {
            id: "people",
            title: t("plex.filter.active"),
            body: (
              <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.length > 1 && (
                  <div className="w-full">
                    <MatchToggle
                      mode={filters.actorMode ?? "any"}
                      onChange={(m) => patch({ actorMode: m })}
                    />
                  </div>
                )}
                {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>
            ),
          },
        ]
      : []),
    {
      id: "collection",
      title: t("plex.filter.collection"),
      body: 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
            ref={collFade.ref}
            style={collFade.style}
            className="flex max-h-56 flex-col gap-0.5 overflow-y-auto no-scrollbar"
          >
            {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>
      ),
    },
    {
      id: "rating",
      title: t("plex.filter.rating"),
      body: (
        <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>
      ),
    },
    ...(facets && facets.genres.length > 0
      ? [
          {
            id: "genre",
            title: t("plex.filter.genre"),
            body: (
              <>
                {filters.genres.length > 1 && (
                  <MatchToggle
                    mode={filters.genreMode ?? "any"}
                    onChange={(m) => patch({ genreMode: m })}
                  />
                )}
                <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>
              </>
            ),
          },
        ]
      : []),
    {
      id: "year",
      title: t("plex.filter.year"),
      body: (
        <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>
      ),
    },
    ...(scope === "movie"
      ? [
          {
            id: "duration",
            title: t("plex.filter.duration"),
            body: (
              <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>
            ),
          },
        ]
      : []),
    {
      id: "added",
      title: t("plex.filter.added"),
      body: (
        <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>
      ),
    },
    ...(facets && facets.content_ratings.length > 0
      ? [
          {
            id: "contentrating",
            title: t("plex.filter.contentRating"),
            body: (
              <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>
            ),
          },
        ]
      : []),
  ];

  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("plex"))}
      editHint={t("sidebar.editHint")}
      actions={
        anyActive ? (
          <button
            onClick={clearAll}
            className="inline-flex items-center gap-1 text-xs text-muted hover:text-accent transition px-1 whitespace-nowrap"
          >
            <X className="w-3.5 h-3.5" />
            {t("plex.filter.clearAll")}
          </button>
        ) : undefined
      }
    >
      <PanelGroups items={items} layout={layout} setLayout={setLayout} editing={editing} />
    </SidePanel>
  );
}

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