"use client";

/**
 * Plan tab — the home of the project's thinking.
 *
 * Sub-areas (always visible, even when empty, so a founder learns the
 * model on a brand-new project):
 *   1. Vision    — the elevator pitch + audience
 *   2. Ideas     — the "park-it" bin for raw thoughts
 *   3. Tasks     — what needs to happen next (open / done)
 *   4. Decisions — log of "we chose X over Y because Z"
 *
 * Same UI rhythm as Infrastructure: section tiles in a horizontal bar
 * (with counts), full-width detail panel below. Everything writes through
 * `/api/projects/[projectId]/plan` and persists under
 * `fs_projects.data.plan`. The AI chat reads the plan as context so
 * decisions don't get re-litigated.
 */

import { useCallback, useEffect, useState } from "react";
import { useParams } from "next/navigation";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import {
  Loader2,
  AlertCircle,
  MessageSquare,
  ListTodo,
  GitBranch,
  Target,
  Plus,
  Trash2,
  Check,
  RotateCcw,
  Pencil,
  X,
  Eye,
  FileText,
} from "lucide-react";

interface Idea {
  id: string;
  text: string;
  createdAt: string;
}
type TaskStatus = "open" | "in_progress" | "review" | "done" | "blocked";
interface Task {
  id: string;
  title: string;
  description?: string;
  status: TaskStatus;
  agent?: {
    runId: string;
    startedAt: string;
    finishedAt?: string;
    status: "queued" | "running" | "succeeded" | "failed";
  } | null;
  createdAt: string;
  startedAt?: string;
  doneAt?: string;
  // Legacy single-line tasks (pre-markdown migration) carry text instead of title.
  text?: string;
}
interface Decision {
  id: string;
  title: string;
  choice: string;
  why?: string;
  createdAt: string;
}
interface Plan {
  vision?: string;
  brief?: string;
  brief_filename?: string; // stored as `vision` server-side, surfaced as "Objective" in the UI
  ideas: Idea[]; // legacy bin — no longer surfaced; kept on the model for backward compat
  tasks: Task[];
  decisions: Decision[];
}

interface Session {
  id: string;
  title: string;
  summary: string | null;
  messageCount: number;
  updatedAt: string;
  createdAt: string;
}

type Section = "objective" | "sessions" | "tasks" | "decisions";

interface SectionDef {
  key: Section;
  label: string;
  icon: React.ComponentType<{ size?: number; style?: React.CSSProperties }>;
  blurb: string;
}

const SECTIONS: SectionDef[] = [
  {
    key: "objective",
    label: "Objective",
    icon: Target,
    blurb: "What you're building, who it's for, and why.",
  },
  {
    key: "tasks",
    label: "Features",
    icon: ListTodo,
    blurb: "High-level capabilities to be delegated to the AI.",
  },
  {
    key: "decisions",
    label: "Decisions",
    icon: GitBranch,
    blurb: "Choices already made — so we stop re-litigating them.",
  },
  {
    key: "sessions",
    label: "Sessions",
    icon: MessageSquare,
    blurb: "Past chat sessions for this project.",
  },
];

// ──────────────────────────────────────────────────
// Page
// ──────────────────────────────────────────────────

export default function PlanTab() {
  const params = useParams();
  const projectId = params.projectId as string;
  const workspace = params.workspace as string;

  const [plan, setPlan] = useState<Plan | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [active, setActive] = useState<Section>("objective");
  const [sessionCount, setSessionCount] = useState(0);

  const load = useCallback(async () => {
    try {
      const res = await fetch(`/api/projects/${projectId}/plan`, {
        credentials: "include",
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const data = await res.json();
      setPlan(data.plan);
      setError(null);
    } catch (e: any) {
      setError(e?.message ?? "Failed to load plan");
    } finally {
      setLoading(false);
    }
  }, [projectId]);

  useEffect(() => {
    load();
    // Only poll the plan API periodically if we aren't currently editing or saving
    // something, otherwise we risk overwriting user input or causing layout thrashing.
    const intervalId = setInterval(load, 15000); // Backed off from 5s to 15s to reduce API spam
    return () => clearInterval(intervalId);
  }, [load]);

  if (loading && !plan) {
    return (
      <div style={pageWrap}>
        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: 10,
            color: INK.mid,
          }}
        >
          <Loader2 size={16} className="animate-spin" /> Loading plan…
        </div>
      </div>
    );
  }
  if (error || !plan) {
    return (
      <div style={pageWrap}>
        <div style={errorBox}>
          <AlertCircle
            size={14}
            style={{ marginRight: 6, verticalAlign: -2 }}
          />
          {error ?? "No plan data"}
        </div>
      </div>
    );
  }

  const counts: Record<Section, number> = {
    objective: plan.vision ? 1 : 0,
    sessions: sessionCount,
    tasks: plan.tasks.filter((t) => t.status !== "done").length,
    decisions: plan.decisions.length,
  };

  return (
    <div style={pageWrap}>
      <style jsx global>{`
        .vibn-prose {
          font-family: "Outfit", "Inter", ui-sans-serif, sans-serif;
          color: #1a1a1a;
          font-size: 0.95rem;
          line-height: 1.65;
        }
        .vibn-prose > :first-child {
          margin-top: 0;
        }
        .vibn-prose > :last-child {
          margin-bottom: 0;
        }
        .vibn-prose h1,
        .vibn-prose h2,
        .vibn-prose h3,
        .vibn-prose h4 {
          font-family: "Newsreader", "Lora", Georgia, serif;
          font-weight: 500;
          letter-spacing: -0.01em;
          margin: 1.4em 0 0.5em;
          line-height: 1.25;
          color: #1a1a1a;
        }
        .vibn-prose h1 {
          font-size: 1.7rem;
          font-weight: 400;
          letter-spacing: -0.02em;
        }
        .vibn-prose h2 {
          font-size: 1.3rem;
        }
        .vibn-prose h3 {
          font-size: 1.05rem;
        }
        .vibn-prose h4 {
          font-size: 0.95rem;
        }
        .vibn-prose p,
        .vibn-prose ul,
        .vibn-prose ol,
        .vibn-prose blockquote {
          margin: 0.75em 0;
        }
        .vibn-prose ul,
        .vibn-prose ol {
          padding-left: 1.4em;
        }
        .vibn-prose li {
          margin: 0.25em 0;
        }
        .vibn-prose li > p {
          margin: 0.25em 0;
        }
        .vibn-prose blockquote {
          border-left: 3px solid #e8e4dc;
          padding: 0.1em 0 0.1em 14px;
          color: #5f5e5a;
          font-style: italic;
        }
        .vibn-prose code {
          font-family: "JetBrains Mono", "SF Mono", ui-monospace, monospace;
          font-size: 0.85em;
          background: #f3eee4;
          padding: 1px 5px;
          border-radius: 4px;
        }
        .vibn-prose pre {
          font-family: "JetBrains Mono", "SF Mono", ui-monospace, monospace;
          font-size: 0.84rem;
          line-height: 1.55;
          background: #1a1a1a;
          color: #f6f4f0;
          padding: 14px 16px;
          border-radius: 8px;
          overflow-x: auto;
          margin: 1em 0;
        }
        .vibn-prose pre code {
          background: transparent;
          color: inherit;
          padding: 0;
          font-size: inherit;
        }
        .vibn-prose a {
          color: #1a1a1a;
          text-decoration: underline;
          text-decoration-color: #c8c2b6;
          text-underline-offset: 3px;
        }
        .vibn-prose a:hover {
          text-decoration-color: #1a1a1a;
        }
        .vibn-prose hr {
          border: none;
          border-top: 1px solid #e8e4dc;
          margin: 1.6em 0;
        }
        .vibn-prose table {
          border-collapse: collapse;
          width: 100%;
          margin: 1em 0;
          font-size: 0.88rem;
        }
        .vibn-prose th,
        .vibn-prose td {
          border: 1px solid #e8e4dc;
          padding: 8px 12px;
          text-align: left;
        }
        .vibn-prose th {
          background: #fafaf6;
          font-weight: 600;
        }
        .vibn-prose img {
          max-width: 100%;
          border-radius: 6px;
        }
      `}</style>
      <div style={planStack}>
        <nav style={planNav} aria-label="Plan sections">
          <h2 style={heading}>Plan</h2>
          <div style={tileRow}>
            {SECTIONS.map((def) => (
              <SectionTile
                key={def.key}
                def={def}
                count={counts[def.key]}
                active={active === def.key}
                onClick={() => setActive(def.key)}
              />
            ))}
          </div>
        </nav>

        <section style={planMain}>
          <div style={panel}>
            {active === "objective" && (
              <VisionPanel
                plan={plan}
                projectId={projectId}
                onChange={setPlan}
              />
            )}
            {active === "sessions" && (
              <SessionsPanel
                workspace={workspace}
                projectId={projectId}
                onCount={setSessionCount}
              />
            )}
            {active === "tasks" && (
              <TasksPanel
                plan={plan}
                projectId={projectId}
                onChange={setPlan}
              />
            )}
            {active === "decisions" && (
              <DecisionsPanel
                plan={plan}
                projectId={projectId}
                onChange={setPlan}
              />
            )}
          </div>
        </section>
      </div>
    </div>
  );
}

// ──────────────────────────────────────────────────
// Left rail tile
// ──────────────────────────────────────────────────

function SectionTile({
  def,
  count,
  active,
  onClick,
}: {
  def: SectionDef;
  count: number;
  active: boolean;
  onClick: () => void;
}) {
  const Icon = def.icon;
  return (
    <button
      type="button"
      onClick={onClick}
      style={{
        ...tileButton,
        background: active ? "#fff" : "transparent",
        borderColor: active ? INK.border : "transparent",
        boxShadow: active ? "0 1px 3px rgba(0,0,0,0.04)" : "none",
      }}
      aria-pressed={active}
    >
      <Icon size={14} style={{ color: INK.mid, flexShrink: 0 }} />
      <span style={{ ...tileLabel, color: active ? INK.ink : INK.mid }}>
        {def.label}
      </span>
      <span style={countPill}>{count}</span>
    </button>
  );
}

// ──────────────────────────────────────────────────
// Vision
// ──────────────────────────────────────────────────

function BriefPanel({
  plan,
  projectId,
  onChange,
}: {
  plan: Plan;
  projectId: string;
  onChange: (p: Plan) => void;
}) {
  const [draft, setDraft] = useState(plan.brief ?? "");
  const [saving, setSaving] = useState(false);
  const [editing, setEditing] = useState(!plan.brief);
  const [editorView, setEditorView] = useState<"write" | "preview">("write");
  const [dirty, setDirty] = useState(false);

  useEffect(() => {
    setDraft(plan.brief ?? "");
    setDirty(false);
  }, [plan.brief]);

  const handleSave = async () => {
    setSaving(true);
    try {
      const res = await fetch(`/api/projects/${projectId}/plan`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ kind: "brief", text: draft }),
      });
      if (res.ok) {
        onChange({ ...plan, brief: draft });
        setDirty(false);
        setEditing(false);
      }
    } finally {
      setSaving(false);
    }
  };

  const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    // For now we just read text files (txt, md, csv).
    // In the future you can send PDFs to an extraction API.
    const reader = new FileReader();
    reader.onload = async (event) => {
      const content = event.target?.result as string;
      setDraft(content);
      setDirty(true);
      setEditing(true);
    };
    reader.readAsText(file);
  };

  if (!editing && plan.brief) {
    return (
      <div className="vibn-enter" style={{ position: "relative" }}>
        <button
          onClick={() => setEditing(true)}
          style={{
            position: "absolute",
            top: 0,
            right: 0,
            padding: "6px 12px",
            borderRadius: 6,
            background: "transparent",
            border: "1px solid #e8e4dc",
            fontSize: "0.75rem",
            color: "#6b6560",
            cursor: "pointer",
            display: "flex",
            alignItems: "center",
            gap: 6,
          }}
        >
          <Pencil style={{ width: 12, height: 12 }} /> Edit
        </button>
        <div style={{ padding: "8px 0" }}>
          <ReactMarkdown
            remarkPlugins={[remarkGfm]}
            components={{
              p: ({ node, ...props }) => (
                <p
                  style={{
                    fontSize: "0.95rem",
                    lineHeight: 1.6,
                    color: "#1a1a1a",
                    marginBottom: 16,
                  }}
                  {...props}
                />
              ),
              h1: ({ node, ...props }) => (
                <h1
                  style={{
                    fontSize: "1.4rem",
                    fontWeight: 600,
                    color: "#1a1a1a",
                    marginTop: 24,
                    marginBottom: 16,
                  }}
                  {...props}
                />
              ),
              h2: ({ node, ...props }) => (
                <h2
                  style={{
                    fontSize: "1.2rem",
                    fontWeight: 600,
                    color: "#1a1a1a",
                    marginTop: 20,
                    marginBottom: 12,
                  }}
                  {...props}
                />
              ),
              h3: ({ node, ...props }) => (
                <h3
                  style={{
                    fontSize: "1.05rem",
                    fontWeight: 600,
                    color: "#1a1a1a",
                    marginTop: 16,
                    marginBottom: 10,
                  }}
                  {...props}
                />
              ),
              ul: ({ node, ...props }) => (
                <ul
                  style={{
                    fontSize: "0.95rem",
                    lineHeight: 1.6,
                    color: "#1a1a1a",
                    paddingLeft: 24,
                    marginBottom: 16,
                    listStyleType: "disc",
                  }}
                  {...props}
                />
              ),
              ol: ({ node, ...props }) => (
                <ol
                  style={{
                    fontSize: "0.95rem",
                    lineHeight: 1.6,
                    color: "#1a1a1a",
                    paddingLeft: 24,
                    marginBottom: 16,
                    listStyleType: "decimal",
                  }}
                  {...props}
                />
              ),
              li: ({ node, ...props }) => (
                <li style={{ marginBottom: 6 }} {...props} />
              ),
            }}
          >
            {plan.brief}
          </ReactMarkdown>
        </div>
      </div>
    );
  }

  return (
    <div className="vibn-enter">
      {!plan.brief && (
        <div style={emptyVisionBox}>
          <p
            style={{
              margin: 0,
              marginBottom: 12,
              fontSize: "0.9rem",
              color: "#444441",
              lineHeight: 1.55,
            }}
          >
            No project brief uploaded. You can paste your PRD, requirements
            document, or notes below. The AI will read this to understand the
            full scope of your project.
          </p>
          <div style={{ display: "flex", gap: "12px" }}>
            <label
              style={{
                ...primaryBtn,
                cursor: "pointer",
                display: "inline-flex",
              }}
            >
              Upload Text File (.md, .txt)
              <input
                type="file"
                accept=".txt,.md,.csv"
                style={{ display: "none" }}
                onChange={handleFileUpload}
              />
            </label>
            <button
              onClick={() => setEditing(true)}
              style={{
                padding: "8px 16px",
                borderRadius: 7,
                background: "transparent",
                color: "#1a1a1a",
                border: "1px solid #1a1a1a",
                fontSize: "0.82rem",
                fontWeight: 600,
                cursor: "pointer",
              }}
            >
              Paste Manually
            </button>
          </div>
        </div>
      )}

      {editing && (
        <div
          style={{
            background: "#fff",
            border: "1px solid #e8e4dc",
            borderRadius: 10,
            overflow: "hidden",
            marginTop: plan.brief ? 0 : 24,
          }}
        >
          <div
            style={{
              display: "flex",
              borderBottom: "1px solid #e8e4dc",
              background: "#faf8f5",
            }}
          >
            <button
              onClick={() => setEditorView("write")}
              style={{
                ...tabStyle(editorView === "write" ? "write" : ""),
                borderRadius: 0,
                border: "none",
                borderRight: "1px solid #e8e4dc",
                borderBottom:
                  editorView === "write"
                    ? "1px solid transparent"
                    : "1px solid #e8e4dc",
                background: editorView === "write" ? "#fff" : "transparent",
              }}
            >
              Write
            </button>
            <button
              onClick={() => setEditorView("preview")}
              style={{
                ...tabStyle(editorView === "preview" ? "preview" : ""),
                borderRadius: 0,
                border: "none",
                borderRight: "1px solid #e8e4dc",
                borderBottom:
                  editorView === "preview"
                    ? "1px solid transparent"
                    : "1px solid #e8e4dc",
                background: editorView === "preview" ? "#fff" : "transparent",
              }}
            >
              Preview
            </button>
            <div style={{ flex: 1, borderBottom: "1px solid #e8e4dc" }} />
          </div>

          {editorView === "write" ? (
            <textarea
              value={draft}
              onChange={(e) => {
                setDraft(e.target.value);
                setDirty(true);
              }}
              placeholder="Paste your PRD or project scope here..."
              style={{
                width: "100%",
                minHeight: 300,
                padding: 20,
                border: "none",
                outline: "none",
                fontSize: "0.9rem",
                lineHeight: 1.6,
                fontFamily: "var(--font-inter), sans-serif",
                color: "#1a1a1a",
                resize: "vertical",
              }}
            />
          ) : (
            <div
              style={{
                padding: 20,
                minHeight: 300,
                maxHeight: 500,
                overflowY: "auto",
              }}
            >
              {draft ? (
                <ReactMarkdown
                  remarkPlugins={[remarkGfm]}
                  components={{
                    p: ({ node, ...props }) => (
                      <p
                        style={{
                          fontSize: "0.95rem",
                          lineHeight: 1.6,
                          color: "#1a1a1a",
                          marginBottom: 16,
                        }}
                        {...props}
                      />
                    ),
                    h1: ({ node, ...props }) => (
                      <h1
                        style={{
                          fontSize: "1.4rem",
                          fontWeight: 600,
                          color: "#1a1a1a",
                          marginTop: 24,
                          marginBottom: 16,
                        }}
                        {...props}
                      />
                    ),
                    h2: ({ node, ...props }) => (
                      <h2
                        style={{
                          fontSize: "1.2rem",
                          fontWeight: 600,
                          color: "#1a1a1a",
                          marginTop: 20,
                          marginBottom: 12,
                        }}
                        {...props}
                      />
                    ),
                    ul: ({ node, ...props }) => (
                      <ul
                        style={{
                          fontSize: "0.95rem",
                          lineHeight: 1.6,
                          color: "#1a1a1a",
                          paddingLeft: 24,
                          marginBottom: 16,
                          listStyleType: "disc",
                        }}
                        {...props}
                      />
                    ),
                    li: ({ node, ...props }) => (
                      <li style={{ marginBottom: 6 }} {...props} />
                    ),
                  }}
                >
                  {draft}
                </ReactMarkdown>
              ) : (
                <p style={{ color: "#a09a90", fontStyle: "italic", margin: 0 }}>
                  Nothing to preview.
                </p>
              )}
            </div>
          )}

          <div
            style={{
              display: "flex",
              justifyContent: "flex-end",
              gap: 12,
              padding: "12px 20px",
              borderTop: "1px solid #e8e4dc",
              background: "#faf8f5",
            }}
          >
            {plan.brief && (
              <button
                onClick={() => {
                  setDraft(plan.brief ?? "");
                  setDirty(false);
                  setEditing(false);
                }}
                style={{
                  padding: "8px 16px",
                  borderRadius: 6,
                  background: "transparent",
                  border: "1px solid #e8e4dc",
                  color: "#6b6560",
                  fontSize: "0.82rem",
                  fontWeight: 500,
                  cursor: "pointer",
                }}
              >
                Cancel
              </button>
            )}
            <button
              onClick={handleSave}
              disabled={!dirty || saving}
              style={{ ...primaryBtn, opacity: !dirty || saving ? 0.5 : 1 }}
            >
              {saving ? (
                <Loader2
                  style={{ width: 14, height: 14 }}
                  className="animate-spin"
                />
              ) : null}
              {saving ? "Saving..." : "Save Brief"}
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

function VisionPanel({
  plan,
  projectId,
  onChange,
}: {
  plan: Plan;
  projectId: string;
  onChange: (p: Plan) => void;
}) {
  const [draft, setDraft] = useState(plan.vision ?? "");
  const [saving, setSaving] = useState(false);
  const [editing, setEditing] = useState(!plan.vision);
  // While editing, toggle between raw markdown source and rendered preview.
  const [editorView, setEditorView] = useState<"write" | "preview">("write");
  const [dirty, setDirty] = useState(false);

  useEffect(() => {
    setDraft(plan.vision ?? "");
    setDirty(false);
  }, [plan.vision]);

  const save = async () => {
    setSaving(true);
    try {
      const r = await fetch(`/api/projects/${projectId}/plan`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ kind: "vision", text: draft }),
      });
      const d = await r.json();
      if (d.plan) onChange(d.plan);
      setDirty(false);
      setEditing(false);
    } finally {
      setSaving(false);
    }
  };

  const cancel = () => {
    setDraft(plan.vision ?? "");
    setDirty(false);
    setEditing(false);
  };

  return (
    <div>
      <div style={visionHeaderRow}>
        <h3 style={panelTitle}>Objective</h3>
        {!editing && plan.vision && (
          <button onClick={() => setEditing(true)} style={ghostBtn}>
            <Pencil size={12} /> Edit
          </button>
        )}
      </div>

      {editing ? (
        <>
          <div style={editorTabs}>
            <button
              type="button"
              onClick={() => setEditorView("write")}
              style={editorView === "write" ? editorTabActive : editorTab}
            >
              <FileText size={12} /> Write
            </button>
            <button
              type="button"
              onClick={() => setEditorView("preview")}
              style={editorView === "preview" ? editorTabActive : editorTab}
            >
              <Eye size={12} /> Preview
            </button>
            <span style={{ flex: 1 }} />
            {dirty && <span style={dirtyDot}>● Unsaved</span>}
          </div>

          {editorView === "write" ? (
            <textarea
              value={draft}
              onChange={(e) => {
                setDraft(e.target.value);
                setDirty(true);
              }}
              placeholder={visionPlaceholder}
              style={visionTextarea}
              spellCheck
            />
          ) : (
            <div style={visionPreviewBox}>
              {draft.trim() ? (
                <div className="vibn-prose">
                  <ReactMarkdown remarkPlugins={[remarkGfm]}>
                    {draft}
                  </ReactMarkdown>
                </div>
              ) : (
                <div style={{ color: INK.muted, fontStyle: "italic" }}>
                  Nothing to preview yet.
                </div>
              )}
            </div>
          )}

          <div style={{ display: "flex", gap: 8, marginTop: 12 }}>
            <button
              onClick={save}
              disabled={saving || !dirty}
              style={primaryBtn}
            >
              {saving ? (
                <Loader2 size={13} className="animate-spin" />
              ) : (
                <Check size={13} />
              )}
              Save
            </button>
            <button onClick={cancel} style={ghostBtn}>
              <X size={13} /> Cancel
            </button>
          </div>
        </>
      ) : plan.vision ? (
        <div className="vibn-prose">
          <ReactMarkdown remarkPlugins={[remarkGfm]}>
            {plan.vision}
          </ReactMarkdown>
        </div>
      ) : (
        <div style={emptyVisionBox}>
          <p
            style={{
              margin: 0,
              marginBottom: 12,
              fontSize: "0.9rem",
              color: INK.mid,
              lineHeight: 1.55,
            }}
          >
            No objective yet. Write a few paragraphs about the problem, the
            people you&apos;re building it for, and the shape of the solution.
            The AI reads this on every chat so it stays anchored to your real
            intent.
          </p>
          <button
            onClick={() => {
              setEditing(true);
              setEditorView("write");
            }}
            style={primaryBtn}
          >
            <Plus size={13} /> Start writing
          </button>
        </div>
      )}
    </div>
  );
}

const visionPlaceholder = `# What we're building

A short paragraph on the problem and who feels it.

## Who it's for

Describe the user — their context, what they currently do, and why it's painful.

## What success looks like

What changes when this exists?
`;

// ──────────────────────────────────────────────────
// Sessions — read-only log of past chat threads scoped to this project.
// Title is auto-generated by the chat backend on first message; summary
// (when present) is filled in by the AI at end-of-thread. Clicking a row
// jumps the user back into that conversation in the side chat panel.
// ──────────────────────────────────────────────────

function SessionsPanel({
  workspace,
  projectId,
  onCount,
}: {
  workspace: string;
  projectId: string;
  onCount: (n: number) => void;
}) {
  const [sessions, setSessions] = useState<Session[] | null>(null);
  const [loading, setLoading] = useState(true);
  const [err, setErr] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    fetch(
      `/api/chat/threads?workspace=${encodeURIComponent(workspace)}&projectId=${encodeURIComponent(projectId)}`,
      {
        credentials: "include",
      },
    )
      .then((r) =>
        r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)),
      )
      .then((d) => {
        if (cancelled) return;
        const list: Session[] = d.threads ?? [];
        setSessions(list);
        onCount(list.length);
      })
      .catch((e) => {
        if (!cancelled) setErr(e?.message ?? "Failed to load sessions");
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [workspace, projectId, onCount]);

  return (
    <div>
      <PanelHeader
        title="Sessions"
        hint="Past chat sessions on this project. Each one captures what was attempted, decided, or shipped."
      />
      {loading ? (
        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: 8,
            color: INK.mid,
            fontSize: "0.85rem",
            padding: "8px 0",
          }}
        >
          <Loader2 size={13} className="animate-spin" /> Loading sessions…
        </div>
      ) : err ? (
        <div style={errorBox}>
          <AlertCircle
            size={13}
            style={{ marginRight: 6, verticalAlign: -2 }}
          />
          {err}
        </div>
      ) : !sessions || sessions.length === 0 ? (
        <div style={emptyBox}>
          No sessions yet.{" "}
          <span style={{ display: "block", marginTop: 8, color: INK.mid }}>
            Open the chat and ask anything — each conversation becomes a session
            entry here with an AI-generated summary.
          </span>
          <span style={promptNudge}>
            Try: &quot;What should I build first for this project?&quot;
          </span>
        </div>
      ) : (
        <ul style={list}>
          {sessions.map((s) => (
            <li key={s.id} style={{ ...sessionRow, padding: 0 }}>
              <a
                href={`/${workspace}/project/${projectId}/sessions/${s.id}`}
                style={{
                  display: "flex",
                  alignItems: "flex-start",
                  gap: 12,
                  textDecoration: "none",
                  color: "inherit",
                  width: "100%",
                  padding: "12px 14px",
                  borderRadius: 8,
                  transition: "background 0.15s",
                }}
                onMouseEnter={(e) =>
                  (e.currentTarget.style.background = "rgba(0,0,0,0.03)")
                }
                onMouseLeave={(e) =>
                  (e.currentTarget.style.background = "transparent")
                }
              >
                <MessageSquare
                  size={14}
                  style={{ color: INK.mid, marginTop: 2, flexShrink: 0 }}
                />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={sessionTitle}>{s.title}</div>
                  {s.summary && <div style={sessionSummary}>{s.summary}</div>}
                  <div style={sessionMeta}>
                    <span>
                      {s.messageCount}{" "}
                      {s.messageCount === 1 ? "message" : "messages"}
                    </span>
                    <span style={{ color: INK.muted }}>·</span>
                    <span>{relativeTime(s.updatedAt)}</span>
                  </div>
                </div>
              </a>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

// ──────────────────────────────────────────────────
// Tasks
// ──────────────────────────────────────────────────

// Tasks. Each task is a *scoped unit of work* with a markdown spec —
// a feature, a refactor, an investigation. The list-detail layout
// mirrors how engineers think: pick one off the queue, see the full
// brief, work it (or delegate it). Phase 2 will wire the "Delegate"
// button to a background agent runner that executes the task spec
// against the MCP and writes progress back here.

function taskTitle(t: Task): string {
  return (t.title || t.text || "Untitled task").trim();
}

const TASK_STATUS_LABEL: Record<TaskStatus, string> = {
  open: "Open",
  in_progress: "In progress",
  review: "In review",

  done: "Done",
  blocked: "Blocked",
};
const TASK_STATUS_COLOR: Record<TaskStatus, string> = {
  open: "#5f5e5a",
  in_progress: "#1d6cd0",
  review: "#f1a80a",

  done: "#1f8c4d",
  blocked: "#b94a3a",
};

function TasksPanel({
  plan,
  projectId,
  onChange,
}: {
  plan: Plan;
  projectId: string;
  onChange: (p: Plan) => void;
}) {
  const tasks = plan.tasks;
  const [selectedId, setSelectedId] = useState<string | null>(
    () => tasks[0]?.id ?? null,
  );
  const [creating, setCreating] = useState(false);
  const [filter, setFilter] = useState<"all" | "active" | "done">("active");

  // Keep selection valid when the underlying list changes (delete / add).
  useEffect(() => {
    if (!selectedId && tasks.length > 0) {
      setSelectedId(tasks[0].id);
      return;
    }
    if (selectedId && !tasks.some((t) => t.id === selectedId)) {
      setSelectedId(tasks[0]?.id ?? null);
    }
  }, [tasks, selectedId]);

  const visible = tasks.filter((t) => {
    if (filter === "all") return true;
    if (filter === "done") return t.status === "done";
    return t.status !== "done";
  });
  const selected = tasks.find((t) => t.id === selectedId) ?? null;

  return (
    <div>
      <div style={visionHeaderRow}>
        <h3 style={panelTitle}>Features</h3>
        <button
          onClick={() => {
            setCreating(true);
            setSelectedId(null);
          }}
          style={primaryBtn}
        >
          <Plus size={13} /> New Feature
        </button>
      </div>

      <div style={taskFilterRow}>
        {(["active", "done", "all"] as const).map((f) => (
          <button
            key={f}
            type="button"
            onClick={() => setFilter(f)}
            style={filter === f ? taskFilterPillActive : taskFilterPill}
          >
            {f === "active" ? "Active" : f === "done" ? "Done" : "All"} ·{" "}
            {f === "active"
              ? tasks.filter((t) => t.status !== "done").length
              : f === "done"
                ? tasks.filter((t) => t.status === "done").length
                : tasks.length}
          </button>
        ))}
      </div>

      <div style={taskSplit}>
        <div style={taskListCol}>
          {visible.length === 0 && !creating ? (
            <div style={{ ...emptyBox, marginTop: 0 }}>
              {tasks.length === 0 ? (
                <>
                  No features defined yet.
                  <span
                    style={{
                      display: "block",
                      marginTop: 8,
                      color: INK.mid,
                      fontSize: "0.8rem",
                    }}
                  >
                    Ask the AI to break down your objective — it will create
                    scoped features automatically.
                  </span>
                  <span style={promptNudge}>
                    Try: &quot;Draft the features for this app&quot;
                  </span>
                </>
              ) : (
                "Nothing in this view."
              )}
            </div>
          ) : (
            <div>
              {(() => {
                const groups: Record<string, typeof visible> = {};
                for (const t of visible) {
                  const title = taskTitle(t);
                  const match = title.match(/^\[(.*?)\]\s*(.*)$/);
                  const groupName = match ? match[1] : "Uncategorized";
                  const cleanTitle = match ? match[2] : title;
                  
                  if (!groups[groupName]) groups[groupName] = [];
                  groups[groupName].push({ ...t, title: cleanTitle });
                }

                const groupKeys = Object.keys(groups).sort((a, b) => {
                  if (a === "Uncategorized") return 1;
                  if (b === "Uncategorized") return -1;
                  return a.localeCompare(b);
                });

                return groupKeys.map(groupKey => (
                  <div key={groupKey} style={{ marginBottom: 16 }}>
                    <div style={{
                      fontSize: "0.75rem",
                      fontWeight: 600,
                      color: "var(--fg-mute)",
                      textTransform: "uppercase",
                      letterSpacing: "0.04em",
                      marginBottom: 8,
                      marginLeft: 2,
                    }}>
                      {groupKey}
                    </div>
                    <ul style={taskList}>
                      {groups[groupKey].map((t) => (
                        <li key={t.id}>
                          <button
                            type="button"
                            onClick={() => {
                              setSelectedId(t.id);
                              setCreating(false);
                            }}
                            style={selectedId === t.id ? taskItemActive : taskItem}
                          >
                            <div
                              style={{ display: "flex", alignItems: "center", gap: 8 }}
                            >
                              <span
                                style={{
                                  ...taskStatusDot,
                                  background: TASK_STATUS_COLOR[t.status],
                                }}
                              />
                              <span style={taskItemTitle}>{t.title}</span>
                            </div>
                            <div style={taskItemMeta}>
                              <span>{TASK_STATUS_LABEL[t.status]}</span>
                              <span style={{ color: INK.muted }}>·</span>
                              <span>
                                {relativeTime(t.doneAt ?? t.startedAt ?? t.createdAt)}
                              </span>
                            </div>
                          </button>
                        </li>
                      ))}
                    </ul>
                  </div>
                ));
              })()}
            </div>
          )}
        </div>

        <div style={taskDetailCol}>
          {creating ? (
            <TaskComposer
              projectId={projectId}
              onCancel={() => setCreating(false)}
              onCreated={(plan, newId) => {
                onChange(plan);
                setCreating(false);
                setSelectedId(newId);
              }}
            />
          ) : selected ? (
            <TaskDetail
              task={selected}
              projectId={projectId}
              onChange={onChange}
            />
          ) : (
            <div style={{ ...emptyBox, marginTop: 0 }}>
              Pick a task on the left, or create a new one.
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────
// Task detail — view + inline edit + status + delegate stub
// ─────────────────────────────────────

function TaskDetail({
  task,
  projectId,
  onChange,
}: {
  task: Task;
  projectId: string;
  onChange: (p: Plan) => void;
}) {
  const [editing, setEditing] = useState(false);
  const [titleDraft, setTitleDraft] = useState(taskTitle(task));
  const [descDraft, setDescDraft] = useState(task.description ?? "");
  const [editorView, setEditorView] = useState<"write" | "preview">("write");
  const [saving, setSaving] = useState(false);

  // Reset drafts whenever the selected task changes.
  useEffect(() => {
    setTitleDraft(taskTitle(task));
    setDescDraft(task.description ?? "");
    setEditing(false);
  }, [task.id, task.title, task.description, task]);

  const patch = async (body: Record<string, unknown>) => {
    const r = await fetch(`/api/projects/${projectId}/plan`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ kind: "task", id: task.id, ...body }),
    });
    const d = await r.json();
    if (d.plan) onChange(d.plan);
  };

  const save = async () => {
    setSaving(true);
    try {
      await patch({ title: titleDraft.trim(), description: descDraft });
      setEditing(false);
    } finally {
      setSaving(false);
    }
  };

  const remove = async () => {
    const r = await fetch(
      `/api/projects/${projectId}/plan?kind=task&id=${task.id}`,
      { method: "DELETE" },
    );
    const d = await r.json();
    if (d.plan) onChange(d.plan);
  };

  return (
    <div style={taskDetailWrap}>
      <div style={taskDetailHeader}>
        {editing ? (
          <input
            value={titleDraft}
            onChange={(e) => setTitleDraft(e.target.value)}
            placeholder="Task title"
            style={{ ...input, fontSize: "1.05rem", fontWeight: 600 }}
          />
        ) : (
          <h4 style={taskDetailTitle}>{taskTitle(task)}</h4>
        )}
        <div style={{ display: "flex", gap: 8, flexShrink: 0 }}>
          {!editing && (
            <button onClick={() => setEditing(true)} style={ghostBtn}>
              <Pencil size={12} /> Edit
            </button>
          )}
          {!editing && (
            <button onClick={remove} style={ghostBtn} title="Delete task">
              <Trash2 size={12} />
            </button>
          )}
        </div>
      </div>

      <div style={taskStatusRow}>
        <span style={taskStatusLabel}>Status</span>
        <select
          value={task.status}
          onChange={(e) => patch({ status: e.target.value })}
          style={taskStatusSelect}
        >
          <option value="open">Open</option>
          <option value="in_progress">In progress</option>
          <option value="review">In review</option>

          <option value="blocked">Blocked</option>
          <option value="done">Done</option>
        </select>
        <span style={{ flex: 1 }} />
        <button
          type="button"
          disabled={saving}
          onClick={async () => {
            if (
              !confirm(
                `Delegate feature "${task.title}" to an autonomous agent? It will execute this in the background, test it, and deploy.`,
              )
            )
              return;
            setSaving(true);
            try {
              const r = await fetch(
                `/api/projects/${projectId}/agent/sessions`,
                {
                  method: "POST",
                  headers: { "Content-Type": "application/json" },
                  body: JSON.stringify({
                    appName: "frontend",
                    appPath: ".",
                    task: `Task: ${task.title}\n\n${task.description || ""}`,
                  }),
                },
              );
              if (!r.ok) {
                const err = await r.json().catch(() => ({}));
                throw new Error(err.error || `HTTP ${r.status}`);
              }
              alert(
                "Agent spawned successfully! It is now working in the background.",
              );
            } catch (err) {
              alert(
                `Failed to spawn agent: ${err instanceof Error ? err.message : String(err)}`,
              );
            } finally {
              setSaving(false);
            }
          }}
          style={{
            ...delegateBtn,
            opacity: saving ? 0.5 : 1,
            cursor: saving ? "wait" : "pointer",
          }}
          title="Delegate this task to a background AI agent"
        >
          <Sparkle /> Delegate to agent
        </button>
      </div>

      {editing ? (
        <>
          <div style={editorTabs}>
            <button
              type="button"
              onClick={() => setEditorView("write")}
              style={editorView === "write" ? editorTabActive : editorTab}
            >
              <FileText size={12} /> Write
            </button>
            <button
              type="button"
              onClick={() => setEditorView("preview")}
              style={editorView === "preview" ? editorTabActive : editorTab}
            >
              <Eye size={12} /> Preview
            </button>
          </div>
          {editorView === "write" ? (
            <textarea
              value={descDraft}
              onChange={(e) => setDescDraft(e.target.value)}
              placeholder={taskPlaceholder}
              style={visionTextarea}
              spellCheck
            />
          ) : (
            <div style={visionPreviewBox}>
              {descDraft.trim() ? (
                <div className="vibn-prose">
                  <ReactMarkdown remarkPlugins={[remarkGfm]}>
                    {descDraft}
                  </ReactMarkdown>
                </div>
              ) : (
                <div style={{ color: INK.muted, fontStyle: "italic" }}>
                  Nothing to preview yet.
                </div>
              )}
            </div>
          )}
          <div style={{ display: "flex", gap: 8, marginTop: 12 }}>
            <button
              onClick={save}
              disabled={saving || !titleDraft.trim()}
              style={primaryBtn}
            >
              {saving ? (
                <Loader2 size={13} className="animate-spin" />
              ) : (
                <Check size={13} />
              )}{" "}
              Save
            </button>
            <button
              onClick={() => {
                setTitleDraft(taskTitle(task));
                setDescDraft(task.description ?? "");
                setEditing(false);
              }}
              style={ghostBtn}
            >
              <X size={13} /> Cancel
            </button>
          </div>
        </>
      ) : task.description?.trim() ? (
        <div className="vibn-prose">
          <ReactMarkdown remarkPlugins={[remarkGfm]}>
            {task.description}
          </ReactMarkdown>
        </div>
      ) : (
        <div style={{ ...emptyBox, marginTop: 0 }}>
          No spec yet. Click <em>Edit</em> and describe what success looks like
          for this task — context, acceptance criteria, gotchas. Markdown
          supported.
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────
// Task composer (new)
// ─────────────────────────────────────

function TaskComposer({
  projectId,
  onCancel,
  onCreated,
}: {
  projectId: string;
  onCancel: () => void;
  onCreated: (plan: Plan, taskId: string) => void;
}) {
  const [title, setTitle] = useState("");
  const [desc, setDesc] = useState("");
  const [editorView, setEditorView] = useState<"write" | "preview">("write");
  const [saving, setSaving] = useState(false);

  const create = async () => {
    if (!title.trim()) return;
    setSaving(true);
    try {
      const r = await fetch(`/api/projects/${projectId}/plan`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          kind: "task",
          title: title.trim(),
          description: desc,
        }),
      });
      const d = await r.json();
      if (d.plan?.tasks?.[0]?.id) onCreated(d.plan, d.plan.tasks[0].id);
    } finally {
      setSaving(false);
    }
  };

  return (
    <div style={taskDetailWrap}>
      <div style={taskDetailHeader}>
        <input
          autoFocus
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="What is this task? (e.g. 'Migrate auth to NextAuth v5')"
          style={{ ...input, fontSize: "1.05rem", fontWeight: 600 }}
        />
      </div>
      <div style={editorTabs}>
        <button
          type="button"
          onClick={() => setEditorView("write")}
          style={editorView === "write" ? editorTabActive : editorTab}
        >
          <FileText size={12} /> Write
        </button>
        <button
          type="button"
          onClick={() => setEditorView("preview")}
          style={editorView === "preview" ? editorTabActive : editorTab}
        >
          <Eye size={12} /> Preview
        </button>
      </div>
      {editorView === "write" ? (
        <textarea
          value={desc}
          onChange={(e) => setDesc(e.target.value)}
          placeholder={taskPlaceholder}
          style={visionTextarea}
          spellCheck
        />
      ) : (
        <div style={visionPreviewBox}>
          {desc.trim() ? (
            <div className="vibn-prose">
              <ReactMarkdown remarkPlugins={[remarkGfm]}>{desc}</ReactMarkdown>
            </div>
          ) : (
            <div style={{ color: INK.muted, fontStyle: "italic" }}>
              Nothing to preview yet.
            </div>
          )}
        </div>
      )}
      <div style={{ display: "flex", gap: 8, marginTop: 12 }}>
        <button
          onClick={create}
          disabled={saving || !title.trim()}
          style={primaryBtn}
        >
          {saving ? (
            <Loader2 size={13} className="animate-spin" />
          ) : (
            <Check size={13} />
          )}{" "}
          Create task
        </button>
        <button onClick={onCancel} style={ghostBtn}>
          <X size={13} /> Cancel
        </button>
      </div>
    </div>
  );
}

const taskPlaceholder = `## Goal

What does success look like? One paragraph.

## Context

Why this matters now. Any background the agent or future-you needs.

## Acceptance criteria

- [ ] First observable thing that proves it's done
- [ ] Second
- [ ] ...

## Notes / gotchas

Edge cases, related decisions, links.
`;

function Sparkle() {
  // tiny inline glyph — avoids pulling another lucide icon for a stub button
  return (
    <span
      aria-hidden
      style={{
        display: "inline-block",
        width: 12,
        height: 12,
        fontSize: 12,
        lineHeight: 1,
      }}
    >
      ✦
    </span>
  );
}

// ──────────────────────────────────────────────────
// Decisions
// ──────────────────────────────────────────────────

function DecisionsPanel({
  plan,
  projectId,
  onChange,
}: {
  plan: Plan;
  projectId: string;
  onChange: (p: Plan) => void;
}) {
  const [adding, setAdding] = useState(false);
  const [title, setTitle] = useState("");
  const [choice, setChoice] = useState("");
  const [why, setWhy] = useState("");
  const [saving, setSaving] = useState(false);

  const add = async () => {
    if (!title.trim() || !choice.trim()) return;
    setSaving(true);
    try {
      const r = await fetch(`/api/projects/${projectId}/plan`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          kind: "decision",
          title: title.trim(),
          choice: choice.trim(),
          why: why.trim() || undefined,
        }),
      });
      const d = await r.json();
      if (d.plan) onChange(d.plan);
      setTitle("");
      setChoice("");
      setWhy("");
      setAdding(false);
    } finally {
      setSaving(false);
    }
  };

  const remove = async (id: string) => {
    const r = await fetch(
      `/api/projects/${projectId}/plan?kind=decision&id=${id}`,
      { method: "DELETE" },
    );
    const d = await r.json();
    if (d.plan) onChange(d.plan);
  };

  return (
    <div>
      <PanelHeader
        title="Decisions"
        hint="Once a question is settled, log it here. The AI reads decisions so it stops asking you to re-decide."
      />
      {adding ? (
        <div style={decisionForm}>
          <input
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            placeholder="Question or topic (e.g. 'Database choice')"
            style={input}
          />
          <input
            value={choice}
            onChange={(e) => setChoice(e.target.value)}
            placeholder="What we chose (e.g. 'Postgres')"
            style={input}
          />
          <textarea
            value={why}
            onChange={(e) => setWhy(e.target.value)}
            placeholder="Why (optional)"
            rows={3}
            style={textarea}
          />
          <div style={{ display: "flex", gap: 8 }}>
            <button
              onClick={add}
              disabled={saving || !title.trim() || !choice.trim()}
              style={primaryBtn}
            >
              {saving ? (
                <Loader2 size={13} className="animate-spin" />
              ) : (
                <Check size={13} />
              )}{" "}
              Save
            </button>
            <button
              onClick={() => {
                setAdding(false);
                setTitle("");
                setChoice("");
                setWhy("");
              }}
              style={ghostBtn}
            >
              <X size={13} /> Cancel
            </button>
          </div>
        </div>
      ) : (
        <button onClick={() => setAdding(true)} style={primaryBtn}>
          <Plus size={13} /> Log a decision
        </button>
      )}
      {plan.decisions.length === 0 ? (
        <div style={{ ...emptyBox, marginTop: 14 }}>
          No decisions yet — the AI logs these automatically when you settle on
          something in chat.
          <span style={promptNudge}>
            Try: &quot;Which database should I use for this project?&quot;
          </span>
        </div>
      ) : (
        <ul style={{ ...list, marginTop: 14 }}>
          {plan.decisions.map((d) => (
            <li key={d.id} style={decisionRow}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={decisionTitle}>{d.title}</div>
                <div style={decisionChoice}>→ {d.choice}</div>
                {d.why && <div style={decisionWhy}>{d.why}</div>}
                <div style={{ ...timestampStyle, marginTop: 4 }}>
                  {relativeTime(d.createdAt)}
                </div>
              </div>
              <button
                onClick={() => remove(d.id)}
                style={iconBtn}
                title="Delete"
              >
                <Trash2 size={13} />
              </button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

// ──────────────────────────────────────────────────
// Bits
// ──────────────────────────────────────────────────

function PanelHeader({ title, hint }: { title: string; hint: string }) {
  return (
    <div style={{ marginBottom: 14 }}>
      <h3 style={panelTitle}>{title}</h3>
      <p style={panelHint}>{hint}</p>
    </div>
  );
}

function relativeTime(iso: string): string {
  const diff = (Date.now() - new Date(iso).getTime()) / 1000;
  if (diff < 60) return "just now";
  if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
  if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
  if (diff < 86400 * 30) return `${Math.floor(diff / 86400)}d ago`;
  return new Date(iso).toLocaleDateString();
}

// ──────────────────────────────────────────────────
// Tokens
// ──────────────────────────────────────────────────

const INK = {
  ink: "#1a1a1a",
  mid: "#5f5e5a",
  muted: "#a09a90",
  border: "#e8e4dc",
  borderSoft: "#efebe1",
  cardBg: "#fff",
  fontSans: '"Outfit", "Inter", ui-sans-serif, sans-serif',
  fontSerif: '"Newsreader", "Lora", Georgia, serif',
} as const;

const pageWrap: React.CSSProperties = {
  padding: "28px 48px 48px",
  fontFamily: INK.fontSans,
  color: INK.ink,
};
/** Vertical stack: horizontal section tabs, then full-width content. */
const planStack: React.CSSProperties = {
  display: "flex",
  flexDirection: "column",
  gap: 16,
  maxWidth: "min(1400px, 100%)",
  margin: "0 auto",
  minHeight: 0,
  flex: 1,
};
const planNav: React.CSSProperties = {
  display: "flex",
  flexWrap: "wrap",
  alignItems: "center",
  gap: "10px 14px",
  rowGap: 10,
};
const tileRow: React.CSSProperties = {
  display: "flex",
  flexWrap: "wrap",
  alignItems: "center",
  gap: 8,
  flex: "1 1 200px",
  minWidth: 0,
};
const planMain: React.CSSProperties = {
  minWidth: 0,
  display: "flex",
  flexDirection: "column",
  flex: 1,
  minHeight: 0,
};
const heading: React.CSSProperties = {
  fontSize: "0.72rem",
  fontWeight: 600,
  letterSpacing: "0.12em",
  textTransform: "uppercase",
  color: INK.muted,
  margin: 0,
  flexShrink: 0,
};
const tileButton: React.CSSProperties = {
  display: "inline-flex",
  alignItems: "center",
  gap: 8,
  width: "auto",
  padding: "8px 14px",
  border: "1px solid transparent",
  borderRadius: 8,
  cursor: "pointer",
  font: "inherit",
  color: "inherit",
  transition: "background 0.12s, border-color 0.12s",
  whiteSpace: "nowrap",
};
const tileLabel: React.CSSProperties = { fontSize: "0.82rem", fontWeight: 600 };
const countPill: React.CSSProperties = {
  fontSize: "0.7rem",
  fontWeight: 600,
  color: INK.mid,
  padding: "1px 7px",
  borderRadius: 999,
  background: "#f3eee4",
};
const panel: React.CSSProperties = {
  background: INK.cardBg,
  border: `1px solid ${INK.border}`,
  borderRadius: 10,
  padding: 22,
  flex: 1,
  minHeight: 0,
  overflowY: "auto",
};
const panelTitle: React.CSSProperties = {
  fontFamily: INK.fontSerif,
  fontSize: "1.4rem",
  fontWeight: 400,
  letterSpacing: "-0.02em",
  margin: 0,
};
const panelHint: React.CSSProperties = {
  fontSize: "0.82rem",
  color: INK.mid,
  marginTop: 4,
  lineHeight: 1.5,
};
const subHeading: React.CSSProperties = {
  fontSize: "0.68rem",
  fontWeight: 600,
  letterSpacing: "0.12em",
  textTransform: "uppercase",
  color: INK.muted,
  margin: "14px 0 8px",
};
const visionHeaderRow: React.CSSProperties = {
  display: "flex",
  alignItems: "center",
  justifyContent: "space-between",
  gap: 16,
  marginBottom: 18,
};
const editorTabs: React.CSSProperties = {
  display: "flex",
  alignItems: "center",
  gap: 4,
  borderBottom: `1px solid ${INK.border}`,
  marginBottom: 12,
  paddingBottom: 0,
};
const editorTab: React.CSSProperties = {
  display: "inline-flex",
  alignItems: "center",
  gap: 6,
  padding: "8px 12px",
  fontSize: "0.78rem",
  fontWeight: 500,
  background: "transparent",
  color: INK.mid,
  border: "none",
  borderBottom: "2px solid transparent",
  cursor: "pointer",
  fontFamily: "inherit",
  marginBottom: -1,
};
const editorTabActive: React.CSSProperties = {
  ...editorTab,
  color: INK.ink,
  fontWeight: 600,
  borderBottomColor: INK.ink,
};
const dirtyDot: React.CSSProperties = {
  fontSize: "0.72rem",
  color: "#b76b00",
  fontWeight: 500,
};
const visionTextarea: React.CSSProperties = {
  width: "100%",
  minHeight: 360,
  padding: "14px 16px",
  fontSize: "0.92rem",
  lineHeight: 1.6,
  fontFamily: '"JetBrains Mono", "SF Mono", ui-monospace, monospace',
  border: `1px solid ${INK.border}`,
  borderRadius: 8,
  background: "#fdfcf9",
  color: INK.ink,
  resize: "vertical",
  outline: "none",
  boxSizing: "border-box",
};
const visionPreviewBox: React.CSSProperties = {
  minHeight: 360,
  padding: "16px 20px",
  border: `1px solid ${INK.border}`,
  borderRadius: 8,
  background: "#fff",
  boxSizing: "border-box",
};
const emptyVisionBox: React.CSSProperties = {
  padding: "22px 24px",
  border: `1px dashed ${INK.borderSoft}`,
  borderRadius: 10,
  background: "#fafaf6",
};
const addRow: React.CSSProperties = {
  display: "flex",
  gap: 8,
  marginBottom: 14,
};
const input: React.CSSProperties = {
  flex: 1,
  padding: "9px 12px",
  fontSize: "0.88rem",
  border: `1px solid ${INK.border}`,
  borderRadius: 8,
  fontFamily: "inherit",
  background: "#fff",
  color: INK.ink,
};
const textarea: React.CSSProperties = {
  width: "100%",
  padding: "10px 12px",
  fontSize: "0.9rem",
  border: `1px solid ${INK.border}`,
  borderRadius: 8,
  fontFamily: "inherit",
  background: "#fff",
  color: INK.ink,
  resize: "vertical",
  lineHeight: 1.5,
};
const list: React.CSSProperties = {
  listStyle: "none",
  padding: 0,
  margin: 0,
  display: "flex",
  flexDirection: "column",
  border: `1px solid ${INK.borderSoft}`,
  borderRadius: 8,
  overflow: "hidden",
};
const listRow: React.CSSProperties = {
  display: "flex",
  alignItems: "center",
  gap: 10,
  padding: "11px 14px",
  fontSize: "0.88rem",
  borderBottom: `1px solid ${INK.borderSoft}`,
  background: "#fff",
};
// Tasks (master/detail)
const taskFilterRow: React.CSSProperties = {
  display: "flex",
  gap: 6,
  marginBottom: 14,
};
const taskFilterPill: React.CSSProperties = {
  padding: "5px 11px",
  fontSize: "0.74rem",
  fontWeight: 500,
  background: "transparent",
  color: INK.mid,
  border: `1px solid ${INK.border}`,
  borderRadius: 999,
  cursor: "pointer",
  fontFamily: "inherit",
};
const taskFilterPillActive: React.CSSProperties = {
  ...taskFilterPill,
  background: INK.ink,
  color: "#fff",
  borderColor: INK.ink,
};
const taskSplit: React.CSSProperties = {
  display: "grid",
  gridTemplateColumns: "minmax(220px, 280px) 1fr",
  gap: 18,
  alignItems: "stretch",
  minHeight: 320,
};
const taskListCol: React.CSSProperties = {
  minWidth: 0,
  borderRight: `1px solid ${INK.borderSoft}`,
  paddingRight: 14,
  maxHeight: 540,
  overflowY: "auto",
};
const taskDetailCol: React.CSSProperties = {
  minWidth: 0,
  paddingLeft: 4,
};
const taskList: React.CSSProperties = {
  listStyle: "none",
  padding: 0,
  margin: 0,
  display: "flex",
  flexDirection: "column",
  gap: 4,
};
const taskItem: React.CSSProperties = {
  display: "flex",
  flexDirection: "column",
  gap: 4,
  width: "100%",
  padding: "10px 12px",
  background: "transparent",
  border: `1px solid transparent`,
  borderRadius: 8,
  cursor: "pointer",
  textAlign: "left",
  fontFamily: "inherit",
  color: "inherit",
};
const taskItemActive: React.CSSProperties = {
  ...taskItem,
  background: "#fff",
  border: `1px solid ${INK.border}`,
  boxShadow: "0 1px 3px rgba(0,0,0,0.04)",
};
const taskItemTitle: React.CSSProperties = {
  fontSize: "0.88rem",
  fontWeight: 600,
  color: INK.ink,
  overflow: "hidden",
  textOverflow: "ellipsis",
  whiteSpace: "nowrap",
  flex: 1,
  minWidth: 0,
};
const taskStatusDot: React.CSSProperties = {
  display: "inline-block",
  width: 8,
  height: 8,
  borderRadius: 999,
  flexShrink: 0,
};
const taskItemMeta: React.CSSProperties = {
  display: "flex",
  gap: 6,
  alignItems: "center",
  fontSize: "0.7rem",
  color: INK.muted,
  paddingLeft: 16,
};
const taskDetailWrap: React.CSSProperties = {
  display: "flex",
  flexDirection: "column",
  minHeight: 0,
};
const taskDetailHeader: React.CSSProperties = {
  display: "flex",
  alignItems: "center",
  justifyContent: "space-between",
  gap: 12,
  marginBottom: 12,
};
const taskDetailTitle: React.CSSProperties = {
  fontFamily: INK.fontSerif,
  fontSize: "1.25rem",
  fontWeight: 400,
  letterSpacing: "-0.01em",
  margin: 0,
};
const taskStatusRow: React.CSSProperties = {
  display: "flex",
  alignItems: "center",
  gap: 10,
  padding: "10px 12px",
  marginBottom: 14,
  background: "#fafaf6",
  border: `1px solid ${INK.borderSoft}`,
  borderRadius: 8,
};
const taskStatusLabel: React.CSSProperties = {
  fontSize: "0.72rem",
  textTransform: "uppercase",
  letterSpacing: "0.1em",
  color: INK.muted,
  fontWeight: 600,
};
const taskStatusSelect: React.CSSProperties = {
  padding: "6px 10px",
  fontSize: "0.82rem",
  border: `1px solid ${INK.border}`,
  borderRadius: 6,
  background: "#fff",
  color: INK.ink,
  fontFamily: "inherit",
  cursor: "pointer",
};
const delegateBtn: React.CSSProperties = {
  display: "inline-flex",
  alignItems: "center",
  gap: 6,
  padding: "6px 12px",
  fontSize: "0.78rem",
  fontWeight: 500,
  background: "#fff",
  color: INK.mid,
  border: `1px solid ${INK.border}`,
  borderRadius: 6,
  cursor: "not-allowed",
  fontFamily: "inherit",
  opacity: 0.7,
};
const sessionRow: React.CSSProperties = {
  display: "flex",
  alignItems: "flex-start",
  gap: 12,
  padding: "12px 14px",
  borderBottom: `1px solid ${INK.borderSoft}`,
  background: "#fff",
};
const sessionTitle: React.CSSProperties = {
  fontSize: "0.9rem",
  fontWeight: 600,
  color: INK.ink,
  overflow: "hidden",
  textOverflow: "ellipsis",
  whiteSpace: "nowrap",
};
const sessionSummary: React.CSSProperties = {
  fontSize: "0.82rem",
  color: INK.mid,
  marginTop: 4,
  lineHeight: 1.5,
};
const sessionMeta: React.CSSProperties = {
  display: "flex",
  gap: 8,
  alignItems: "center",
  fontSize: "0.72rem",
  color: INK.muted,
  marginTop: 6,
};
const decisionRow: React.CSSProperties = {
  display: "flex",
  alignItems: "flex-start",
  gap: 10,
  padding: "12px 14px",
  borderBottom: `1px solid ${INK.borderSoft}`,
  background: "#fff",
};
const decisionTitle: React.CSSProperties = {
  fontSize: "0.88rem",
  fontWeight: 600,
  color: INK.ink,
};
const decisionChoice: React.CSSProperties = {
  fontSize: "0.88rem",
  color: INK.ink,
  marginTop: 2,
};
const decisionWhy: React.CSSProperties = {
  fontSize: "0.82rem",
  color: INK.mid,
  marginTop: 6,
  lineHeight: 1.5,
};
const decisionForm: React.CSSProperties = {
  display: "flex",
  flexDirection: "column",
  gap: 8,
  marginBottom: 14,
};
const timestampStyle: React.CSSProperties = {
  fontSize: "0.7rem",
  color: INK.muted,
  flexShrink: 0,
};
const primaryBtn: React.CSSProperties = {
  display: "inline-flex",
  alignItems: "center",
  gap: 6,
  padding: "8px 14px",
  fontSize: "0.82rem",
  fontWeight: 600,
  background: "#1a1a1a",
  color: "#fff",
  border: "none",
  borderRadius: 8,
  cursor: "pointer",
  fontFamily: "inherit",
};
const ghostBtn: React.CSSProperties = {
  display: "inline-flex",
  alignItems: "center",
  gap: 6,
  padding: "8px 12px",
  fontSize: "0.82rem",
  fontWeight: 500,
  background: "transparent",
  color: INK.mid,
  border: `1px solid ${INK.border}`,
  borderRadius: 8,
  cursor: "pointer",
  fontFamily: "inherit",
};
const iconBtn: React.CSSProperties = {
  display: "inline-flex",
  alignItems: "center",
  justifyContent: "center",
  width: 28,
  height: 28,
  padding: 0,
  background: "transparent",
  color: INK.muted,
  border: "none",
  borderRadius: 6,
  cursor: "pointer",
  flexShrink: 0,
};
const checkbox: React.CSSProperties = {
  width: 22,
  height: 22,
  padding: 0,
  display: "inline-flex",
  alignItems: "center",
  justifyContent: "center",
  background: "transparent",
  border: "none",
  cursor: "pointer",
  flexShrink: 0,
  color: INK.mid,
};
const emptyBox: React.CSSProperties = {
  padding: "16px 18px",
  fontSize: "0.85rem",
  color: INK.mid,
  background: "#fafaf6",
  border: `1px dashed ${INK.borderSoft}`,
  borderRadius: 8,
  lineHeight: 1.5,
  textAlign: "center",
};
const promptNudge: React.CSSProperties = {
  display: "block",
  marginTop: 10,
  background: "#f3eee4",
  borderRadius: 5,
  padding: "5px 10px",
  fontSize: "0.76rem",
  color: INK.mid,
  fontStyle: "italic",
};
const errorBox: React.CSSProperties = {
  padding: "12px 14px",
  fontSize: "0.85rem",
  color: "#7a1f15",
  background: "#fbe9e7",
  border: `1px solid #f4c2bc`,
  borderRadius: 8,
};
