"use client";

import Link from "next/link";
import { useState, useEffect, useMemo, useRef, useCallback, memo } from "react";
import { useRouter } from "next/navigation";
import { cn } from "@/lib/cn";
import type { ProjectInfo } from "@/lib/project-name";
import { getAttentionLevel, type DashboardSession } from "@/lib/types";
import { isOrchestratorSession } from "@aoagents/ao-core/types";
import { getSessionTitle, humanizeBranch } from "@/lib/format";
import { usePopoverClamp } from "@/hooks/usePopoverClamp";
import { projectDashboardPath, projectReviewPath, projectSessionPath } from "@/lib/routes";
import { ThemeToggle } from "./ThemeToggle";
import { AddProjectModal } from "./AddProjectModal";
import { ProjectSettingsModal } from "./ProjectSettingsModal";

/** Minimal shape needed to render an orchestrator link in the sidebar. */
export interface ProjectSidebarOrchestrator {
  id: string;
  projectId: string;
}

interface ProjectSidebarProps {
  projects: ProjectInfo[];
  sessions: DashboardSession[] | null;
  /**
   * Per-project orchestrator link. Sourced upstream from `/api/sessions`
   * (the `orchestrators` field), which already applies the canonical
   * "prefer live, fall back to terminal" selection. Not derivable from
   * `sessions`: the sessions endpoint strips orchestrators out.
   */
  orchestrators?: ProjectSidebarOrchestrator[];
  activeProjectId: string | undefined;
  activeSessionId: string | undefined;
  loading?: boolean;
  error?: boolean;
  onRetry?: () => void;
  collapsed?: boolean;
  onToggleCollapsed?: () => void;
  onMobileClose?: () => void;
}

type SessionDotLevel = "respond" | "review" | "action" | "pending" | "working" | "merge" | "done";

const SessionDot = memo(function SessionDot({ level }: { level: SessionDotLevel }) {
  return (
    <div
      className={cn(
        "sidebar-session-dot shrink-0 rounded-full",
        level === "working" && "sidebar-session-dot--glow",
      )}
      data-level={level}
    />
  );
});

// ProjectSidebar consumes `getAttentionLevel()` without passing a mode,
// so the function defaults to "detailed" and `action` never appears here
// in practice. The entry is kept for exhaustiveness — TypeScript requires
// every `AttentionLevel` variant to be present in this `Record` — and
// as forward-compat in case the sidebar ever opts into simple mode.
const SHOW_SESSION_ID_KEY = "ao:sidebar:show-session-id";

function loadShowSessionId(): boolean {
  if (typeof window === "undefined") return false;
  try {
    return window.localStorage.getItem(SHOW_SESSION_ID_KEY) === "true";
  } catch {
    return false;
  }
}

const SHOW_KILLED_KEY = "ao:sidebar:show-killed";
const SHOW_DONE_KEY = "ao:sidebar:show-done";
const EXPANDED_PROJECTS_KEY = "ao:sidebar:expanded-projects";

function loadShowKilled(): boolean {
  if (typeof window === "undefined") return false;
  try {
    return window.sessionStorage.getItem(SHOW_KILLED_KEY) === "true";
  } catch {
    return false;
  }
}

function loadShowDone(): boolean {
  if (typeof window === "undefined") return false;
  try {
    return window.sessionStorage.getItem(SHOW_DONE_KEY) === "true";
  } catch {
    return false;
  }
}

function loadExpandedProjects(): Set<string> | null {
  if (typeof window === "undefined") return null;
  try {
    const raw = window.sessionStorage.getItem(EXPANDED_PROJECTS_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    if (Array.isArray(parsed)) return new Set<string>(parsed);
    return null;
  } catch {
    return null;
  }
}


export function ProjectSidebar(props: ProjectSidebarProps) {
  if (props.projects.length === 0) {
    return <ProjectSidebarEmpty collapsed={props.collapsed} />;
  }
  return <ProjectSidebarInner {...props} />;
}

interface SessionRowProps {
  session: DashboardSession;
  level: SessionDotLevel;
  isActive: boolean;
  showSessionId: boolean;
  pendingRename: string | undefined;
  onNavigate: (href: string, session: DashboardSession) => void;
  onStartRename: (session: DashboardSession, title: string) => void;
}

const SessionRow = memo(function SessionRow({
  session,
  level,
  isActive,
  showSessionId,
  pendingRename,
  onNavigate,
  onStartRename,
}: SessionRowProps) {
  const effectiveDisplayName =
    pendingRename !== undefined
      ? pendingRename
      : session.displayNameUserSet
        ? (session.displayName ?? "")
        : "";
  const title =
    effectiveDisplayName !== ""
      ? effectiveDisplayName
      : (session.branch ?? getSessionTitle(session));
  const sessionHref = projectSessionPath(session.projectId, session.id);

  return (
    <div
      className={cn(
        "project-sidebar__sess-row group",
        isActive && "project-sidebar__sess-row--active",
      )}
    >
      <a
        href={sessionHref}
        onClick={(e) => {
          if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
          e.preventDefault();
          onNavigate(sessionHref, session);
        }}
        className="project-sidebar__sess-link flex flex-1 min-w-0 items-center gap-[7px]"
        aria-current={isActive ? "page" : undefined}
        aria-label={`Open ${title}`}
      >
        <SessionDot level={level} />
        <div className="flex-1 min-w-0">
          <span
            className={cn(
              "project-sidebar__sess-label",
              isActive && "project-sidebar__sess-label--active",
            )}
          >
            {title}
          </span>
          {showSessionId ? (
            <div className="project-sidebar__sess-meta">
              <span className="project-sidebar__sess-id">{session.id}</span>
            </div>
          ) : null}
        </div>
      </a>
      <button
        type="button"
        onClick={(e) => {
          e.preventDefault();
          e.stopPropagation();
          onStartRename(session, title);
        }}
        className="project-sidebar__sess-rename-btn opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus:opacity-100"
        title="Rename session"
        aria-label={`Rename ${session.id}`}
      >
        <svg
          width="11"
          height="11"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          strokeWidth="2"
          strokeLinecap="round"
          strokeLinejoin="round"
          aria-hidden="true"
        >
          <path d="M12 20h9" />
          <path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z" />
        </svg>
      </button>
    </div>
  );
});

function ProjectSidebarEmpty({ collapsed = false }: { collapsed?: boolean }) {
  const [addProjectOpen, setAddProjectOpen] = useState(false);

  if (collapsed) {
    return (
      <aside className="project-sidebar project-sidebar--collapsed flex h-full flex-col items-center gap-1 py-2">
        <button
          type="button"
          className="project-sidebar__add-btn"
          aria-label="New project"
          onClick={() => setAddProjectOpen(true)}
        >
          <svg
            aria-hidden="true"
            fill="none"
            stroke="currentColor"
            strokeWidth="2"
            viewBox="0 0 24 24"
          >
            <path d="M12 5v14M5 12h14" />
          </svg>
        </button>
        <AddProjectModal open={addProjectOpen} onClose={() => setAddProjectOpen(false)} />
      </aside>
    );
  }

  return (
    <aside className="project-sidebar flex h-full flex-col">
      <div className="project-sidebar__compact-hdr">
        <span className="project-sidebar__sect-label">Projects</span>
        <button
          type="button"
          className="project-sidebar__add-btn"
          aria-label="New project"
          onClick={() => setAddProjectOpen(true)}
        >
          <svg
            aria-hidden="true"
            fill="none"
            stroke="currentColor"
            strokeWidth="2"
            viewBox="0 0 24 24"
          >
            <path d="M12 5v14M5 12h14" />
          </svg>
        </button>
      </div>
      <div className="project-sidebar__empty flex-1 text-[var(--color-text-tertiary)]">
        No projects yet. Click + to add one.
      </div>
      <div className="project-sidebar__footer">
        <div className="flex items-center justify-end gap-1 border-t border-[var(--color-border-subtle)] px-2 py-2">
          <ThemeToggle className="project-sidebar__theme-toggle" />
        </div>
      </div>
      <AddProjectModal open={addProjectOpen} onClose={() => setAddProjectOpen(false)} />
    </aside>
  );
}

function ProjectSidebarInner({
  projects,
  sessions,
  orchestrators,
  activeProjectId,
  activeSessionId,
  loading = false,
  error = false,
  onRetry,
  collapsed = false,
  onToggleCollapsed: _onToggleCollapsed,
  onMobileClose,
}: ProjectSidebarProps) {
  const router = useRouter();
  const _isLoading = loading || sessions === null;

  const [expandedProjects, setExpandedProjects] = useState<Set<string>>(
    () =>
      loadExpandedProjects() ??
      new Set(activeProjectId && activeProjectId !== "all" ? [activeProjectId] : []),
  );
  const [showKilled, setShowKilled] = useState<boolean>(loadShowKilled);
  const [showDone, setShowDone] = useState<boolean>(loadShowDone);
  const [showSessionId, setShowSessionId] = useState<boolean>(loadShowSessionId);
  // Inline session-rename state. Only one row is edited at a time. `pendingRenames`
  // mirrors the in-flight / just-saved value so the new label appears immediately
  // without waiting for the next SSE refresh.
  const [editingSessionId, setEditingSessionId] = useState<string | null>(null);
  const [editingValue, setEditingValue] = useState("");
  const [pendingRenames, setPendingRenames] = useState<Map<string, string>>(new Map());
  const [settingsOpen, setSettingsOpen] = useState(false);
  const [projectMenuOpenId, setProjectMenuOpenId] = useState<string | null>(null);
  const [projectSettingsProjectId, setProjectSettingsProjectId] = useState<string | null>(null);
  const [deletingProjectId, setDeletingProjectId] = useState<string | null>(null);
  const [removedProjectIds, setRemovedProjectIds] = useState<Set<string>>(new Set());
  const [addProjectOpen, setAddProjectOpen] = useState(false);
  const settingsRef = useRef<HTMLDivElement>(null);
  const settingsPopoverRef = useRef<HTMLDivElement>(null);
  const projectMenuRef = useRef<HTMLDivElement>(null);
  const projectMenuPopoverRef = useRef<HTMLDivElement>(null);
  usePopoverClamp(settingsOpen, settingsPopoverRef);
  usePopoverClamp(Boolean(projectMenuOpenId), projectMenuPopoverRef);

  // Persist the session-id preference across reloads.
  useEffect(() => {
    try {
      window.localStorage.setItem(SHOW_SESSION_ID_KEY, String(showSessionId));
    } catch {
      // localStorage unavailable — accept the in-memory state for this session.
    }
  }, [showSessionId]);

  useEffect(() => {
    try {
      window.sessionStorage.setItem(SHOW_KILLED_KEY, String(showKilled));
    } catch {
      // sessionStorage unavailable — accept in-memory state.
    }
  }, [showKilled]);

  useEffect(() => {
    try {
      window.sessionStorage.setItem(SHOW_DONE_KEY, String(showDone));
    } catch {
      // sessionStorage unavailable — accept in-memory state.
    }
  }, [showDone]);

  useEffect(() => {
    try {
      window.sessionStorage.setItem(EXPANDED_PROJECTS_KEY, JSON.stringify([...expandedProjects]));
    } catch {
      // sessionStorage unavailable — accept in-memory state.
    }
  }, [expandedProjects]);

  // Close the settings popover on outside click or Escape.
  useEffect(() => {
    if (!settingsOpen) return;
    const handlePointer = (e: MouseEvent) => {
      if (settingsRef.current && !settingsRef.current.contains(e.target as Node)) {
        setSettingsOpen(false);
      }
    };
    const handleKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") setSettingsOpen(false);
    };
    document.addEventListener("mousedown", handlePointer);
    document.addEventListener("keydown", handleKey);
    return () => {
      document.removeEventListener("mousedown", handlePointer);
      document.removeEventListener("keydown", handleKey);
    };
  }, [settingsOpen]);

  useEffect(() => {
    if (!projectMenuOpenId) return;
    const handlePointer = (e: MouseEvent) => {
      if (projectMenuRef.current && !projectMenuRef.current.contains(e.target as Node)) {
        setProjectMenuOpenId(null);
      }
    };
    const handleKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") setProjectMenuOpenId(null);
    };
    document.addEventListener("mousedown", handlePointer);
    document.addEventListener("keydown", handleKey);
    return () => {
      document.removeEventListener("mousedown", handlePointer);
      document.removeEventListener("keydown", handleKey);
    };
  }, [projectMenuOpenId]);

  useEffect(() => {
    if (activeProjectId && activeProjectId !== "all") {
      setExpandedProjects((prev) => new Set([...prev, activeProjectId]));
    }
  }, [activeProjectId]);

  useEffect(() => {
    setRemovedProjectIds((prev) => {
      if (prev.size === 0) return prev;
      const next = new Set(
        [...prev].filter((projectId) => !projects.some((project) => project.id === projectId)),
      );
      return next.size === prev.size ? prev : next;
    });
  }, [projects]);

  const visibleProjects = useMemo(
    () => projects.filter((project) => !removedProjectIds.has(project.id)),
    [projects, removedProjectIds],
  );

  const prefixByProject = useMemo(
    () => new Map(visibleProjects.map((p) => [p.id, p.sessionPrefix ?? p.id])),
    [visibleProjects],
  );

  const allPrefixes = useMemo(
    () => visibleProjects.map((p) => p.sessionPrefix ?? p.id),
    [visibleProjects],
  );

  const orchestratorByProject = useMemo(
    () => new Map((orchestrators ?? []).map((o) => [o.projectId, o])),
    [orchestrators],
  );

  // Stable ref so sessionsByProject can read latest sessions without depending
  // on the array reference (which changes every SSE tick even when content is unchanged).
  const sessionsRef = useRef(sessions);
  sessionsRef.current = sessions;

  // Content-based key — only changes when session IDs, statuses, or projects change.
  // Used as the sole sessions-related dependency of sessionsByProject below.
  const sessionsKey = useMemo(
    () =>
      (sessions ?? [])
        .map(
          (s) =>
            `${s.id}:${s.status}:${s.activity ?? ""}:${s.projectId}:${s.displayName ?? ""}:${s.displayNameUserSet ? "1" : "0"}:${s.branch ?? ""}:${s.issueTitle ?? ""}:${s.pr?.title ?? ""}:${s.summary ?? ""}`,
        )
        .join("|"),
    [sessions],
  );

  const sessionsByProject = useMemo(() => {
    const map = new Map<string, DashboardSession[]>();
    // Build a set of valid project IDs to filter sessions strictly
    const validProjectIds = new Set(visibleProjects.map((p) => p.id));

    // Read via ref so this memo only reruns when sessionsKey changes (content
    // changed), not when sessions gets a new array reference with identical data.
    for (const s of sessionsRef.current ?? []) {
      // Only include sessions whose projectId matches a configured project
      if (!validProjectIds.has(s.projectId)) continue;
      if (isOrchestratorSession(s, prefixByProject.get(s.projectId), allPrefixes)) continue;
      // Keep terminal sessions visible when they still need human attention.
      // Otherwise ACTION-column cards disappear from the sidebar just because
      // their runtime has ended.
      const level = getAttentionLevel(s);
      if (level === "done") {
        if (s.status === "killed" ? !showKilled && !showDone : !showDone) continue;
      }
      const list = map.get(s.projectId) ?? [];
      list.push(s);
      map.set(s.projectId, list);
    }
    return map;
  }, [sessionsKey, prefixByProject, allPrefixes, visibleProjects, showKilled, showDone]);


  // Clear an optimistic rename once the prop session.displayName catches up.
  // Without this, we'd keep masking the server value forever after a save.
  useEffect(() => {
    if (pendingRenames.size === 0 || !sessions) return;
    const next = new Map(pendingRenames);
    let changed = false;
    for (const session of sessions) {
      const pending = next.get(session.id);
      if (pending !== undefined && (session.displayName ?? "") === pending) {
        next.delete(session.id);
        changed = true;
      }
    }
    if (changed) setPendingRenames(next);
  }, [sessions, pendingRenames]);

  const pendingRenamesRef = useRef(pendingRenames);
  pendingRenamesRef.current = pendingRenames;

  const startRename = useCallback(
    (session: DashboardSession, currentTitle: string) => {
      // Prefer the in-flight optimistic value over the prop — if the user opens
      // rename while a previous PATCH is still propagating, the prop still shows
      // the pre-rename value but we want the input to start from the latest.
      // Auto-derived displayName isn't pre-filled (user-set flag absent) — start
      // from the live title so the user types over the visible label.
      const pending = pendingRenamesRef.current.get(session.id);
      const initial = pending ?? (session.displayNameUserSet ? (session.displayName ?? "") : "");
      setEditingSessionId(session.id);
      setEditingValue(initial || currentTitle);
    },
    [],
  );

  const cancelRename = () => {
    setEditingSessionId(null);
    setEditingValue("");
  };

  const submitRename = async (sessionId: string) => {
    // Guard against double-submit. submitRename is wired to both Enter (which
    // unmounts the input) and onBlur (which can fire during that unmount in
    // some browsers); without this, both paths would fire a PATCH.
    if (editingSessionId !== sessionId) return;
    // Trim, but allow empty — empty means "revert to default" on the server.
    const next = editingValue.trim();
    setEditingSessionId(null);
    setEditingValue("");
    setPendingRenames((prev) => {
      const map = new Map(prev);
      map.set(sessionId, next);
      return map;
    });
    try {
      const res = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ displayName: next === "" ? null : next }),
      });
      if (!res.ok) {
        const body = (await res.json().catch(() => null)) as { error?: string } | null;
        throw new Error(body?.error ?? "Failed to rename session");
      }
    } catch {
      // Roll back the optimistic update so the row reverts to the prop value.
      // The user sees the original name return — no further notification is
      // needed for this niche failure path.
      setPendingRenames((prev) => {
        const map = new Map(prev);
        map.delete(sessionId);
        return map;
      });
    }
  };

  const navigate = useCallback(
    (url: string, session?: DashboardSession) => {
      if (session) {
        try {
          sessionStorage.setItem(`ao-session-nav:${session.id}`, JSON.stringify(session));
        } catch {
          // sessionStorage unavailable — silent fallback
        }
      }
      router.push(url);
      onMobileClose?.();
    },
    [router, onMobileClose],
  );

  const toggleExpand = (projectId: string) => {
    setExpandedProjects((prev) => {
      const next = new Set(prev);
      if (next.has(projectId)) {
        next.delete(projectId);
      } else {
        next.add(projectId);
      }
      return next;
    });
  };

  const handleRemoveProject = async (project: ProjectInfo) => {
    const confirmed = window.confirm(
      `Remove project ${project.name} from AO? This clears its AO sessions/history and removes it from the portfolio, but keeps the repository folder on disk.`,
    );
    if (!confirmed) return;

    setDeletingProjectId(project.id);
    try {
      const response = await fetch(`/api/projects/${encodeURIComponent(project.id)}`, {
        method: "DELETE",
      });
      const body = await response.json().catch(() => null);
      if (!response.ok) {
        throw new Error(
          (body && typeof body === "object" && "error" in body && typeof body.error === "string"
            ? body.error
            : null) ?? "Failed to remove project.",
        );
      }

      setRemovedProjectIds((prev) => new Set(prev).add(project.id));
      setExpandedProjects((prev) => {
        const next = new Set(prev);
        next.delete(project.id);
        return next;
      });
      setProjectMenuOpenId(null);
      if (activeProjectId === project.id) {
        router.push("/");
      } else if ("refresh" in router && typeof router.refresh === "function") {
        router.refresh();
      }
      onMobileClose?.();
    } catch (error) {
      window.alert(error instanceof Error ? error.message : "Failed to remove project.");
    } finally {
      setDeletingProjectId(null);
    }
  };

  if (collapsed) {
    return (
      <aside
        className={cn(
          "project-sidebar project-sidebar--collapsed flex flex-col h-full items-center py-2 gap-1 overflow-y-auto",
        )}
      >
        {visibleProjects.map((project, idx) => {
          const workerSessions = sessionsByProject.get(project.id) ?? [];
          // sessionsByProject already applies the showDone filter consistently.
          const visibleSessions = workerSessions;
          const projectAbbr = project.name.slice(0, 2).toUpperCase();
          return (
            <div key={project.id} className="flex flex-col items-center gap-0.5 w-full px-1">
              {idx > 0 && <div className="project-sidebar__collapsed-divider" aria-hidden="true" />}
              <a
                href={projectDashboardPath(project.id)}
                className={cn(
                  "project-sidebar__collapsed-icon",
                  activeProjectId === project.id && "project-sidebar__collapsed-icon--active",
                )}
                title={project.name}
                aria-label={project.name}
              >
                <span className="project-sidebar__collapsed-abbr">{projectAbbr}</span>
              </a>
              {visibleSessions.slice(0, 5).map((session) => {
                const level = getAttentionLevel(session);
                const rawTitle = session.branch ?? getSessionTitle(session);
                const displayTitle = session.branch
                  ? humanizeBranch(session.branch) || rawTitle
                  : rawTitle;
                const abbr = displayTitle.replace(/\s+/g, "").slice(0, 3).toUpperCase();
                const isActive = activeSessionId === session.id;
                const sessionHref = projectSessionPath(project.id, session.id);
                return (
                  <a
                    key={session.id}
                    href={sessionHref}
                    onClick={(e) => {
                      if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
                      e.preventDefault();
                      navigate(sessionHref, session);
                    }}
                    className={cn(
                      "project-sidebar__collapsed-session-btn",
                      isActive && "project-sidebar__collapsed-session-btn--active",
                    )}
                    data-level={level}
                    title={rawTitle}
                    aria-label={rawTitle}
                  >
                    <span className="project-sidebar__session-abbr-first">{abbr[0]}</span>
                    <span className="project-sidebar__session-abbr-rest">{abbr.slice(1)}</span>
                  </a>
                );
              })}
              {visibleSessions.length > 5 && (
                <span className="project-sidebar__collapsed-overflow">
                  +{visibleSessions.length - 5}
                </span>
              )}
            </div>
          );
        })}
      </aside>
    );
  }

  return (
    <aside className="project-sidebar flex h-full flex-col">
      <div className="project-sidebar__compact-hdr">
        <span className="project-sidebar__sect-label">Projects</span>
        <button
          type="button"
          className="project-sidebar__add-btn"
          aria-label="New project"
          onClick={() => setAddProjectOpen(true)}
        >
          <svg
            aria-hidden="true"
            fill="none"
            stroke="currentColor"
            strokeWidth="2"
            viewBox="0 0 24 24"
          >
            <path d="M12 5v14M5 12h14" />
          </svg>
        </button>
      </div>

      {/* Stale-data banner: keep cached sessions visible on fetch failure but
            surface the error so users know the list may be out of date. */}
      {error && sessions && sessions.length > 0 ? (
        <div
          role="status"
          className="mx-3 mb-2 flex items-center justify-between gap-2 rounded-md border border-[var(--color-border-strong)] bg-[var(--color-bg-primary)] px-2 py-1.5 text-[11px] text-[var(--color-text-tertiary)]"
        >
          <span>Failed to refresh · showing cached sessions</span>
          {onRetry ? (
            <button
              type="button"
              onClick={onRetry}
              className="font-medium text-[var(--color-link)] hover:underline"
            >
              Retry
            </button>
          ) : null}
        </div>
      ) : null}

      {/* Project tree */}
      <div className="project-sidebar__tree flex-1 overflow-y-auto overflow-x-hidden">
        {sessions === null ? (
          <div className="space-y-1 px-3 py-3" aria-label="Loading projects">
            {Array.from({ length: 4 }, (_, i) => (
              <div key={i} className="flex items-center gap-2 py-1">
                <div className="h-2 w-2 shrink-0 animate-pulse rounded-full bg-[var(--color-border-strong)]" />
                <div className="h-3 flex-1 animate-pulse rounded bg-[var(--color-bg-primary)]" />
              </div>
            ))}
          </div>
        ) : null}
        {visibleProjects.map((project) => {
          const workerSessions = sessionsByProject.get(project.id) ?? [];
          const isExpanded = expandedProjects.has(project.id);
          const isActive = activeProjectId === project.id;
          const isDegraded = Boolean(project.resolveError);
          const projectHref = projectDashboardPath(project.id);
          // sessionsByProject already applies the showDone filter consistently.
          const visibleSessions = workerSessions;
          const hasActiveSessions = visibleSessions.length > 0;
          const orchestratorLink = orchestratorByProject.get(project.id) ?? null;
          // Look up the full session object so navigate() can cache it in
          // sessionStorage — prevents the "Session unavailable" flash on
          // first load. Orchestrators are filtered out of sessionsByProject
          // but still present in the raw sessions prop.
          const orchestratorSession = orchestratorLink
            ? (sessions?.find((s) => s.id === orchestratorLink.id) ?? null)
            : null;

          return (
            <div key={project.id} className="project-sidebar__project">
              {/* Project row: toggle + action buttons */}
              <div className="project-sidebar__proj-row flex items-center">
                {isDegraded ? (
                  <a
                    href={projectHref}
                    onClick={(e) => {
                      if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
                      e.preventDefault();
                      navigate(projectHref);
                    }}
                    className={cn(
                      "project-sidebar__proj-toggle project-sidebar__proj-toggle--link project-sidebar__proj-toggle--degraded",
                      isActive && "project-sidebar__proj-toggle--active",
                    )}
                    aria-current={isActive ? "page" : undefined}
                  >
                    <svg
                      className="project-sidebar__proj-chevron project-sidebar__proj-chevron--degraded"
                      width="10"
                      height="10"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2"
                      viewBox="0 0 24 24"
                      aria-hidden="true"
                    >
                      <path d="M12 9v4" />
                      <path d="M12 17h.01" />
                      <path d="M10.3 3.86 1.82 18a2 2 0 0 0 1.72 3h16.92a2 2 0 0 0 1.72-3L13.7 3.86a2 2 0 0 0-3.4 0Z" />
                    </svg>
                    <span className="project-sidebar__proj-name">{project.name}</span>
                    <span className="project-sidebar__proj-badge project-sidebar__proj-badge--degraded">
                      degraded
                    </span>
                  </a>
                ) : (
                  <button
                    type="button"
                    onClick={() => toggleExpand(project.id)}
                    className={cn(
                      "project-sidebar__proj-toggle",
                      isActive && "project-sidebar__proj-toggle--active",
                    )}
                    aria-expanded={isExpanded}
                    aria-current={isActive ? "page" : undefined}
                  >
                    <svg
                      className={cn(
                        "project-sidebar__proj-chevron",
                        isExpanded && "project-sidebar__proj-chevron--open",
                      )}
                      width="10"
                      height="10"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2.5"
                      viewBox="0 0 24 24"
                    >
                      <path d="m9 18 6-6-6-6" />
                    </svg>
                    <span className="project-sidebar__proj-name">{project.name}</span>
                    <span
                      className={cn(
                        "project-sidebar__proj-badge",
                        hasActiveSessions && "project-sidebar__proj-badge--active",
                      )}
                    >
                      {sessionsByProject.get(project.id)?.length ?? 0}
                    </span>
                  </button>
                )}

                {/* Dashboard button */}
                {!isDegraded ? (
                  <Link
                    href={projectHref}
                    prefetch={false}
                    onClick={(e) => {
                      e.stopPropagation();
                      onMobileClose?.();
                    }}
                    className="project-sidebar__proj-action"
                    aria-label={`Open ${project.name} dashboard`}
                    title="Dashboard"
                  >
                    <svg
                      width="12"
                      height="12"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2"
                      viewBox="0 0 24 24"
                    >
                      <path d="M3 13h8V3H3zm10 8h8V11h-8zM3 21h8v-6H3zm10-10h8V3h-8z" />
                    </svg>
                  </Link>
                ) : null}

                {!isDegraded ? (
                  <Link
                    href={projectReviewPath(project.id)}
                    onClick={(e) => {
                      e.stopPropagation();
                      onMobileClose?.();
                    }}
                    className="project-sidebar__proj-action"
                    aria-label={`Open ${project.name} reviews`}
                    title="Reviews"
                  >
                    <svg
                      width="12"
                      height="12"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2"
                      viewBox="0 0 24 24"
                    >
                      <path d="M9 11l2 2 4-4" />
                      <path d="M5 4h14v16H5z" />
                    </svg>
                  </Link>
                ) : null}

                {!isDegraded && orchestratorLink && (
                  <a
                    href={projectSessionPath(project.id, orchestratorLink.id)}
                    onClick={(e) => {
                      if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
                      e.preventDefault();
                      e.stopPropagation();
                      navigate(
                        projectSessionPath(project.id, orchestratorLink.id),
                        orchestratorSession ?? undefined,
                      );
                    }}
                    className="project-sidebar__proj-action"
                    aria-label={`Open ${project.name} orchestrator`}
                    title="Orchestrator"
                  >
                    <svg
                      width="12"
                      height="12"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2"
                      viewBox="0 0 24 24"
                    >
                      <circle cx="12" cy="5" r="2" fill="currentColor" stroke="none" />
                      <path d="M12 7v4M12 11H6M12 11h6M6 11v3M12 11v3M18 11v3" />
                      <circle cx="6" cy="17" r="2" />
                      <circle cx="12" cy="17" r="2" />
                      <circle cx="18" cy="17" r="2" />
                    </svg>
                  </a>
                )}

                <div
                  className="project-sidebar__proj-menu"
                  ref={projectMenuOpenId === project.id ? projectMenuRef : undefined}
                >
                  <button
                    type="button"
                    onClick={(e) => {
                      e.stopPropagation();
                      setProjectMenuOpenId((current) =>
                        current === project.id ? null : project.id,
                      );
                    }}
                    className="project-sidebar__proj-action project-sidebar__proj-action--menu"
                    aria-label={`Project actions for ${project.name}`}
                    aria-expanded={projectMenuOpenId === project.id}
                    aria-haspopup="menu"
                    title="Project actions"
                  >
                    <svg
                      width="12"
                      height="12"
                      fill="currentColor"
                      viewBox="0 0 24 24"
                      aria-hidden="true"
                    >
                      <circle cx="12" cy="5" r="1.75" />
                      <circle cx="12" cy="12" r="1.75" />
                      <circle cx="12" cy="19" r="1.75" />
                    </svg>
                  </button>
                  {projectMenuOpenId === project.id ? (
                    <div
                      ref={projectMenuPopoverRef}
                      className="project-sidebar__proj-menu-popover"
                      role="menu"
                      aria-label={`${project.name} actions`}
                    >
                      {orchestratorLink ? (
                        <button
                          type="button"
                          className="project-sidebar__proj-menu-item"
                          role="menuitem"
                          onClick={() => {
                            setProjectMenuOpenId(null);
                            navigate(
                              projectSessionPath(project.id, orchestratorLink.id),
                              orchestratorSession ?? undefined,
                            );
                          }}
                        >
                          Open orchestrator
                        </button>
                      ) : null}
                      <button
                        type="button"
                        className="project-sidebar__proj-menu-item"
                        role="menuitem"
                        onClick={() => {
                          setProjectMenuOpenId(null);
                          setProjectSettingsProjectId(project.id);
                        }}
                      >
                        Project settings
                      </button>
                      <button
                        type="button"
                        className="project-sidebar__proj-menu-item project-sidebar__proj-menu-item--danger"
                        role="menuitem"
                        onClick={() => void handleRemoveProject(project)}
                        disabled={deletingProjectId === project.id}
                      >
                        {deletingProjectId === project.id ? "Removing..." : "Remove project"}
                      </button>
                    </div>
                  ) : null}
                </div>
              </div>

              {isDegraded ? (
                <div className="project-sidebar__degraded-note">Config needs repair</div>
              ) : null}

              {/* Sessions */}
              {!isDegraded && isExpanded && (
                <div className="project-sidebar__sessions">
                  {sessions === null ? (
                    <div className="space-y-2 px-3 py-2" aria-label="Loading sessions">
                      {Array.from({ length: 3 }, (_, index) => (
                        <div
                          key={`${project.id}-loading-${index}`}
                          className="flex items-center gap-3 border border-[var(--color-border-subtle)] bg-[var(--color-bg-surface)] px-2 py-2"
                        >
                          <div className="h-2 w-2 shrink-0 animate-pulse bg-[var(--color-border-strong)]" />
                          <div className="h-3 flex-1 animate-pulse bg-[var(--color-bg-primary)]" />
                          <div className="h-3 w-12 animate-pulse bg-[var(--color-bg-primary)]" />
                        </div>
                      ))}
                    </div>
                  ) : visibleSessions.length > 0 ? (
                    visibleSessions.map((session) => {
                      const level = getAttentionLevel(session);
                      const isSessionActive = activeSessionId === session.id;
                      const isEditing = editingSessionId === session.id;
                      if (isEditing) {
                        return (
                          <div
                            key={session.id}
                            className={cn(
                              "project-sidebar__sess-row",
                              isSessionActive && "project-sidebar__sess-row--active",
                            )}
                            data-editing="true"
                          >
                            <SessionDot level={level} />
                            <input
                              type="text"
                              autoFocus
                              value={editingValue}
                              onChange={(e) => setEditingValue(e.target.value)}
                              onKeyDown={(e) => {
                                if (e.key === "Enter") {
                                  e.preventDefault();
                                  void submitRename(session.id);
                                } else if (e.key === "Escape") {
                                  e.preventDefault();
                                  cancelRename();
                                }
                              }}
                              onFocus={(e) => e.currentTarget.select()}
                              onBlur={() => void submitRename(session.id)}
                              maxLength={80}
                              aria-label={`Rename ${session.id}`}
                              className="project-sidebar__sess-rename-input"
                            />
                          </div>
                        );
                      }
                      return (
                        <SessionRow
                          key={session.id}
                          session={session}
                          level={level}
                          isActive={isSessionActive}
                          showSessionId={showSessionId}
                          pendingRename={pendingRenames.get(session.id)}
                          onNavigate={navigate}
                          onStartRename={startRename}
                        />
                      );
                    })
                  ) : error ? (
                    <div className="px-3 py-2">
                      <div className="project-sidebar__empty">Failed to load sessions</div>
                      <button
                        type="button"
                        className="mt-2 text-xs font-medium text-[var(--color-link)] hover:underline"
                        onClick={onRetry}
                      >
                        Retry
                      </button>
                    </div>
                  ) : (
                    <div className="project-sidebar__empty">
                      No active sessions
                    </div>
                  )}
                </div>
              )}
            </div>
          );
        })}
      </div>
      <div className="project-sidebar__footer">
        <div className="flex items-center gap-1 border-t border-[var(--color-border-subtle)] px-2 py-2">
          {/* Show killed toggle */}
          <button
            type="button"
            onClick={() => setShowKilled(!showKilled)}
            className={cn(
              "project-sidebar__footer-btn",
              showKilled && "project-sidebar__footer-btn--active",
            )}
            aria-pressed={showKilled}
            title={showKilled ? "Hide killed sessions" : "Show killed sessions"}
            aria-label={showKilled ? "Hide killed sessions" : "Show killed sessions"}
          >
            {/* skull / terminated icon */}
            <svg
              width="13"
              height="13"
              fill="none"
              stroke="currentColor"
              strokeWidth="1.75"
              viewBox="0 0 24 24"
              aria-hidden="true"
            >
              <path d="M12 3C7.03 3 3 7.03 3 12c0 3.1 1.5 5.84 3.8 7.55V21h2.4v-1h1.6v1h2.4v-1h1.6v1H17v-1.45A9 9 0 0 0 21 12c0-4.97-4.03-9-9-9z" />
              <circle cx="9" cy="11" r="1.5" fill="currentColor" stroke="none" />
              <circle cx="15" cy="11" r="1.5" fill="currentColor" stroke="none" />
            </svg>
          </button>
          {/* Show done toggle */}
          <button
            type="button"
            onClick={() => setShowDone(!showDone)}
            className={cn(
              "project-sidebar__footer-btn",
              showDone && "project-sidebar__footer-btn--active",
            )}
            aria-pressed={showDone}
            title={showDone ? "Hide completed sessions" : "Show completed sessions"}
            aria-label={showDone ? "Hide completed sessions" : "Show completed sessions"}
          >
            {/* checkmark / done icon */}
            <svg
              width="13"
              height="13"
              fill="none"
              stroke="currentColor"
              strokeWidth="2"
              viewBox="0 0 24 24"
              aria-hidden="true"
            >
              <path d="M20 6 9 17l-5-5" />
            </svg>
          </button>
          <div className="flex-1" />
          {/* Sidebar display settings (gear) */}
          <div className="project-sidebar__settings-wrap" ref={settingsRef}>
            <button
              type="button"
              onClick={() => setSettingsOpen((v) => !v)}
              className={cn(
                "project-sidebar__footer-btn",
                settingsOpen && "project-sidebar__footer-btn--active",
              )}
              aria-expanded={settingsOpen}
              aria-haspopup="dialog"
              title="Sidebar settings"
              aria-label="Sidebar settings"
            >
              <svg
                width="13"
                height="13"
                fill="none"
                stroke="currentColor"
                strokeWidth="1.75"
                viewBox="0 0 24 24"
                aria-hidden="true"
              >
                <circle cx="12" cy="12" r="3" />
                <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
              </svg>
            </button>
            {settingsOpen ? (
              <div
                ref={settingsPopoverRef}
                className="project-sidebar__settings-popover"
                role="dialog"
                aria-label="Sidebar settings"
              >
                <label className="project-sidebar__settings-row">
                  <input
                    type="checkbox"
                    checked={showSessionId}
                    onChange={(e) => setShowSessionId(e.target.checked)}
                  />
                  <span>Show session ID</span>
                </label>
              </div>
            ) : null}
          </div>
          <ThemeToggle className="project-sidebar__theme-toggle" />
        </div>
      </div>
      <AddProjectModal open={addProjectOpen} onClose={() => setAddProjectOpen(false)} />
      <ProjectSettingsModal
        open={projectSettingsProjectId !== null}
        projectId={projectSettingsProjectId}
        onClose={() => setProjectSettingsProjectId(null)}
      />
    </aside>
  );
}
