/**
 * `ProjectsView` — the top-level project surface (L2 / L11).
 *
 * IDE layout (full remaining height), reworked in ticket #16:
 *
 *   ┌───────────────────────────────────────────────────────┐
 *   │ PROJECT TABS  [ alpha × ][ beta × ]              [ + ] │
 *   ├───────────────────────────────────────────────────────┤
 *   │ MENU BAR   Panneaux │ Settings                        │
 *   ├───────────────────────────────────────────────────────┤
 *   │  MAIN — LayoutGrid (fills the FULL width)             │
 *   │  — or the projects manager / welcome when no project  │
 *   └───────────────────────────────────────────────────────┘
 *
 * The former left sidebar is gone: every panel (context, work, tickets, agents,
 * templates, skills, permissions, memory, git, projects) is reached from the
 * single **Panneaux** menu (#26), whose per-panel submenu picks the placement
 * directly — closed / floating / docked left/right / detached OS window — and
 * shows it in a chrome-level {@link FloatingWindow} or {@link DockRegion}.
 * Project creation/switching is the **Projects** panel, opened by the tab-bar
 * "+" (or `Panneaux → Projects`) and, when no project is open, shown inline in
 * the welcome area (so it is always reachable).
 *
 * Pure presentation: all behaviour comes from {@link useProjects}. Styling via
 * `@/shared`; no inline styles beyond z-index tokens, no `invoke()`.
 *
 * **Test-contract** — the create-project form + known-projects list are in the
 * DOM whenever no project is active (welcome area) or when the Projects window is
 * open; the project tab bar (`role="tablist"`) is always present.
 */

import { useEffect, useState, type ReactNode } from "react";

import type { DomainEvent, LayoutInfo } from "@/domain";
import { LayoutGrid, LayoutTabs } from "@/features/layout";
import { ConversationViewer } from "@/features/conversations";
import { ProfilesSettings } from "@/features/first-run";
import { GitGraphView } from "@/features/git";
import {
  Button,
  DockRegion,
  FloatingWindow,
  IconButton,
  Input,
  MenuBar,
  Panel,
  cn,
  zIndex,
  type FloatingWindowSize,
  type MenuBarItem,
  type MenuBarMenu,
} from "@/shared";
import { useGateways } from "@/app/di";
import { useProjects } from "./useProjects";
import { ProjectTabs } from "./ProjectTabs";
import { ViewPanelBody, type ViewPanelId } from "./ViewPanelBody";
import {
  PANEL_TITLE,
  floatingPanels,
  isDetached,
  isDockedTo,
  panelsDockedTo,
  placementOf,
  type PanelId,
  type ViewPlacement,
  type ViewPlacements,
} from "./viewPlacement";

/** Window width preset per panel (content-heavy panels get more room). */
const PANEL_SIZE: Record<PanelId, FloatingWindowSize> = {
  projects: "md",
  context: "lg",
  work: "lg",
  tickets: "lg",
  agents: "lg",
  templates: "lg",
  skills: "lg",
  permissions: "md",
  memory: "lg",
  git: "lg",
};

interface BackgroundTaskToast {
  id: string;
  projectId: string;
  agentId: string;
  taskId: string;
  state: string;
}

function isTerminalBackgroundTaskEvent(
  event: DomainEvent,
): event is Extract<DomainEvent, { type: "backgroundTaskChanged" }> {
  return (
    event.type === "backgroundTaskChanged" &&
    (event.state === "completed" ||
      event.state === "failed" ||
      event.state === "cancelled" ||
      event.state === "delivered")
  );
}

export function ProjectsView() {
  const vm = useProjects();
  const { system, window: windowGateway } = useGateways();
  const [name, setName] = useState("");
  const [root, setRoot] = useState("");
  // Placement of every open view (#22): each panel is "closed" (absent),
  // "floating" (modal window), or docked left/right. One view = exactly one
  // slot. Ephemeral local state in V1 — restore-at-restart is a deferred
  // backend follow-up.
  const [placements, setPlacements] = useState<ViewPlacements>({});
  // Width (px) of each dock column, driven by the DockRegion resize handle.
  const [leftDockWidth, setLeftDockWidth] = useState(340);
  const [rightDockWidth, setRightDockWidth] = useState(340);
  // Top-level view switch (#16): when true, the main area shows the AI Profiles
  // settings instead of the project surface. The single menu bar stays visible
  // so the user can toggle back from Settings → AI Profiles.
  const [showSettings, setShowSettings] = useState(false);
  // The active layout (id + kind), reported by LayoutTabs — the single source of
  // truth. `kind` decides whether the main area is the terminal grid or the git
  // graph view.
  const [activeLayout, setActiveLayout] = useState<LayoutInfo | null>(null);
  // When set, the main area swaps the terminal grid for the read-only
  // conversation viewer (LS7) — pure local UI state, same mechanic as the
  // terminal↔gitGraph swap; **not** a backend layout kind.
  const [viewerConversationId, setViewerConversationId] = useState<
    string | null
  >(null);
  const [taskToasts, setTaskToasts] = useState<BackgroundTaskToast[]>([]);

  const active = vm.openTabs.find((t) => t.id === vm.activeTabId) ?? null;

  // Reset the active layout whenever the active project changes. `activeLayout`
  // is only repopulated asynchronously by `LayoutTabs` (which re-fetches the new
  // project's layouts). Without this reset, the stale id of the *previous*
  // project would be handed to `LayoutGrid`/`GitGraphView` during the gap, and
  // loading it against the new project's store fails with "not found: layout X".
  useEffect(() => {
    setActiveLayout(null);
    setViewerConversationId(null);
  }, [active?.id]);

  useEffect(() => {
    let unsubscribe: (() => void) | undefined;
    let cancelled = false;
    void system.onDomainEvent((event) => {
      if (!isTerminalBackgroundTaskEvent(event)) return;
      const toast: BackgroundTaskToast = {
        id: `${event.taskId}-${event.state}-${Date.now()}`,
        projectId: event.projectId,
        agentId: event.agentId,
        taskId: event.taskId,
        state: event.state,
      };
      setTaskToasts((prev) => [...prev.slice(-2), toast]);
      window.setTimeout(() => {
        setTaskToasts((prev) => prev.filter((item) => item.id !== toast.id));
      }, 7000);
    }).then((u) => {
      if (cancelled) u();
      else unsubscribe = u;
    });
    return () => {
      cancelled = true;
      unsubscribe?.();
    };
  }, [system]);

  const activeLayoutKind = activeLayout?.kind ?? "terminal";

  const canCreate = name.trim().length > 0 && root.trim().length > 0 && !vm.busy;

  async function submit(e: React.FormEvent) {
    e.preventDefault();
    if (!canCreate) return;
    await vm.createProject(name.trim(), root.trim());
    setName("");
    setRoot("");
  }

  async function handleBrowse() {
    const picked = await system.pickFolder();
    if (picked !== null) setRoot(picked);
  }

  // ── View placement (#22) ────────────────────────────────────────────────
  // Move a view to a specific slot (floating or docked). Enforces the
  // one-slot-per-view invariant implicitly (a panel has a single entry) and the
  // floating surface stays single-window (the #16 modal contract): promoting a
  // view to floating demotes any other floating view to closed. Docks hold as
  // many views as fit — that's the multi-view surface.
  function setPlacement(panel: PanelId, placement: ViewPlacement) {
    setPlacements((prev) => {
      const next: ViewPlacements = { ...prev };
      if (placement === "floating") {
        for (const other of Object.keys(next) as PanelId[]) {
          if (other !== panel && next[other] === "floating") delete next[other];
        }
      }
      next[panel] = placement;
      return next;
    });
  }
  // Close a view (remove its slot → "closed").
  function closePanel(panel: PanelId) {
    setPlacements((prev) => {
      if (!(panel in prev)) return prev;
      const next = { ...prev };
      delete next[panel];
      return next;
    });
  }
  // Dismiss every floating view (docked views stay put). Used when a full-main
  // surface takes over so a modal window doesn't obscure it.
  function dismissFloating() {
    setPlacements((prev) => {
      let changed = false;
      const next = { ...prev };
      for (const panel of Object.keys(next) as PanelId[]) {
        if (next[panel] === "floating") {
          delete next[panel];
          changed = true;
        }
      }
      return changed ? next : prev;
    });
  }

  // Detach a view into its own OS window (#23): ask the backend to open the
  // window, then mark the slot "detached" so nothing renders for it in the main
  // window. Requires an active project (the window is project-scoped). On
  // failure the placement is left untouched (no ghost "detached" slot).
  function detachPanel(panel: PanelId) {
    if (!active) return;
    const projectId = active.id;
    void windowGateway
      .openViewWindow(panel, projectId)
      .then(() => setPlacement(panel, "detached"))
      .catch(() => {
        /* window failed to open; keep the current placement */
      });
  }

  // When a detached window closes (OS close or programmatic), re-toggle its
  // placement out of "detached" so it isn't stuck as an invisible ghost slot.
  useEffect(() => {
    let unsubscribe: (() => void) | undefined;
    let cancelled = false;
    void windowGateway
      .onViewWindowClosed(({ panel }) => {
        setPlacements((prev) =>
          prev[panel as PanelId] === "detached"
            ? (() => {
                const next = { ...prev };
                delete next[panel as PanelId];
                return next;
              })()
            : prev,
        );
      })
      .then((u) => {
        if (cancelled) u();
        else unsubscribe = u;
      });
    return () => {
      cancelled = true;
      unsubscribe?.();
    };
  }, [windowGateway]);

  // Opening a conversation viewer takes over the main area — dismiss floating
  // windows so they don't obscure the viewer (LS7). Docked views stay beside it.
  function openConversation(conversationId: string) {
    setViewerConversationId(conversationId);
    dismissFloating();
  }

  // ── Menus (#26) ─────────────────────────────────────────────────────────
  // A single « Panneaux » menu: one entry per panel, each opening a submenu with
  // the placement actions directly (closed / floating / docked left/right /
  // detached OS window). One place to pick the panel *and* its mode; the current
  // placement is marked in the submenu (and the parent entry shows ● when the
  // panel is open somewhere). Order per UX: content panels first, Projects last.
  const panelOrder: PanelId[] = [
    "context",
    "work",
    "tickets",
    "agents",
    "templates",
    "skills",
    "permissions",
    "memory",
    "git",
    "projects",
  ];

  // Placement actions for one panel, marking the active slot. `projects` is
  // main-window-only chrome, so it never offers the detached-window action.
  function placementSubmenu(panel: PanelId): MenuBarItem[] {
    const placement = placementOf(placements, panel);
    const items: MenuBarItem[] = [
      {
        id: "closed",
        label: "Fermé",
        active: placement === "closed",
        onSelect: () => closePanel(panel),
      },
      {
        id: "floating",
        label: "Flottant",
        active: placement === "floating",
        onSelect: () => setPlacement(panel, "floating"),
      },
      {
        id: "dock-left",
        label: "Ancré à gauche",
        active: isDockedTo(placement, "left"),
        onSelect: () => setPlacement(panel, { dock: "left" }),
      },
      {
        id: "dock-right",
        label: "Ancré à droite",
        active: isDockedTo(placement, "right"),
        onSelect: () => setPlacement(panel, { dock: "right" }),
      },
    ];
    if (panel !== "projects") {
      items.push({
        id: "detached",
        label: "Fenêtre détachée",
        active: isDetached(placement),
        disabled: !active,
        onSelect: () => detachPanel(panel),
      });
    }
    return items;
  }

  const menus: MenuBarMenu[] = [
    {
      id: "panels",
      label: "Panneaux",
      items: panelOrder.map((panel) => ({
        id: panel,
        label: PANEL_TITLE[panel],
        active: placementOf(placements, panel) !== "closed",
        onSelect: () => {},
        submenu: placementSubmenu(panel),
      })),
    },
    {
      id: "settings",
      label: "Settings",
      items: [
        {
          id: "ai-profiles",
          label: showSettings ? "Close AI Profiles" : "AI Profiles",
          active: showSettings,
          onSelect: () => {
            setShowSettings((v) => !v);
            dismissFloating();
          },
        },
      ],
    },
  ];

  // The create-project form + known-projects list. Rendered inline in the
  // welcome area (no active project) or inside the Projects floating window.
  const projectsManager: ReactNode = (
    <div className="flex flex-col gap-4">
      <form onSubmit={submit} className="flex flex-col gap-2">
        <h3 className="text-xs font-semibold uppercase tracking-wide text-faint">
          New project
        </h3>
        <Input
          aria-label="project name"
          placeholder="Project name"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
        <div className="flex gap-2">
          <Input
            aria-label="project root"
            placeholder="/absolute/project/root"
            value={root}
            onChange={(e) => setRoot(e.target.value)}
            className="flex-1"
          />
          <Button
            type="button"
            aria-label="browse project folder"
            onClick={() => void handleBrowse()}
            disabled={vm.busy}
          >
            Browse…
          </Button>
        </div>
        <div className="flex gap-2">
          <Button
            type="submit"
            variant="primary"
            disabled={!canCreate}
            className="flex-1"
          >
            Create project
          </Button>
          <Button
            type="button"
            onClick={() => void vm.refresh()}
            disabled={vm.busy}
          >
            Refresh
          </Button>
        </div>
      </form>

      <Panel title="Known projects">
        {vm.projects.length === 0 ? (
          <p className="text-sm text-muted">No projects yet.</p>
        ) : (
          <ul className="flex flex-col divide-y divide-border">
            {vm.projects.map((p) => (
              <li
                key={p.id}
                className="flex items-center justify-between gap-2 py-2 first:pt-0 last:pb-0"
              >
                <span className="flex min-w-0 flex-col gap-0.5">
                  <span className="font-medium text-content">{p.name}</span>
                  <code className="truncate text-xs text-muted">{p.root}</code>
                </span>
                <Button size="sm" onClick={() => void vm.openProject(p.id)}>
                  Open
                </Button>
              </li>
            ))}
          </ul>
        )}
      </Panel>
    </div>
  );

  // Body of a panel wherever it is placed (docked/floating). Delegates to the
  // shared {@link ViewPanelBody} (also used by the detached ViewWindow) so a
  // view is identical across placements. "projects" is main-window-only chrome.
  function renderPanel(panel: PanelId): ReactNode {
    if (panel === "projects") return projectsManager;
    if (!active) {
      return (
        <p className="text-sm text-muted">Open a project to use this panel.</p>
      );
    }
    return (
      <ViewPanelBody
        panel={panel as ViewPanelId}
        projectId={active.id}
        projectRoot={active.root}
        onOpenConversation={openConversation}
      />
    );
  }

  // Projects manager renders inline in the welcome area only when it is not
  // already shown as a panel (avoids duplicate form inputs).
  const showInlineProjects =
    !active && placementOf(placements, "projects") === "closed";

  // Views by slot, in stable declaration order.
  const leftPanels = panelsDockedTo(placements, "left");
  const rightPanels = panelsDockedTo(placements, "right");
  const floatingList = floatingPanels(placements);

  // Placement controls for a view's header — labelled icon-buttons aligned 1:1
  // with the « Panneaux » menu wording (#42). Order: ⇤ gauche · ⇥ droite · □
  // flottant · ↗ fenêtre détachée · × fermé. The *current* placement reads as
  // ACTIVE (aria-pressed + ring), never disabled — a disabled control loses its
  // tooltip/keyboard focus and conflates "impossible" with "already selected".
  // Only genuinely-impossible actions are disabled (detach with no active
  // project). All labelling flows through `title` + `aria-label`; no visible
  // text. The detach control is hidden for the main-window-only `projects` view.
  function DockControls({ panel }: { panel: PanelId }) {
    const placement = placementOf(placements, panel);
    const title = PANEL_TITLE[panel];
    // Active (current-placement) styling — visually distinct, still clickable.
    const activeClass = "bg-raised text-content ring-1 ring-border-strong";
    return (
      <div className="flex shrink-0 items-center gap-1">
        <IconButton
          size="sm"
          title="Ancré à gauche"
          aria-label={`Ancré à gauche — ${title}`}
          aria-pressed={isDockedTo(placement, "left")}
          className={cn(isDockedTo(placement, "left") && activeClass)}
          onClick={() => setPlacement(panel, { dock: "left" })}
        >
          ⇤
        </IconButton>
        <IconButton
          size="sm"
          title="Ancré à droite"
          aria-label={`Ancré à droite — ${title}`}
          aria-pressed={isDockedTo(placement, "right")}
          className={cn(isDockedTo(placement, "right") && activeClass)}
          onClick={() => setPlacement(panel, { dock: "right" })}
        >
          ⇥
        </IconButton>
        <IconButton
          size="sm"
          title="Flottant"
          aria-label={`Flottant — ${title}`}
          aria-pressed={placement === "floating"}
          className={cn(placement === "floating" && activeClass)}
          onClick={() => setPlacement(panel, "floating")}
        >
          □
        </IconButton>
        {panel !== "projects" && (
          <IconButton
            size="sm"
            title="Fenêtre détachée"
            aria-label={`Fenêtre détachée — ${title}`}
            aria-pressed={isDetached(placement)}
            disabled={!active}
            className={cn(isDetached(placement) && activeClass)}
            onClick={() => detachPanel(panel)}
          >
            ↗
          </IconButton>
        )}
        <IconButton
          size="sm"
          title="Fermé"
          aria-label={`Fermé — ${title}`}
          onClick={() => closePanel(panel)}
        >
          ×
        </IconButton>
      </div>
    );
  }

  // A single docked view inside a DockRegion column: header (title + placement
  // controls) over the existing view component, reused as-is.
  function renderDockedView(panel: PanelId): ReactNode {
    return (
      <section
        key={panel}
        aria-label={`${PANEL_TITLE[panel]} panel`}
        className="flex min-h-0 flex-1 flex-col border-b border-border last:border-b-0"
      >
        <header className="flex shrink-0 items-center justify-between gap-2 border-b border-border px-3 py-2">
          <span className="truncate text-sm font-medium text-content">
            {PANEL_TITLE[panel]}
          </span>
          <DockControls panel={panel} />
        </header>
        <div className="min-h-0 flex-1 overflow-auto p-3">
          {renderPanel(panel)}
        </div>
      </section>
    );
  }

  return (
    <div className="flex flex-1 flex-col overflow-hidden">
      {/* ── Error alert ── */}
      {vm.error && (
        <p
          role="alert"
          className="mx-4 mt-2 shrink-0 rounded-md border border-danger/40 bg-danger/10 px-3 py-2 text-sm text-danger"
        >
          {vm.error}
        </p>
      )}

      {/* ── Project tab bar (#26): tabs = open projects, "+" opens Projects ── */}
      <ProjectTabs
        items={vm.openTabs.map((t) => ({ id: t.id, label: t.name }))}
        activeTabId={vm.activeTabId}
        onSelect={(id) => vm.activateTab(id)}
        onClose={(id) => void vm.closeTab(id)}
        projectsPanelOpen={placementOf(placements, "projects") !== "closed"}
        onOpenProjectsPanel={() => {
          setShowSettings(false);
          setPlacement("projects", "floating");
        }}
      />

      {/* ── Menu bar (replaces the former left sidebar) ── */}
      <MenuBar menus={menus} />

      {/* ── Chrome row: left dock │ main │ right dock (#22). Docks are in-flow
          resizable columns, not overlays — they sit beside the main surface. */}
      <div className="flex min-h-0 flex-1 overflow-hidden">
        {leftPanels.length > 0 && (
          <DockRegion
            side="left"
            width={leftDockWidth}
            onResize={setLeftDockWidth}
            aria-label="left dock"
          >
            {leftPanels.map(renderDockedView)}
          </DockRegion>
        )}

        {/* ── Main: AI Profiles / terminal grid / git graph / welcome ── */}
        <main className="flex min-w-0 flex-1 flex-col overflow-hidden">
        {showSettings ? (
          // Top-level view switch (#16): AI Profiles settings takes over the main
          // area while the menu bar above stays visible to toggle back.
          <div className="flex flex-1 justify-center overflow-y-auto p-6">
            <div className="w-full max-w-2xl">
              <ProfilesSettings />
            </div>
          </div>
        ) : active && viewerConversationId ? (
          <ConversationViewer
            key={`${active.id}-${viewerConversationId}`}
            projectId={active.id}
            conversationId={viewerConversationId}
            onClose={() => setViewerConversationId(null)}
          />
        ) : active ? (
          <>
            <LayoutTabs
              projectId={active.id}
              onActiveLayoutChange={setActiveLayout}
            />
            {activeLayoutKind === "gitGraph" ? (
              <GitGraphView
                key={`${active.id}-${activeLayout?.id ?? "default"}-graph`}
                projectId={active.id}
              />
            ) : (
              <LayoutGrid
                key={`${active.id}-${activeLayout?.id ?? "default"}`}
                projectId={active.id}
                cwd={active.root}
                layoutId={activeLayout?.id}
                onOpenConversation={openConversation}
              />
            )}
          </>
        ) : (
          <div className="flex flex-1 justify-center overflow-y-auto p-6">
            <div className="w-full max-w-xl">
              {showInlineProjects ? (
                projectsManager
              ) : (
                <p className="text-sm text-muted">
                  Select or create a project to get started.
                </p>
              )}
            </div>
          </div>
        )}
        </main>

        {rightPanels.length > 0 && (
          <DockRegion
            side="right"
            width={rightDockWidth}
            onResize={setRightDockWidth}
            aria-label="right dock"
          >
            {rightPanels.map(renderDockedView)}
          </DockRegion>
        )}
      </div>

      {/* ── Floating panel windows (modal overlays) ── */}
      {floatingList.map((panel) => (
        <FloatingWindow
          key={panel}
          open
          title={PANEL_TITLE[panel]}
          size={PANEL_SIZE[panel]}
          onClose={() => closePanel(panel)}
        >
          <div className="mb-3 flex justify-end border-b border-border pb-3">
            <DockControls panel={panel} />
          </div>
          {renderPanel(panel)}
        </FloatingWindow>
      ))}

      {/* ── Background-task toasts (above floating windows) ── */}
      {taskToasts.length > 0 && (
        <div
          className="fixed bottom-4 right-4 flex w-80 max-w-[calc(100vw-2rem)] flex-col gap-2"
          style={{ zIndex: zIndex.toast }}
        >
          {taskToasts.map((toast) => (
            <button
              key={toast.id}
              type="button"
              className="rounded-md border border-border bg-surface px-3 py-2 text-left shadow-lg hover:border-border-strong"
              onClick={() => {
                const projectOpen = vm.openTabs.some(
                  (tab) => tab.id === toast.projectId,
                );
                if (projectOpen) vm.activateTab(toast.projectId);
                setShowSettings(false);
                setViewerConversationId(null);
                setPlacement("work", "floating");
                setTaskToasts((prev) =>
                  prev.filter((item) => item.id !== toast.id),
                );
              }}
            >
              <span className="block text-sm font-medium text-content">
                Background task {toast.state}
              </span>
              <span className="mt-0.5 block truncate text-xs text-muted">
                {toast.agentId.slice(0, 8)} · {toast.taskId.slice(0, 8)}
              </span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}
