"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,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import {
  AuthorLine,
  CategorySelect,
  CommentBlock,
  Composer,
  ContentEditor,
  DeleteButton,
  NoteImage,
  type NewCommentInput,
} from "@/components/notes-sheet";
import {
  ALL,
  EnumFilter,
  ExpandableSearch,
} from "@/components/column-filters";
import { StatChip } from "@/components/stat-chip";
import { Markdown } from "@/components/markdown";
import { createClient } from "@/lib/supabase/client";
import { NOTE_BUCKET } from "@/lib/constants";
import { cn } from "@/lib/utils";
import { formatTimestamp } from "@/lib/format";
import {
  CATEGORY_DOT,
  ISSUE_STATUS_DOT,
  ISSUE_STATUS_STYLES,
} from "@/lib/status-styles";
import {
  DEFAULT_NOTE_CATEGORY,
  ISSUE_STATUSES,
  NOTE_CATEGORIES,
  type Issue,
  type IssueComment,
  type IssueStatus,
  type NoteCategory,
} from "@/lib/types";

const LAST_ASSIGNEE_KEY = "review:lastAssignee";
const ACTIONABLE = "actionable";
const ME = "__me__";

type IssuePatch = Partial<{
  category: NoteCategory;
  content: string;
  status: IssueStatus;
  action_to: string[];
  prev_action_to: string[];
}>;

function StatusPill({
  value,
  onSelect,
}: {
  value: IssueStatus;
  onSelect: (next: IssueStatus) => void;
}) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        render={
          <button
            type="button"
            aria-label={`Status: ${value}`}
            className={cn(
              "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
              ISSUE_STATUS_STYLES[value],
            )}
          >
            <span
              className={cn("size-1.5 rounded-full", ISSUE_STATUS_DOT[value])}
            />
            {value}
            <ChevronDown className="size-3 opacity-60" />
          </button>
        }
      />
      <DropdownMenuContent align="start" className="min-w-48">
        {ISSUE_STATUSES.map((option) => (
          <DropdownMenuItem
            key={option}
            onClick={() => onSelect(option)}
            className="gap-2"
          >
            <span
              className={cn("size-2 rounded-full", ISSUE_STATUS_DOT[option])}
            />
            <span className="flex-1">{option}</span>
            {option === value && <Check className="size-3.5 opacity-70" />}
          </DropdownMenuItem>
        ))}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

function AssigneeSelect({
  users,
  value,
  onChange,
}: {
  users: string[];
  value: string[];
  onChange: (next: string[]) => void;
}) {
  function toggle(email: string) {
    onChange(
      value.includes(email)
        ? value.filter((e) => e !== email)
        : [...value, email],
    );
  }

  return (
    <div className="flex flex-wrap items-center gap-1">
      {value.map((email) => (
        <span
          key={email}
          className="inline-flex items-center gap-1 rounded-full border bg-muted px-2 py-0.5 text-[11px]"
        >
          {email}
          <button
            type="button"
            onClick={() => toggle(email)}
            aria-label={`Unassign ${email}`}
            className="text-muted-foreground hover:text-foreground"
          >
            <X className="size-2.5" />
          </button>
        </span>
      ))}
      <DropdownMenu>
        <DropdownMenuTrigger
          render={
            <button
              type="button"
              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" />
              {value.length === 0 ? "Assign" : ""}
            </button>
          }
        />
        <DropdownMenuContent align="start" className="min-w-52">
          {users.length === 0 ? (
            <div className="px-2 py-1.5 text-xs text-muted-foreground">
              No users found
            </div>
          ) : (
            users.map((email) => (
              <DropdownMenuCheckboxItem
                key={email}
                checked={value.includes(email)}
                onCheckedChange={() => toggle(email)}
              >
                {email}
              </DropdownMenuCheckboxItem>
            ))
          )}
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  );
}

const COLLAPSED_HEIGHT = 96; // px — roughly 5-6 lines before "Show more"

// Caps tall content and reveals a "Show more" toggle when it overflows.
// Keyed by content in the parent so it re-measures when the text changes.
function ExpandableContent({ children }: { children: React.ReactNode }) {
  const ref = useRef<HTMLDivElement>(null);
  const [expanded, setExpanded] = useState(false);
  const [overflowing, setOverflowing] = useState(false);

  useEffect(() => {
    const el = ref.current;
    if (el) setOverflowing(el.scrollHeight > COLLAPSED_HEIGHT + 8);
  }, []);

  return (
    <div>
      <div
        ref={ref}
        className="relative overflow-hidden"
        style={{ maxHeight: expanded ? undefined : COLLAPSED_HEIGHT }}
      >
        {children}
        {!expanded && overflowing && (
          <div className="pointer-events-none absolute inset-x-0 bottom-0 h-8 bg-gradient-to-t from-background to-transparent" />
        )}
      </div>
      {overflowing && (
        <button
          type="button"
          onClick={(e) => {
            e.stopPropagation();
            setExpanded((v) => !v);
          }}
          className="mt-1 text-xs font-medium text-primary hover:underline"
        >
          {expanded ? "Show less" : "Show more"}
        </button>
      )}
    </div>
  );
}

// Read-only issue content in the table cell. Clicking anywhere opens the
// detail sheet (where you edit + comment). Links/Show-more stop propagation.
function IssueContent({ issue, onOpen }: { issue: Issue; onOpen: () => void }) {
  const count = issue.comments.length;
  return (
    <div
      role="button"
      tabIndex={0}
      onClick={onOpen}
      onKeyDown={(e) => {
        if (e.key === "Enter" || e.key === " ") {
          e.preventDefault();
          onOpen();
        }
      }}
      className="w-full cursor-pointer space-y-1 text-left"
    >
      <div className="min-w-0 break-words">
        {issue.content ? (
          <ExpandableContent key={issue.content}>
            <Markdown content={issue.content} />
          </ExpandableContent>
        ) : (
          <span className="text-muted-foreground italic">
            No description — click to open
          </span>
        )}
      </div>
      {issue.image_url && <NoteImage url={issue.image_url} />}
      <div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 pt-0.5">
        <AuthorLine email={issue.author_email} />
        <span className="font-mono text-xs text-muted-foreground">
          {formatTimestamp(issue.created_at)}
        </span>
        {issue.image_url && (
          <Paperclip className="size-3 text-muted-foreground" />
        )}
        <span className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground">
          <MessageSquare className="size-3" />
          {count > 0 ? `${count} ${count === 1 ? "comment" : "comments"}` : "Comment"}
        </span>
      </div>
    </div>
  );
}

// Full issue detail: editable type/status/assignees + description + comment thread.
function IssueSheet({
  issue,
  users,
  currentUserEmail,
  open,
  onOpenChange,
  onSetCategory,
  onSetStatus,
  onSetAssignees,
  onEditContent,
  onAddComment,
  onUpdateComment,
  onDelete,
}: {
  issue: Issue | null;
  users: string[];
  currentUserEmail: string | null;
  open: boolean;
  onOpenChange: (open: boolean) => void;
  onSetCategory: (issue: Issue, category: NoteCategory) => void;
  onSetStatus: (issue: Issue, status: IssueStatus) => void;
  onSetAssignees: (issue: Issue, action_to: string[]) => void;
  onEditContent: (issueId: string, content: string) => void;
  onAddComment: (issueId: string, input: NewCommentInput) => void;
  onUpdateComment: (commentId: string, content: string) => void;
  onDelete: (issueId: string) => void;
}) {
  const [editing, setEditing] = useState(false);
  useEffect(() => {
    setEditing(false);
  }, [issue?.id]);

  const canEdit = (email: string | null) =>
    email !== null && email === currentUserEmail;

  return (
    <Sheet open={open} onOpenChange={onOpenChange}>
      <SheetContent className="flex w-full flex-col sm:max-w-md">
        {issue && (
          <>
            <SheetHeader className="border-b">
              <SheetTitle>Issue</SheetTitle>
              <SheetDescription>
                {issue.comments.length}{" "}
                {issue.comments.length === 1 ? "comment" : "comments"}
              </SheetDescription>
              <div className="flex flex-wrap items-center gap-2 pt-1">
                <CategorySelect
                  value={issue.category}
                  onSelect={(c) => onSetCategory(issue, c)}
                />
                <StatusPill
                  value={issue.status}
                  onSelect={(s) => onSetStatus(issue, s)}
                />
              </div>
              <div className="pt-1 text-xs text-muted-foreground">
                <span className="mr-2">Action to:</span>
                <AssigneeSelect
                  users={users}
                  value={issue.action_to}
                  onChange={(a) => onSetAssignees(issue, a)}
                />
              </div>
            </SheetHeader>

            <div className="flex-1 space-y-3 overflow-y-auto px-4">
              <div className="rounded-lg border bg-muted/30 p-3 text-sm">
                <div className="mb-1 flex items-center gap-2">
                  <AuthorLine email={issue.author_email} />
                  <span className="font-mono text-xs text-muted-foreground">
                    {formatTimestamp(issue.created_at)}
                  </span>
                  {canEdit(issue.author_email) && (
                    <span className="ml-auto flex items-center gap-0.5">
                      {!editing && (
                        <button
                          type="button"
                          aria-label="Edit issue"
                          title="Edit issue"
                          onClick={() => setEditing(true)}
                          className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
                        >
                          <Pencil className="size-3.5" />
                        </button>
                      )}
                      <DeleteButton
                        label="Delete issue"
                        onDelete={() => {
                          onDelete(issue.id);
                          onOpenChange(false);
                        }}
                      />
                    </span>
                  )}
                </div>
                {editing ? (
                  <ContentEditor
                    initial={issue.content}
                    onSave={(content) => {
                      onEditContent(issue.id, content);
                      setEditing(false);
                    }}
                    onCancel={() => setEditing(false)}
                  />
                ) : (
                  <>
                    {issue.content ? (
                      <Markdown
                        content={issue.content}
                        onChange={
                          canEdit(issue.author_email)
                            ? (content) => onEditContent(issue.id, content)
                            : undefined
                        }
                      />
                    ) : (
                      <span className="text-muted-foreground">
                        No description.
                      </span>
                    )}
                    {issue.image_url && <NoteImage url={issue.image_url} />}
                  </>
                )}
              </div>

              {issue.comments.length > 0 && (
                <div className="space-y-2 border-l-2 pl-3">
                  {issue.comments.map((c) => (
                    <CommentBlock
                      key={c.id}
                      comment={c}
                      canEdit={canEdit(c.author_email)}
                      onUpdate={(content) => onUpdateComment(c.id, content)}
                    />
                  ))}
                </div>
              )}
            </div>

            <SheetFooter className="border-t">
              <Composer
                key={`issue-comment-${issue.id}`}
                label="Add a comment"
                placeholder="Add a comment… or paste a screenshot with ⌘V / Ctrl+V"
                submitLabel="Add Comment"
                showCategory={false}
                defaultCategory={DEFAULT_NOTE_CATEGORY}
                mentionUsers={users}
                onSubmit={({ content, imageDataUrl, imageFile }) =>
                  onAddComment(issue.id, { content, imageDataUrl, imageFile })
                }
              />
            </SheetFooter>
          </>
        )}
      </SheetContent>
    </Sheet>
  );
}

export function IssuesTable({
  projectId,
  initialIssues,
  users,
  currentUserEmail,
}: {
  projectId: string;
  initialIssues: Issue[];
  users: string[];
  currentUserEmail: string | null;
}) {
  const [issues, setIssues] = useState<Issue[]>(initialIssues);
  const [addOpen, setAddOpen] = useState(false);
  const [openIssueId, setOpenIssueId] = useState<string | null>(null);
  const [lastAssignee, setLastAssignee] = useState<string | null>(null);
  // Column filters. Resolved issues are hidden by default.
  const [issueSearch, setIssueSearch] = useState("");
  const [typeFilter, setTypeFilter] = useState<string>(ALL);
  // Default to "Me" each new session so people land on their own action items.
  const [assigneeFilter, setAssigneeFilter] = useState<string>(
    currentUserEmail ? ME : ALL,
  );
  const [statusFilter, setStatusFilter] = useState<string>(ALL);
  const [showResolved, setShowResolved] = useState(false);
  const router = useRouter();
  const supabase = useMemo(() => createClient(), []);
  const activeIssue = issues.find((i) => i.id === openIssueId) ?? null;
  // Issues created or edited this session. Under the "Me" assignee filter these
  // stay visible even after being re-assigned away, until a full page reload
  // (persists across router.refresh; cleared only when the component remounts).
  const sessionTouchedRef = useRef<Set<string>>(new Set());

  useEffect(() => {
    setIssues(initialIssues);
  }, [initialIssues]);

  // Let the sticky header's "+ Add Issue" button open the composer.
  useEffect(() => {
    const handler = () => setAddOpen(true);
    window.addEventListener("review:add-issue", handler);
    return () => window.removeEventListener("review:add-issue", handler);
  }, []);

  useEffect(() => {
    const saved = localStorage.getItem(LAST_ASSIGNEE_KEY);
    if (saved) setLastAssignee(saved);
  }, []);

  useEffect(() => {
    const channel = supabase
      .channel(`issues-${projectId}`)
      .on(
        "postgres_changes",
        {
          event: "*",
          schema: "public",
          table: "issues",
          filter: `project_id=eq.${projectId}`,
        },
        () => router.refresh(),
      )
      .on(
        "postgres_changes",
        { event: "*", schema: "public", table: "issue_comments" },
        () => router.refresh(),
      )
      .subscribe();
    return () => {
      supabase.removeChannel(channel);
    };
  }, [supabase, projectId, router]);

  // Deep-link from a notification: /<slug>?focus=issue:<id>. Opens the issue
  // detail sheet as a modal (works regardless of filters — activeIssue reads
  // the full issues list). The _t param makes re-clicks re-open it.
  const searchParams = useSearchParams();
  const handledFocus = useRef<string | null>(null);
  useEffect(() => {
    const focus = searchParams.get("focus");
    if (!focus || !focus.startsWith("issue:")) return;
    const key = focus + ":" + (searchParams.get("_t") ?? "");
    if (handledFocus.current === key) return;
    const issueId = focus.slice("issue:".length);
    if (!issues.some((i) => i.id === issueId)) return;
    handledFocus.current = key;
    setOpenIssueId(issueId);
  }, [searchParams, issues]);

  async function updateIssue(id: string, patch: IssuePatch) {
    sessionTouchedRef.current.add(id);
    const snapshot = issues;
    setIssues((prev) =>
      prev.map((i) => (i.id === id ? { ...i, ...patch } : i)),
    );
    const { error } = await supabase.from("issues").update(patch).eq("id", id);
    if (error) {
      setIssues(snapshot);
      toast.error("Couldn't update the issue. Please try again.");
      return;
    }
    router.refresh();
  }

  async function addComment(issueId: string, input: NewCommentInput) {
    try {
      let imagePath: string | null = null;
      if (input.imageFile) {
        const ext =
          input.imageFile.name.split(".").pop()?.toLowerCase() || "png";
        imagePath = `issues/${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("issue_comments")
        .insert({ issue_id: issueId, content: input.content, image_path: imagePath })
        .select("id, created_at")
        .single();
      if (error) throw error;

      const comment: IssueComment = {
        id: data.id,
        issue_id: issueId,
        content: input.content,
        image_url: input.imageDataUrl,
        author_email: currentUserEmail,
        created_at: data.created_at,
      };
      setIssues((prev) =>
        prev.map((i) =>
          i.id === issueId ? { ...i, comments: [...i.comments, comment] } : i,
        ),
      );
      router.refresh();
    } catch {
      toast.error("Couldn't add the comment. Please try again.");
    }
  }

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

  async function deleteIssue(issueId: string) {
    const snapshot = issues;
    setIssues((prev) => prev.filter((i) => i.id !== issueId));
    const { error } = await supabase.from("issues").delete().eq("id", issueId);
    if (error) {
      setIssues(snapshot);
      toast.error("Couldn't delete the issue. Please try again.");
      return;
    }
    router.refresh();
  }

  function setCategory(issue: Issue, category: NoteCategory) {
    // Automation: marking an issue "Informational" sets its status to FYI Only.
    const patch: IssuePatch = { category };
    if (category === "Informational") patch.status = "FYI Only";
    updateIssue(issue.id, patch);
  }

  function setStatus(issue: Issue, status: IssueStatus) {
    const patch: IssuePatch = { status };
    if (status === "Ready for Review") {
      // Remember current assignees, then hand back to the creator.
      patch.prev_action_to = issue.action_to;
      patch.action_to = issue.author_email ? [issue.author_email] : [];
    } else if (status === "Revision Required") {
      // Restore whoever it was assigned to before "Ready for Review".
      patch.action_to = issue.prev_action_to;
    } else if (status === "Resolved") {
      patch.action_to = [];
    }
    updateIssue(issue.id, patch);
  }

  function setAssignees(issue: Issue, action_to: string[]) {
    updateIssue(issue.id, { action_to });
    if (action_to.length > 0) {
      const last = action_to[action_to.length - 1];
      setLastAssignee(last);
      localStorage.setItem(LAST_ASSIGNEE_KEY, last);
    }
  }

  async function createIssue(input: {
    content: string;
    category: NoteCategory;
    imageDataUrl: string | null;
    imageFile: File | null;
  }) {
    try {
      let imagePath: string | null = null;
      if (input.imageFile) {
        const ext =
          input.imageFile.name.split(".").pop()?.toLowerCase() || "png";
        imagePath = `issues/${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 action_to = lastAssignee ? [lastAssignee] : [];
      const { data, error } = await supabase
        .from("issues")
        .insert({
          project_id: projectId,
          category: input.category,
          content: input.content,
          image_path: imagePath,
          status: "Open",
          action_to,
        })
        .select("id, created_at")
        .single();
      if (error) throw error;
      sessionTouchedRef.current.add(data.id);

      const issue: Issue = {
        id: data.id,
        project_id: projectId,
        category: input.category,
        content: input.content,
        image_url: input.imageDataUrl,
        status: "Open",
        action_to,
        prev_action_to: [],
        author_email: currentUserEmail,
        created_at: data.created_at,
        comments: [],
      };
      setIssues((prev) => [issue, ...prev]);
      router.refresh();
    } catch {
      toast.error("Couldn't add the issue. Please try again.");
    }
  }

  // "Actionable" = something that needs doing: not Informational, not FYI Only
  // (and not Resolved, which the resolved-hiding rule already excludes).
  const isActionable = (i: Issue) =>
    i.category !== "Informational" && i.status !== "FYI Only";

  const query = issueSearch.trim().toLowerCase();
  const visibleIssues = issues.filter((i) => {
    if (i.status === "Resolved" && !showResolved && statusFilter !== "Resolved")
      return false;
    if (statusFilter === ACTIONABLE) {
      if (!isActionable(i)) return false;
    } else if (statusFilter !== ALL && i.status !== statusFilter) {
      return false;
    }
    if (typeFilter !== ALL && i.category !== typeFilter) return false;
    if (assigneeFilter === ME) {
      // Snapshot filter: assigned to me, OR touched this session (so an issue I
      // just created/re-assigned doesn't vanish until the next full refresh).
      const mine =
        currentUserEmail !== null && i.action_to.includes(currentUserEmail);
      if (!mine && !sessionTouchedRef.current.has(i.id)) return false;
    } else if (assigneeFilter !== ALL && !i.action_to.includes(assigneeFilter)) {
      return false;
    }
    if (query && !i.content.toLowerCase().includes(query)) return false;
    return true;
  });

  const allCleared =
    statusFilter === ALL &&
    typeFilter === ALL &&
    assigneeFilter === ALL &&
    query === "" &&
    !showResolved;

  function resetFilters() {
    setStatusFilter(ALL);
    setTypeFilter(ALL);
    setAssigneeFilter(ALL);
    setIssueSearch("");
    setShowResolved(false);
  }
  function focusStatus(status: string) {
    resetFilters();
    setStatusFilter(status);
  }

  const countStatus = (s: IssueStatus) =>
    issues.filter((i) => i.status === s).length;
  const actionableCount = issues.filter(
    (i) => i.status !== "Resolved" && isActionable(i),
  ).length;

  return (
    <div className="mb-8">
      <div className="mb-3 flex items-center justify-between">
        <h2 className="text-lg font-semibold tracking-tight">General Issues</h2>
        <Button size="sm" className="gap-1" onClick={() => setAddOpen(true)}>
          <Plus className="size-4" />
          Add Issue
        </Button>
      </div>

      {/* Number-card quick filters. */}
      <div className="mb-3 flex flex-wrap gap-2">
        <StatChip
          label="Total"
          value={issues.length}
          active={allCleared}
          onClick={resetFilters}
        />
        <StatChip
          label="Open"
          value={countStatus("Open")}
          dot={ISSUE_STATUS_DOT.Open}
          active={statusFilter === "Open"}
          onClick={() => focusStatus("Open")}
        />
        <StatChip
          label="Actionable"
          value={actionableCount}
          active={statusFilter === ACTIONABLE}
          onClick={() => focusStatus(ACTIONABLE)}
        />
        <StatChip
          label="Revision Required"
          value={countStatus("Revision Required")}
          dot={ISSUE_STATUS_DOT["Revision Required"]}
          active={statusFilter === "Revision Required"}
          onClick={() => focusStatus("Revision Required")}
        />
        <StatChip
          label="Ready for Review"
          value={countStatus("Ready for Review")}
          dot={ISSUE_STATUS_DOT["Ready for Review"]}
          active={statusFilter === "Ready for Review"}
          onClick={() => focusStatus("Ready for Review")}
        />
        <StatChip
          label="Address Later"
          value={countStatus("Address Later")}
          dot={ISSUE_STATUS_DOT["Address Later"]}
          active={statusFilter === "Address Later"}
          onClick={() => focusStatus("Address Later")}
        />
      </div>

      <div className="overflow-hidden rounded-lg border bg-background">
        <Table className="table-fixed">
          <TableHeader>
            <TableRow className="bg-muted/40 hover:bg-muted/40">
              <TableHead className="px-4 py-3 align-top">
                <div className="flex flex-col gap-2">
                  <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                    Issue
                  </span>
                  <ExpandableSearch
                    value={issueSearch}
                    onChange={setIssueSearch}
                    placeholder="Search issues…"
                    label="Search issues"
                  />
                </div>
              </TableHead>
              <TableHead className="w-[22%] px-4 py-3 align-top">
                <div className="flex flex-col gap-2">
                  <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                    Action To
                  </span>
                  <EnumFilter
                    ariaLabel="Filter by assignee"
                    value={assigneeFilter}
                    options={users}
                    onChange={setAssigneeFilter}
                    allLabel="Anyone"
                    extraOptions={
                      currentUserEmail ? [{ value: ME, label: "Me" }] : undefined
                    }
                  />
                </div>
              </TableHead>
              <TableHead className="w-[14%] px-4 py-3 align-top">
                <div className="flex flex-col gap-2">
                  <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                    Type
                  </span>
                  <EnumFilter
                    ariaLabel="Filter by type"
                    value={typeFilter}
                    options={NOTE_CATEGORIES}
                    dotClass={CATEGORY_DOT}
                    onChange={setTypeFilter}
                    allLabel="All types"
                  />
                </div>
              </TableHead>
              <TableHead className="w-[16%] px-4 py-3 align-top">
                <div className="flex flex-col gap-2">
                  <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                    Status
                  </span>
                  <EnumFilter
                    ariaLabel="Filter by status"
                    value={statusFilter}
                    options={ISSUE_STATUSES}
                    dotClass={ISSUE_STATUS_DOT}
                    onChange={setStatusFilter}
                    extraOptions={[{ value: ACTIONABLE, label: "Actionable" }]}
                  />
                  <label className="flex cursor-pointer items-center gap-1.5 text-xs font-normal text-muted-foreground">
                    <Switch
                      checked={showResolved}
                      onCheckedChange={setShowResolved}
                    />
                    Show resolved
                  </label>
                </div>
              </TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {visibleIssues.length === 0 ? (
              <TableRow>
                <TableCell
                  colSpan={4}
                  className="py-8 text-center text-sm text-muted-foreground"
                >
                  {issues.length === 0
                    ? "No general issues yet."
                    : "No issues match the current filters."}
                </TableCell>
              </TableRow>
            ) : (
              visibleIssues.map((issue) => (
                <TableRow key={issue.id} className="align-top">
                  <TableCell className="py-4 whitespace-normal">
                    <IssueContent
                      issue={issue}
                      onOpen={() => setOpenIssueId(issue.id)}
                    />
                  </TableCell>
                  <TableCell className="py-4">
                    <AssigneeSelect
                      users={users}
                      value={issue.action_to}
                      onChange={(action_to) => setAssignees(issue, action_to)}
                    />
                  </TableCell>
                  <TableCell className="py-4">
                    <CategorySelect
                      value={issue.category}
                      onSelect={(category) => setCategory(issue, category)}
                    />
                  </TableCell>
                  <TableCell className="py-4">
                    <StatusPill
                      value={issue.status}
                      onSelect={(status) => setStatus(issue, status)}
                    />
                  </TableCell>
                </TableRow>
              ))
            )}
          </TableBody>
        </Table>
      </div>

      <Sheet open={addOpen} onOpenChange={setAddOpen}>
        <SheetContent className="flex w-full flex-col sm:max-w-md">
          <SheetHeader className="border-b">
            <SheetTitle>New issue</SheetTitle>
            <SheetDescription>
              Add a general issue that isn&apos;t tied to a specific page.
            </SheetDescription>
          </SheetHeader>
          <div className="flex-1" />
          <SheetFooter className="border-t">
            <Composer
              key={addOpen ? "open" : "closed"}
              label="Issue"
              placeholder="Describe the issue… or paste a screenshot with ⌘V / Ctrl+V"
              submitLabel="Add Issue"
              showCategory
              defaultCategory={DEFAULT_NOTE_CATEGORY}
              mentionUsers={users}
              onSubmit={(data) => {
                createIssue(data);
                setAddOpen(false);
              }}
            />
          </SheetFooter>
        </SheetContent>
      </Sheet>

      <IssueSheet
        issue={activeIssue}
        users={users}
        currentUserEmail={currentUserEmail}
        open={openIssueId !== null}
        onOpenChange={(next) => {
          if (!next) setOpenIssueId(null);
        }}
        onSetCategory={setCategory}
        onSetStatus={setStatus}
        onSetAssignees={setAssignees}
        onEditContent={(issueId, content) => updateIssue(issueId, { content })}
        onAddComment={addComment}
        onUpdateComment={updateComment}
        onDelete={deleteIssue}
      />
    </div>
  );
}
