/**
 * `ViewWindow` — the panel-only entry (ticket #23).
 *
 * A detached OS window loads the same SPA with `?panel=<panel>&project=<id>`;
 * {@link main} routes to this component instead of the full {@link App} when a
 * `panel` param is present. It re-instantiates the DI (same adapters/gateways
 * as the main window — views are gateway/read-model-driven, so no heavy state
 * is transferred) and renders **only** the requested view for the given
 * project.
 *
 * Pure presentation over the ports: it resolves the project through the
 * {@link ProjectGateway} (for its name/root) and hands off to the shared
 * {@link ViewPanelBody}. No `invoke()`, no cross-window messaging.
 */

import { useEffect, useState } from "react";

import type { Project } from "@/domain";
import { Panel, Spinner } from "@/shared";
import { PANEL_TITLE, type PanelId } from "@/features/projects";
import { ViewPanelBody, type ViewPanelId } from "@/features/projects";
import { useGateways } from "./di";

/** Panels that may be shown in a detached window (everything but "projects"). */
const DETACHABLE = new Set<string>(
  (Object.keys(PANEL_TITLE) as PanelId[]).filter((p) => p !== "projects"),
);

/** Parsed `?panel=&project=` params, or `null` when absent/invalid. */
export interface ViewWindowParams {
  panel: ViewPanelId;
  projectId: string;
}

/** Reads and validates the panel-only params from a query string. */
export function parseViewWindowParams(
  search: string,
): ViewWindowParams | null {
  const params = new URLSearchParams(search);
  const panel = params.get("panel");
  const projectId = params.get("project");
  if (!panel || !projectId || !DETACHABLE.has(panel)) return null;
  return { panel: panel as ViewPanelId, projectId };
}

export interface ViewWindowProps {
  panel: ViewPanelId;
  projectId: string;
}

export function ViewWindow({ panel, projectId }: ViewWindowProps) {
  const { project } = useGateways();
  const [resolved, setResolved] = useState<Project | null>(null);
  const [error, setError] = useState<string | null>(null);

  // Resolve the project to get its root (the agents panel needs it) and name
  // (window title). Opening is idempotent on the backend registry.
  useEffect(() => {
    let cancelled = false;
    project
      .openProject(projectId)
      .then((p) => {
        if (!cancelled) setResolved(p);
      })
      .catch((e: unknown) => {
        if (!cancelled) setError(describeError(e));
      });
    return () => {
      cancelled = true;
    };
  }, [project, projectId]);

  return (
    <div className="flex h-full flex-col bg-canvas text-content">
      <header className="flex shrink-0 items-center gap-2 border-b border-border px-4 py-2.5">
        <h1 className="text-sm font-semibold text-content">
          {PANEL_TITLE[panel]}
        </h1>
        {resolved && (
          <span className="truncate text-xs text-muted">· {resolved.name}</span>
        )}
      </header>
      <main className="min-h-0 flex-1 overflow-auto p-4">
        {error ? (
          <Panel className="border-danger/40">
            <p className="text-sm text-danger">Error: {error}</p>
          </Panel>
        ) : resolved ? (
          <ViewPanelBody
            panel={panel}
            projectId={resolved.id}
            projectRoot={resolved.root}
          />
        ) : (
          <div className="flex items-center gap-2 text-sm text-muted">
            <Spinner size={14} />
            <span>Loading…</span>
          </div>
        )}
      </main>
    </div>
  );
}

function describeError(e: unknown): string {
  if (e && typeof e === "object" && "message" in e) {
    return String((e as { message: unknown }).message);
  }
  return String(e);
}
