"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { toast } from "sonner";
import {
  Check,
  ChevronDown,
  MessageSquare,
  Paperclip,
  Pencil,
  Plus,
  X,
} from "lucide-react";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import {
  NotesSheet,
  type NewCommentInput,
  type NewNoteInput,
  type NotePatch,
} from "@/components/notes-sheet";
import {
  ALL,
  EnumFilter,
  ExpandableSearch,
  TagFilter,
} from "@/components/column-filters";
import { createClient } from "@/lib/supabase/client";
import { NOTE_BUCKET } from "@/lib/constants";
import { cn } from "@/lib/utils";
import { composeUrl, formatTimestamp, slugPath, truncate } from "@/lib/format";
import {
  CATEGORY_DOT,
  SIGNOFF_DOT,
  SIGNOFF_STYLES,
  STATUS_DOT,
  STATUS_STYLES,
} from "@/lib/status-styles";
import {
  DEFAULT_NOTE_CATEGORY,
  NOTE_CATEGORIES,
  PAGE_STATUSES,
  SIGNOFF_OPTIONS,
  type Comment,
  type Note,
  type NoteCategory,
  type Page,
  type PageStatus,
  type Signoff,
} from "@/lib/types";

const INCOMPLETE = "incomplete";
const IN_PROGRESS = "inprogress";

const LAST_CATEGORY_KEY = "review:lastNoteCategory";

/**
 * A single editable "pill" cell backed by a dropdown menu. Generic over the
 * option type so it serves both the Status and Signoff columns.
 */
export function PillSelect<T extends string>({
  value,
  options,
  pillClass,
  dotClass,
  onSelect,
  label,
}: {
  value: T;
  options: readonly T[];
  pillClass: Record<T, string>;
  dotClass: Record<T, string>;
  onSelect: (next: T) => void;
  label: string;
}) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        render={
          <button
            type="button"
            aria-label={`${label}: ${value}`}
            className={cn(
              "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium capitalize transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
              pillClass[value],
            )}
          >
            <span className={cn("size-1.5 rounded-full", dotClass[value])} />
            {value}
            <ChevronDown className="size-3 opacity-60" />
          </button>
        }
      />
      <DropdownMenuContent align="start" className="min-w-44">
        {options.map((option) => (
          <DropdownMenuItem
            key={option}
            onClick={() => onSelect(option)}
            className="gap-2 capitalize"
          >
            <span className={cn("size-2 rounded-full", dotClass[option])} />
            <span className="flex-1">{option}</span>
            {option === value && <Check className="size-3.5 opacity-70" />}
          </DropdownMenuItem>
        ))}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

function UrlRow({
  label,
  domain,
  slug,
  pillClass,
}: {
  label: string;
  domain: string | null;
  slug: string;
  pillClass: string;
}) {
  const href = composeUrl(domain, slug);
  const text = slugPath(slug);
  return (
    <div className="flex items-center gap-2">
      {href ? (
        <a
          href={href}
          target="_blank"
          rel="noopener noreferrer"
          title={`Open ${label.toLowerCase()} — middle-click or Ctrl/Cmd-click to open in a background tab`}
          className={cn(
            "w-14 shrink-0 rounded px-1.5 py-0.5 text-center text-[10px] font-medium transition-opacity hover:opacity-80",
            pillClass,
          )}
        >
          {label}
        </a>
      ) : (
        <span
          className={cn(
            "w-14 shrink-0 rounded px-1.5 py-0.5 text-center text-[10px] font-medium opacity-70",
            pillClass,
          )}
        >
          {label}
        </span>
      )}
      {href ? (
        <a
          href={href}
          target="_blank"
          rel="noopener noreferrer"
          className="truncate font-mono text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
        >
          {text}
        </a>
      ) : (
        <span
          className="truncate font-mono text-xs text-muted-foreground/70"
          title="Set the root domain above to enable the link"
        >
          {text}
        </span>
      )}
    </div>
  );
}

function PageTags({
  tags,
  onUpdate,
}: {
  tags: string[];
  onUpdate: (tags: string[]) => void;
}) {
  const [adding, setAdding] = useState(false);
  const [value, setValue] = useState("");

  function commit() {
    const tag = value.trim();
    if (tag && !tags.includes(tag)) onUpdate([...tags, tag]);
    setValue("");
    setAdding(false);
  }

  return (
    <div className="flex flex-wrap items-center gap-1">
      {tags.map((tag) => (
        <span
          key={tag}
          className="inline-flex items-center gap-1 rounded-full border bg-muted px-2 py-0.5 text-[11px]"
        >
          {tag}
          <button
            type="button"
            onClick={() => onUpdate(tags.filter((t) => t !== tag))}
            aria-label={`Remove tag ${tag}`}
            className="text-muted-foreground hover:text-foreground"
          >
            <X className="size-2.5" />
          </button>
        </span>
      ))}
      {adding ? (
        <input
          autoFocus
          value={value}
          onChange={(e) => setValue(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === "Enter") commit();
            if (e.key === "Escape") {
              setValue("");
              setAdding(false);
            }
          }}
          onBlur={commit}
          placeholder="tag"
          className="h-6 w-20 rounded border bg-background px-1.5 text-[11px] focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
        />
      ) : (
        <button
          type="button"
          onClick={() => setAdding(true)}
          className="inline-flex items-center gap-0.5 rounded-full border border-dashed px-2 py-0.5 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground"
        >
          <Plus className="size-2.5" />
          Tag
        </button>
      )}
    </div>
  );
}

function PageCell({
  page,
  onUpdateTags,
  stagingDomain,
  liveDomain,
}: {
  page: Page;
  onUpdateTags: (tags: string[]) => void;
  stagingDomain: string | null;
  liveDomain: string | null;
}) {
  return (
    <div className="space-y-2">
      <div className="font-semibold">{page.page_name}</div>
      <div className="flex flex-col gap-1">
        <UrlRow
          label="Staging"
          domain={stagingDomain}
          slug={page.slug}
          pillClass="bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-200"
        />
        <UrlRow
          label="Live"
          domain={liveDomain}
          slug={page.slug}
          pillClass="bg-green-100 text-green-800 dark:bg-green-950 dark:text-green-200"
        />
      </div>
      <PageTags tags={page.tags} onUpdate={onUpdateTags} />
    </div>
  );
}

function DomainField({
  label,
  value,
  placeholder,
  onSave,
}: {
  label: string;
  value: string | null;
  placeholder: string;
  onSave: (value: string) => void;
}) {
  const [editing, setEditing] = useState(false);
  const [draft, setDraft] = useState(value ?? "");

  useEffect(() => {
    setDraft(value ?? "");
  }, [value]);

  if (editing) {
    return (
      <span className="inline-flex items-center gap-1">
        <span className="text-muted-foreground">{label}:</span>
        <input
          autoFocus
          value={draft}
          onChange={(e) => setDraft(e.target.value)}
          placeholder={placeholder}
          onKeyDown={(e) => {
            if (e.key === "Enter") {
              onSave(draft.trim());
              setEditing(false);
            }
            if (e.key === "Escape") {
              setDraft(value ?? "");
              setEditing(false);
            }
          }}
          className="h-6 w-44 rounded border bg-background px-1.5 font-mono text-xs focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
        />
        <button
          type="button"
          aria-label="Save domain"
          onClick={() => {
            onSave(draft.trim());
            setEditing(false);
          }}
          className="rounded p-0.5 text-green-600 hover:bg-accent"
        >
          <Check className="size-3.5" />
        </button>
        <button
          type="button"
          aria-label="Cancel"
          onClick={() => {
            setDraft(value ?? "");
            setEditing(false);
          }}
          className="rounded p-0.5 text-muted-foreground hover:bg-accent"
        >
          <X className="size-3.5" />
        </button>
      </span>
    );
  }

  return (
    <button
      type="button"
      onClick={() => setEditing(true)}
      title={`Edit ${label} domain`}
      className="inline-flex items-center gap-1 rounded px-1 py-0.5 hover:bg-accent"
    >
      <span className="text-muted-foreground">{label}:</span>
      <span className="font-mono">
        {value || <span className="italic text-muted-foreground">not set</span>}
      </span>
      <Pencil className="size-3 opacity-50" />
    </button>
  );
}

export function NotesCell({
  notes: allNotes,
  onOpenNotes,
  onOpenThread,
  categoryFilter,
  showResolved,
}: {
  notes: Note[];
  onOpenNotes: () => void;
  onOpenThread: (noteId: string) => void;
  categoryFilter: string;
  showResolved: boolean;
}) {
  const notes = [...allNotes]
    .filter((n) => showResolved || !n.resolved)
    .filter((n) => categoryFilter === ALL || n.category === categoryFilter)
    // Open notes first (newest within each group).
    .sort((a, b) => {
      if (a.resolved !== b.resolved) return a.resolved ? 1 : -1;
      return b.created_at.localeCompare(a.created_at);
    });

  const emptyText =
    allNotes.length === 0 ? "No notes yet." : "No notes match the filter.";

  return (
    <div className="space-y-2">
      {notes.length === 0 ? (
        <p className="text-xs text-muted-foreground">{emptyText}</p>
      ) : (
        <ul className="space-y-1.5">
          {notes.map((note) => (
            <li key={note.id}>
              <button
                type="button"
                onClick={() => onOpenThread(note.id)}
                title={`${note.category} — open thread`}
                className={cn(
                  "flex w-full items-start gap-2 rounded-md border bg-muted/30 px-2 py-1.5 text-left text-xs transition-colors hover:bg-accent",
                  note.resolved && "opacity-55",
                )}
              >
                <span
                  className={cn(
                    "mt-1 size-1.5 shrink-0 rounded-full",
                    CATEGORY_DOT[note.category],
                  )}
                />
                <span className="whitespace-nowrap font-mono text-[10px] text-muted-foreground">
                  {formatTimestamp(note.created_at)}
                </span>
                <span
                  className={cn(
                    "flex-1 text-foreground/80",
                    note.resolved && "line-through",
                  )}
                >
                  {truncate(note.content) || "(screenshot)"}
                </span>
                {note.comments.length > 0 && (
                  <span className="mt-0.5 inline-flex shrink-0 items-center gap-0.5 text-[10px] text-muted-foreground">
                    <MessageSquare className="size-3" />
                    {note.comments.length}
                  </span>
                )}
                {note.image_url && (
                  <Paperclip className="mt-0.5 size-3 shrink-0 text-muted-foreground" />
                )}
              </button>
            </li>
          ))}
        </ul>
      )}
      <Button
        variant="outline"
        size="sm"
        className="h-7 gap-1 text-xs"
        onClick={onOpenNotes}
      >
        <Plus className="size-3" />
        Add Note
      </Button>
    </div>
  );
}

const IN_PROGRESS_STATUSES: PageStatus[] = [
  "creating",
  "finalizing",
  "revision pending",
  "revising",
];

export function StatCard({
  label,
  value,
  dot,
  active,
  onClick,
}: {
  label: string;
  value: number;
  dot: string | null;
  active: boolean;
  onClick: () => void;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      className={cn(
        "flex items-center gap-1.5 whitespace-nowrap rounded-md border bg-background px-2.5 py-1.5 transition-colors hover:bg-accent",
        active && "border-foreground/40 ring-1 ring-foreground/20",
      )}
    >
      <span className="text-base font-semibold tabular-nums">{value}</span>
      {dot && <span className={cn("size-1.5 rounded-full", dot)} />}
      <span className="text-xs text-muted-foreground">{label}</span>
    </button>
  );
}

function StatsBar({
  pages,
  statusFilter,
  categoryFilter,
  allCleared,
  onTotal,
  onStatus,
  onCategory,
}: {
  pages: Page[];
  statusFilter: string;
  categoryFilter: string;
  allCleared: boolean;
  onTotal: () => void;
  onStatus: (value: string) => void;
  onCategory: (value: NoteCategory) => void;
}) {
  const countPages = (fn: (p: Page) => boolean) => pages.filter(fn).length;
  // Open (unresolved) notes of a given category across all pages.
  const countNotes = (category: NoteCategory) =>
    pages.reduce(
      (n, p) =>
        n + p.notes.filter((x) => !x.resolved && x.category === category).length,
      0,
    );

  return (
    <div className="mb-4 flex flex-wrap gap-2">
      <StatCard
        label="Total pages"
        value={pages.length}
        dot={null}
        active={allCleared}
        onClick={onTotal}
      />
      <StatCard
        label="Open"
        value={countPages((p) => p.status === "open")}
        dot={STATUS_DOT.open}
        active={statusFilter === "open"}
        onClick={() => onStatus("open")}
      />
      <StatCard
        label="In progress"
        value={countPages((p) => IN_PROGRESS_STATUSES.includes(p.status))}
        dot={STATUS_DOT.creating}
        active={statusFilter === IN_PROGRESS}
        onClick={() => onStatus(IN_PROGRESS)}
      />
      <StatCard
        label="Drafted"
        value={countPages((p) => p.status === "drafted")}
        dot={STATUS_DOT.drafted}
        active={statusFilter === "drafted"}
        onClick={() => onStatus("drafted")}
      />
      <StatCard
        label="Completed"
        value={countPages((p) => p.status === "completed")}
        dot={STATUS_DOT.completed}
        active={statusFilter === "completed"}
        onClick={() => onStatus("completed")}
      />
      {NOTE_CATEGORIES.map((category) => (
        <StatCard
          key={category}
          label={category}
          value={countNotes(category)}
          dot={CATEGORY_DOT[category]}
          active={categoryFilter === category}
          onClick={() => onCategory(category)}
        />
      ))}
    </div>
  );
}

export function ReviewTable({
  projectId,
  initialPages,
  currentUserEmail,
  initialStagingDomain,
  initialLiveDomain,
  users = [],
}: {
  projectId: string;
  initialPages: Page[];
  currentUserEmail: string | null;
  initialStagingDomain: string | null;
  initialLiveDomain: string | null;
  users?: string[];
}) {
  const [pages, setPages] = useState<Page[]>(initialPages);
  const [stagingDomain, setStagingDomain] = useState(initialStagingDomain);
  const [liveDomain, setLiveDomain] = useState(initialLiveDomain);
  useEffect(() => setStagingDomain(initialStagingDomain), [initialStagingDomain]);
  useEffect(() => setLiveDomain(initialLiveDomain), [initialLiveDomain]);
  const [openPageId, setOpenPageId] = useState<string | null>(null);
  const [openNoteId, setOpenNoteId] = useState<string | null>(null);
  const router = useRouter();
  const supabase = useMemo(() => createClient(), []);

  // Column filters. "incomplete" (everything except completed) is the default.
  const [pageQuery, setPageQuery] = useState("");
  const [statusFilter, setStatusFilter] = useState<string>(INCOMPLETE);
  const [signoffFilter, setSignoffFilter] = useState<string>(ALL);
  const [categoryFilter, setCategoryFilter] = useState<string>(ALL);
  const [showResolved, setShowResolved] = useState(false);
  const [selectedTags, setSelectedTags] = useState<Set<string>>(new Set());

  // Remember the note type this user last picked (persists across reloads).
  const [defaultCategory, setDefaultCategory] = useState<NoteCategory>(
    DEFAULT_NOTE_CATEGORY,
  );
  useEffect(() => {
    const saved = localStorage.getItem(LAST_CATEGORY_KEY);
    if (saved && (NOTE_CATEGORIES as readonly string[]).includes(saved)) {
      setDefaultCategory(saved as NoteCategory);
    }
  }, []);

  // Re-sync local state whenever the server sends fresh data
  // (after router.refresh() or a realtime event).
  useEffect(() => {
    setPages(initialPages);
  }, [initialPages]);

  // Deep-link from a notification: /<slug>?focus=page:<id>&note=<id>. Opens that
  // page's notes thread as a modal (works regardless of filters/scroll — the
  // sheet reads the full pages list). The _t param (added by the bell) makes
  // each click unique so re-clicking the same notification re-opens it.
  const searchParams = useSearchParams();
  const handledFocus = useRef<string | null>(null);
  useEffect(() => {
    const focus = searchParams.get("focus");
    if (!focus || !focus.startsWith("page:")) return;
    const key = focus + ":" + (searchParams.get("_t") ?? "");
    if (handledFocus.current === key) return;
    const pageId = focus.slice("page:".length);
    if (!pages.some((p) => p.id === pageId)) return;
    handledFocus.current = key;
    setOpenPageId(pageId);
    setOpenNoteId(searchParams.get("note"));
  }, [searchParams, pages]);

  // Realtime: when anyone changes this project's pages or notes, pull fresh
  // server data (which also re-signs image URLs). Keeps reviewers in sync.
  useEffect(() => {
    const channel = supabase
      .channel(`project-${projectId}`)
      .on(
        "postgres_changes",
        {
          event: "*",
          schema: "public",
          table: "pages",
          filter: `project_id=eq.${projectId}`,
        },
        () => router.refresh(),
      )
      .on(
        "postgres_changes",
        { event: "*", schema: "public", table: "notes" },
        () => router.refresh(),
      )
      .on(
        "postgres_changes",
        { event: "*", schema: "public", table: "comments" },
        () => router.refresh(),
      )
      .subscribe();

    return () => {
      supabase.removeChannel(channel);
    };
  }, [supabase, projectId, router]);

  async function setStatus(pageId: string, status: PageStatus) {
    // Automation: the status drives the Review state.
    const patch: { status: PageStatus; signoff?: Signoff } = { status };
    if (status === "open" || status === "creating" || status === "finalizing") {
      patch.signoff = "not ready";
    } else if (status === "drafted") {
      patch.signoff = "ready for review";
    } else if (status === "revising") {
      patch.signoff = "revision not ready";
    }

    const snapshot = pages;
    setPages((prev) =>
      prev.map((p) => (p.id === pageId ? { ...p, ...patch } : p)),
    );
    // The DB trigger writes the StatusLogs history row automatically.
    const { error } = await supabase.from("pages").update(patch).eq("id", pageId);
    if (error) {
      setPages(snapshot);
      toast.error("Couldn't update status. Please try again.");
      return;
    }
    router.refresh();
  }

  async function setSignoff(pageId: string, signoff: Signoff) {
    // Automation: a review state can drive the page status.
    const patch: { signoff: Signoff; status?: PageStatus } = { signoff };
    if (signoff === "revision needed") patch.status = "revision pending";
    else if (signoff === "approved") patch.status = "completed";

    const snapshot = pages;
    setPages((prev) =>
      prev.map((p) => (p.id === pageId ? { ...p, ...patch } : p)),
    );
    const { error } = await supabase.from("pages").update(patch).eq("id", pageId);
    if (error) {
      setPages(snapshot);
      toast.error("Couldn't update review. Please try again.");
      return;
    }
    router.refresh();
  }

  async function addNote(pageId: string, input: NewNoteInput) {
    try {
      let imagePath: string | null = null;

      if (input.imageFile) {
        const ext =
          input.imageFile.name.split(".").pop()?.toLowerCase() || "png";
        imagePath = `${pageId}/${crypto.randomUUID()}.${ext}`;
        const { error: uploadError } = await supabase.storage
          .from(NOTE_BUCKET)
          .upload(imagePath, input.imageFile, {
            contentType: input.imageFile.type || "image/png",
          });
        if (uploadError) throw uploadError;
      }

      const { data, error } = await supabase
        .from("notes")
        .insert({
          page_id: pageId,
          content: input.content,
          image_path: imagePath,
          category: input.category,
        })
        .select("id, created_at")
        .single();
      if (error) throw error;

      // Optimistically append using the local data URL for an instant preview;
      // router.refresh() then reconciles with the signed URL from the server.
      const note: Note = {
        id: data.id,
        page_id: pageId,
        content: input.content,
        image_url: input.imageDataUrl,
        category: input.category,
        resolved: false,
        author_email: currentUserEmail,
        created_at: data.created_at,
        comments: [],
      };
      setPages((prev) =>
        prev.map((p) =>
          p.id === pageId ? { ...p, notes: [...p.notes, note] } : p,
        ),
      );
      // Remember this type as the default for the next note.
      setDefaultCategory(input.category);
      localStorage.setItem(LAST_CATEGORY_KEY, input.category);
      router.refresh();
    } catch {
      toast.error("Couldn't add the note. Please try again.");
    }
  }

  async function saveDomains(patch: {
    staging_domain?: string | null;
    live_domain?: string | null;
  }) {
    if (patch.staging_domain !== undefined)
      setStagingDomain(patch.staging_domain || null);
    if (patch.live_domain !== undefined)
      setLiveDomain(patch.live_domain || null);
    const { error } = await supabase
      .from("projects")
      .update(patch)
      .eq("id", projectId);
    if (error) {
      toast.error("Couldn't save the domain. Please try again.");
    }
    router.refresh();
  }

  async function updatePageTags(pageId: string, tags: string[]) {
    const snapshot = pages;
    setPages((prev) =>
      prev.map((p) => (p.id === pageId ? { ...p, tags } : p)),
    );
    const { error } = await supabase
      .from("pages")
      .update({ tags })
      .eq("id", pageId);
    if (error) {
      setPages(snapshot);
      toast.error("Couldn't update tags. Please try again.");
      return;
    }
    router.refresh();
  }

  async function addComment(noteId: string, input: NewCommentInput) {
    try {
      let imagePath: string | null = null;

      if (input.imageFile) {
        const ext =
          input.imageFile.name.split(".").pop()?.toLowerCase() || "png";
        imagePath = `${noteId}/${crypto.randomUUID()}.${ext}`;
        const { error: uploadError } = await supabase.storage
          .from(NOTE_BUCKET)
          .upload(imagePath, input.imageFile, {
            contentType: input.imageFile.type || "image/png",
          });
        if (uploadError) throw uploadError;
      }

      const { data, error } = await supabase
        .from("comments")
        .insert({ note_id: noteId, content: input.content, image_path: imagePath })
        .select("id, created_at")
        .single();
      if (error) throw error;

      const comment: Comment = {
        id: data.id,
        note_id: noteId,
        content: input.content,
        image_url: input.imageDataUrl,
        author_email: currentUserEmail,
        created_at: data.created_at,
      };
      setPages((prev) =>
        prev.map((p) => ({
          ...p,
          notes: p.notes.map((n) =>
            n.id === noteId
              ? { ...n, comments: [...n.comments, comment] }
              : n,
          ),
        })),
      );
      router.refresh();
    } catch {
      toast.error("Couldn't add the comment. Please try again.");
    }
  }

  async function updateComment(commentId: string, content: string) {
    const snapshot = pages;
    setPages((prev) =>
      prev.map((p) => ({
        ...p,
        notes: p.notes.map((n) => ({
          ...n,
          comments: n.comments.map((c) =>
            c.id === commentId ? { ...c, content } : c,
          ),
        })),
      })),
    );
    const { error } = await supabase
      .from("comments")
      .update({ content })
      .eq("id", commentId);
    if (error) {
      setPages(snapshot);
      toast.error("Couldn't update the comment. Please try again.");
      return;
    }
    router.refresh();
  }

  async function updateNote(noteId: string, patch: NotePatch) {
    const snapshot = pages;
    setPages((prev) =>
      prev.map((p) => ({
        ...p,
        notes: p.notes.map((n) =>
          n.id === noteId ? { ...n, ...patch } : n,
        ),
      })),
    );

    // When resolving, stamp resolved_at; clear it when reopening.
    const dbPatch: NotePatch & { resolved_at?: string | null } = { ...patch };
    if (patch.resolved !== undefined) {
      dbPatch.resolved_at = patch.resolved ? new Date().toISOString() : null;
    }

    const { error } = await supabase
      .from("notes")
      .update(dbPatch)
      .eq("id", noteId);
    if (error) {
      setPages(snapshot);
      toast.error("Couldn't update the note. Please try again.");
      return;
    }
    router.refresh();
  }

  async function deleteNote(noteId: string) {
    const snapshot = pages;
    setPages((prev) =>
      prev.map((p) => ({
        ...p,
        notes: p.notes.filter((n) => n.id !== noteId),
      })),
    );
    const { error } = await supabase.from("notes").delete().eq("id", noteId);
    if (error) {
      setPages(snapshot);
      toast.error("Couldn't delete the note. Please try again.");
      return;
    }
    router.refresh();
  }

  const openPage = pages.find((p) => p.id === openPageId) ?? null;
  const activeNote =
    openNoteId !== null
      ? (openPage?.notes.find((n) => n.id === openNoteId) ?? null)
      : null;

  const allTags = Array.from(new Set(pages.flatMap((p) => p.tags))).sort();

  function toggleTag(tag: string) {
    setSelectedTags((prev) => {
      const next = new Set(prev);
      if (next.has(tag)) next.delete(tag);
      else next.add(tag);
      return next;
    });
  }

  const query = pageQuery.trim().toLowerCase();
  const visiblePages = pages.filter((p) => {
    if (statusFilter === INCOMPLETE && p.status === "completed") return false;
    if (
      statusFilter === IN_PROGRESS &&
      !IN_PROGRESS_STATUSES.includes(p.status)
    )
      return false;
    if (
      statusFilter !== ALL &&
      statusFilter !== INCOMPLETE &&
      statusFilter !== IN_PROGRESS &&
      p.status !== statusFilter
    )
      return false;
    if (signoffFilter !== ALL && p.signoff !== signoffFilter) return false;
    if (selectedTags.size > 0 && !p.tags.some((t) => selectedTags.has(t)))
      return false;
    // When filtering by note type, only show pages that actually have a
    // (currently visible) note of that type.
    if (
      categoryFilter !== ALL &&
      !p.notes.some(
        (n) =>
          n.category === categoryFilter && (showResolved || !n.resolved),
      )
    )
      return false;
    if (query) {
      const haystack = `${p.page_name} ${p.slug}`.toLowerCase();
      if (!haystack.includes(query)) return false;
    }
    return true;
  });

  const allCleared =
    statusFilter === ALL &&
    signoffFilter === ALL &&
    categoryFilter === ALL &&
    selectedTags.size === 0 &&
    pageQuery.trim() === "";

  function resetFilters() {
    setStatusFilter(ALL);
    setSignoffFilter(ALL);
    setCategoryFilter(ALL);
    setSelectedTags(new Set());
    setPageQuery("");
  }

  function focusStatus(value: string) {
    resetFilters();
    setStatusFilter(value);
  }

  function focusCategory(category: NoteCategory) {
    resetFilters();
    setCategoryFilter(category);
  }

  function openNotes(pageId: string) {
    setOpenPageId(pageId);
    setOpenNoteId(null);
  }

  function openThread(pageId: string, noteId: string) {
    setOpenPageId(pageId);
    setOpenNoteId(noteId);
  }

  return (
    <div>
      <div className="mb-4 flex flex-wrap items-center gap-x-6 gap-y-1 text-xs">
        <DomainField
          label="Staging root"
          value={stagingDomain}
          placeholder="dev.example.com"
          onSave={(v) => saveDomains({ staging_domain: v })}
        />
        <DomainField
          label="Live root"
          value={liveDomain}
          placeholder="example.com"
          onSave={(v) => saveDomains({ live_domain: v })}
        />
      </div>
      <StatsBar
        pages={pages}
        statusFilter={statusFilter}
        categoryFilter={categoryFilter}
        allCleared={allCleared}
        onTotal={resetFilters}
        onStatus={focusStatus}
        onCategory={focusCategory}
      />
      <div className="overflow-hidden rounded-lg border bg-background">
        <Table>
        <TableHeader>
          <TableRow className="bg-muted/40 hover:bg-muted/40">
            <TableHead className="w-[26%] px-4 py-3 align-top">
              <div className="flex flex-col gap-2.5">
                <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                  Page
                </span>
                <div className="flex items-center gap-2">
                  <ExpandableSearch
                    value={pageQuery}
                    onChange={setPageQuery}
                    placeholder="Search name or URL…"
                    label="Search pages"
                  />
                  <TagFilter
                    allTags={allTags}
                    selected={selectedTags}
                    onToggle={toggleTag}
                    onClear={() => setSelectedTags(new Set())}
                  />
                </div>
              </div>
            </TableHead>
            <TableHead className="w-[15%] px-4 py-3 align-top">
              <div className="flex flex-col gap-2.5">
                <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                  Status
                </span>
                <EnumFilter
                  ariaLabel="Filter by status"
                  value={statusFilter}
                  options={PAGE_STATUSES}
                  dotClass={STATUS_DOT}
                  onChange={setStatusFilter}
                  extraOptions={[
                    { value: INCOMPLETE, label: "Incomplete" },
                    { value: IN_PROGRESS, label: "In progress" },
                  ]}
                />
              </div>
            </TableHead>
            <TableHead className="w-[18%] px-4 py-3 align-top">
              <div className="flex flex-col gap-2.5">
                <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                  Review
                </span>
                <EnumFilter
                  ariaLabel="Filter by review state"
                  value={signoffFilter}
                  options={SIGNOFF_OPTIONS}
                  dotClass={SIGNOFF_DOT}
                  onChange={setSignoffFilter}
                />
              </div>
            </TableHead>
            <TableHead className="px-4 py-3 align-top">
              <div className="flex flex-col gap-2.5">
                <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                  Review Notes
                </span>
                <div className="flex flex-wrap items-center gap-x-4 gap-y-2">
                  <EnumFilter
                    ariaLabel="Filter notes by type"
                    value={categoryFilter}
                    options={NOTE_CATEGORIES}
                    dotClass={CATEGORY_DOT}
                    onChange={setCategoryFilter}
                    allLabel="All types"
                  />
                  <div className="flex items-center gap-2">
                    <Switch
                      id="show-resolved"
                      checked={showResolved}
                      onCheckedChange={setShowResolved}
                    />
                    <label
                      htmlFor="show-resolved"
                      className="cursor-pointer text-xs font-normal text-muted-foreground"
                    >
                      Show resolved
                    </label>
                  </div>
                </div>
              </div>
            </TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {visiblePages.length === 0 ? (
            <TableRow>
              <TableCell
                colSpan={4}
                className="py-10 text-center text-sm text-muted-foreground"
              >
                No pages match the current filters.
              </TableCell>
            </TableRow>
          ) : (
            visiblePages.map((page) => (
              <TableRow key={page.id} className="align-top">
                <TableCell className="py-4">
                  <PageCell
                    page={page}
                    onUpdateTags={(tags) => updatePageTags(page.id, tags)}
                    stagingDomain={stagingDomain}
                    liveDomain={liveDomain}
                  />
                </TableCell>
                <TableCell className="py-4">
                  <PillSelect
                    label="Status"
                    value={page.status}
                    options={PAGE_STATUSES}
                    pillClass={STATUS_STYLES}
                    dotClass={STATUS_DOT}
                    onSelect={(next) => setStatus(page.id, next)}
                  />
                </TableCell>
                <TableCell className="py-4">
                  <PillSelect
                    label="Signoff"
                    value={page.signoff}
                    options={SIGNOFF_OPTIONS}
                    pillClass={SIGNOFF_STYLES}
                    dotClass={SIGNOFF_DOT}
                    onSelect={(next) => setSignoff(page.id, next)}
                  />
                </TableCell>
                <TableCell className="py-4">
                  <NotesCell
                    notes={page.notes}
                    onOpenNotes={() => openNotes(page.id)}
                    onOpenThread={(noteId) => openThread(page.id, noteId)}
                    categoryFilter={categoryFilter}
                    showResolved={showResolved}
                  />
                </TableCell>
              </TableRow>
            ))
          )}
        </TableBody>
        </Table>
      </div>

      <NotesSheet
        parent={
          openPage
            ? {
                id: openPage.id,
                title: openPage.page_name,
                notes: openPage.notes,
                stagingUrl: composeUrl(stagingDomain, openPage.slug),
                liveUrl: composeUrl(liveDomain, openPage.slug),
              }
            : null
        }
        activeNote={activeNote}
        open={openPageId !== null}
        onOpenChange={(next) => {
          if (!next) {
            setOpenPageId(null);
            setOpenNoteId(null);
          }
        }}
        onAddNote={addNote}
        onUpdateNote={updateNote}
        onDeleteNote={deleteNote}
        onAddComment={addComment}
        onUpdateComment={updateComment}
        onOpenThread={(noteId) => setOpenNoteId(noteId)}
        onBack={() => setOpenNoteId(null)}
        currentUserEmail={currentUserEmail}
        defaultCategory={defaultCategory}
        mentionUsers={users}
      />
    </div>
  );
}
