import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { BookmarkPlus, Check, GripVertical, Pencil, Share2, Star, Trash2, 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, getActiveAccount, type FeedFilters, type SavedView } from "../lib/api";
import { filtersToParams, shareUrl } from "../lib/urlState";
import { notify } from "../lib/notifications";
import { copyText } from "../lib/clipboard";
import { accountKey, LS, writeJSON } from "../lib/storage";
import { useConfirm } from "./ConfirmProvider";

// Compact, order-independent signature of a filter set (reuses the share serializer, which
// emits only non-default values and ignores the transient shuffle seed). Used to highlight the
// saved view that matches the feed's current filters.
const keyOf = (f: FeedFilters) => filtersToParams(asFilters(f)).toString();

// A saved view's `filters` is a JSON blob straight from the DB — the FeedFilters type says nothing
// about what an older build actually wrote there, so coerce the array fields before the blob reaches
// app state or a serializer. Deliberately NOT a migration: a view saved against a dropped field just
// loses that filter.
const asFilters = (raw: FeedFilters): FeedFilters => ({
  ...raw,
  tags: raw.tags ?? [],
  channelIds: raw.channelIds ?? [],
});

function syncDefaultMirror(views: SavedView[]): void {
  const key = accountKey(LS.defaultViewFilters, getActiveAccount());
  if (!key) return;
  const def = views.find((v) => v.is_default);
  writeJSON(key, def ? def.filters : null);
}

export default function SavedViewsWidget({
  filters,
  onApply,
}: {
  filters: FeedFilters;
  onApply: (f: FeedFilters) => void;
}) {
  const { t } = useTranslation();
  const qc = useQueryClient();
  const confirm = useConfirm();
  const viewsQuery = useQuery({ queryKey: ["saved-views"], queryFn: api.savedViews });
  const views = viewsQuery.data ?? [];
  const [naming, setNaming] = useState(false);
  const [draftName, setDraftName] = useState("");
  const [renameId, setRenameId] = useState<number | null>(null);

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

  // Keep the default-view mirror (read on load) in sync with the server state.
  useEffect(() => {
    if (viewsQuery.data) syncDefaultMirror(viewsQuery.data);
  }, [viewsQuery.data]);

  const refresh = () => qc.invalidateQueries({ queryKey: ["saved-views"] });
  const currentKey = keyOf(filters);

  async function save() {
    const name = draftName.trim();
    if (!name) return;
    // Strip the transient shuffle seed so a saved "shuffle" view re-rolls fresh on apply.
    const { seed: _seed, ...clean } = filters;
    await api.createView(name, clean);
    setNaming(false);
    setDraftName("");
    refresh();
  }

  async function rename(id: number, name: string) {
    const next = name.trim();
    if (next) await api.updateView(id, { name: next });
    setRenameId(null);
    refresh();
  }

  async function toggleDefault(v: SavedView) {
    await api.updateView(v.id, { is_default: !v.is_default });
    refresh();
  }

  async function remove(v: SavedView) {
    const ok = await confirm({
      message: t("views.deleteConfirm", { name: v.name }),
      confirmLabel: t("views.delete"),
      danger: true,
    });
    if (!ok) return;
    await api.deleteView(v.id);
    refresh();
  }

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

  async function onDragEnd(e: DragEndEvent) {
    const { active, over } = e;
    if (!over || active.id === over.id) return;
    const ids = views.map((v) => v.id);
    const oldIndex = ids.indexOf(active.id as number);
    const newIndex = ids.indexOf(over.id as number);
    if (oldIndex < 0 || newIndex < 0) return;
    const next = arrayMove(ids, oldIndex, newIndex);
    // Optimistic reorder so the drop sticks visually before the round-trip.
    qc.setQueryData<SavedView[]>(
      ["saved-views"],
      next.map((id) => views.find((v) => v.id === id)!),
    );
    await api.reorderViews(next);
    refresh();
  }

  return (
    <div className="space-y-2">
      {views.length === 0 && !naming && (
        <p className="text-[11px] text-muted leading-snug">{t("views.empty")}</p>
      )}

      <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
        <SortableContext items={views.map((v) => v.id)} strategy={verticalListSortingStrategy}>
          <div className="space-y-1">
            {views.map((v) => (
              <ViewRow
                key={v.id}
                view={v}
                active={keyOf(v.filters) === currentKey}
                renaming={renameId === v.id}
                onApply={() => onApply(asFilters(v.filters))}
                onStartRename={() => setRenameId(v.id)}
                onRename={(name) => rename(v.id, name)}
                onCancelRename={() => setRenameId(null)}
                onToggleDefault={() => toggleDefault(v)}
                onShare={() => share(v)}
                onDelete={() => remove(v)}
              />
            ))}
          </div>
        </SortableContext>
      </DndContext>

      {naming ? (
        <div className="flex items-center gap-1">
          <input
            autoFocus
            value={draftName}
            onChange={(e) => setDraftName(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "Enter") void save();
              if (e.key === "Escape") setNaming(false);
            }}
            placeholder={t("views.namePlaceholder")}
            maxLength={80}
            className="flex-1 min-w-0 bg-card border border-border rounded-lg px-2 py-1 text-xs outline-none focus:border-accent"
          />
          <button
            onClick={() => void save()}
            title={t("views.saveAction")}
            className="p-1 rounded-lg text-muted hover:text-accent hover:bg-card transition"
          >
            <Check className="w-4 h-4" />
          </button>
          <button
            onClick={() => setNaming(false)}
            title={t("views.cancel")}
            className="p-1 rounded-lg text-muted hover:text-fg hover:bg-card transition"
          >
            <X className="w-4 h-4" />
          </button>
        </div>
      ) : (
        <button
          onClick={() => {
            setDraftName("");
            setNaming(true);
          }}
          className="w-full inline-flex items-center justify-center gap-1.5 text-xs px-2 py-1.5 rounded-lg border border-dashed border-border text-muted hover:text-accent hover:border-accent transition"
        >
          <BookmarkPlus className="w-3.5 h-3.5" />
          {t("views.save")}
        </button>
      )}
    </div>
  );
}

function ViewRow({
  view,
  active,
  renaming,
  onApply,
  onStartRename,
  onRename,
  onCancelRename,
  onToggleDefault,
  onShare,
  onDelete,
}: {
  view: SavedView;
  active: boolean;
  renaming: boolean;
  onApply: () => void;
  onStartRename: () => void;
  onRename: (name: string) => void;
  onCancelRename: () => void;
  onToggleDefault: () => void;
  onShare: () => void;
  onDelete: () => void;
}) {
  const { t } = useTranslation();
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
    id: view.id,
  });
  const [draft, setDraft] = useState(view.name);
  // Reset the edit draft to the current name each time rename mode opens (so a second rename
  // doesn't start from a stale value).
  useEffect(() => {
    if (renaming) setDraft(view.name);
  }, [renaming, view.name]);
  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.5 : 1,
  };

  if (renaming) {
    return (
      <div ref={setNodeRef} style={style} className="flex items-center gap-1">
        <input
          autoFocus
          value={draft}
          onChange={(e) => setDraft(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === "Enter") onRename(draft);
            if (e.key === "Escape") onCancelRename();
          }}
          maxLength={80}
          className="flex-1 min-w-0 bg-card border border-border rounded-lg px-2 py-1 text-xs outline-none focus:border-accent"
        />
        <button
          onClick={() => onRename(draft)}
          title={t("views.saveAction")}
          className="p-1 rounded-lg text-muted hover:text-accent hover:bg-card transition"
        >
          <Check className="w-4 h-4" />
        </button>
        <button
          onClick={onCancelRename}
          title={t("views.cancel")}
          className="p-1 rounded-lg text-muted hover:text-fg hover:bg-card transition"
        >
          <X className="w-4 h-4" />
        </button>
      </div>
    );
  }

  return (
    <div
      ref={setNodeRef}
      style={style}
      className={`group flex items-center gap-0.5 rounded-lg pr-1 ${
        active ? "bg-accent/10" : "hover:bg-card"
      }`}
    >
      <button
        {...attributes}
        {...listeners}
        title={t("views.reorder")}
        className="p-1 text-muted/60 hover:text-muted cursor-grab touch-none shrink-0"
      >
        <GripVertical className="w-3.5 h-3.5" />
      </button>
      <button
        onClick={onApply}
        title={t("views.apply")}
        className={`flex-1 min-w-0 text-left text-xs py-1 truncate ${
          active ? "text-accent font-medium" : "text-fg"
        }`}
      >
        {view.name}
      </button>
      <div className="flex items-center shrink-0">
        <button
          onClick={onToggleDefault}
          title={view.is_default ? t("views.isDefault") : t("views.makeDefault")}
          className={`p-1 rounded transition ${
            view.is_default
              ? "text-accent"
              : "text-muted opacity-0 group-hover:opacity-100 hover:text-accent"
          }`}
        >
          <Star className={`w-3.5 h-3.5 ${view.is_default ? "fill-current" : ""}`} />
        </button>
        <button
          onClick={onShare}
          title={t("views.share")}
          className="p-1 rounded text-muted opacity-0 group-hover:opacity-100 hover:text-fg transition"
        >
          <Share2 className="w-3.5 h-3.5" />
        </button>
        <button
          onClick={onStartRename}
          title={t("views.rename")}
          className="p-1 rounded text-muted opacity-0 group-hover:opacity-100 hover:text-fg transition"
        >
          <Pencil className="w-3.5 h-3.5" />
        </button>
        <button
          onClick={onDelete}
          title={t("views.delete")}
          className="p-1 rounded text-muted opacity-0 group-hover:opacity-100 hover:text-red-400 transition"
        >
          <Trash2 className="w-3.5 h-3.5" />
        </button>
      </div>
    </div>
  );
}
