import React, { useCallback, useEffect, useMemo, useState } from "react";
import {
  Background,
  BackgroundVariant,
  Controls,
  MiniMap,
  ReactFlow,
  type Connection,
  type Edge,
  type Node,
  type NodeChange,
  type ReactFlowInstance,
  type Viewport,
} from "@xyflow/react";

import { ApiClient } from "./api-client.ts";
import {
  buildCustomNodeEnvelopePreview,
  type CustomNodeValidationIssue,
  validateCustomNodeDefinition,
} from "../../../../packages/contracts/src/custom-node.ts";
import {
  formatCustomNodeInputModeKey,
  formatCustomNodeOutputModeKey,
  formatCustomNodeSourceKindKey,
} from "./custom-node-presenter.ts";
import {
  localizeNodeDefinition,
  type TranslationKey,
  useI18n,
} from "./i18n.tsx";
import {
  addNodeToDraft,
  addNodeToDraftAtPosition,
  canConnectNodesInDraft,
  connectNodesInDraft,
  createDefaultWorkflowDraft,
  getNodeRuntimeConfig,
  removeNodeFromDraft,
  setNodePosition,
  resolveDefinitionIdForNode,
  serializeWorkflowDraft,
  setViewportInDraft,
  setNodeRuntimeConfig,
  workflowDraftFromVersion,
  type WorkflowConnectionValidationReason,
  type WorkflowDraft,
  type WorkflowNodeRuntimeConfig,
} from "./workflow-editor-state.ts";

const NODE_LIBRARY_MIME = "application/x-emboflow-node-definition";
const ACTIVE_PROJECT_STORAGE_KEY_PREFIX = "emboflow.activeProject";

function navigateTo(pathname: string) {
  if (typeof window === "undefined") {
    return;
  }
  if (window.location.pathname === pathname) {
    return;
  }
  window.history.pushState({}, "", pathname);
  window.dispatchEvent(new PopStateEvent("popstate"));
}

function getActiveProjectStorageKey(workspaceId: string) {
  return `${ACTIVE_PROJECT_STORAGE_KEY_PREFIX}:${workspaceId}`;
}

function normalizePathnameForProjectSwitch(pathname: string) {
  if (pathname.startsWith("/assets/")) {
    return "/assets";
  }
  if (pathname.startsWith("/nodes")) {
    return "/nodes";
  }
  if (pathname.startsWith("/workflows/")) {
    return "/workflows";
  }
  if (pathname.startsWith("/runs/")) {
    return "/runs";
  }
  if (pathname.startsWith("/explore/")) {
    return "/explore";
  }
  return pathname === "/" ? "/projects" : pathname;
}

function mapConnectionValidationReasonToKey(
  reason: WorkflowConnectionValidationReason | "missing_connection_endpoint",
): TranslationKey {
  switch (reason) {
    case "missing_source":
    case "missing_target":
    case "missing_connection_endpoint":
      return "invalidConnectionMissingEndpoint";
    case "self":
      return "invalidConnectionSelf";
    case "duplicate":
      return "invalidConnectionDuplicate";
    case "source_disallows_outgoing":
      return "invalidConnectionSourceDisallowsOutgoing";
    case "target_disallows_incoming":
      return "invalidConnectionTargetDisallowsIncoming";
    case "target_already_has_incoming":
      return "invalidConnectionTargetAlreadyHasIncoming";
    case "cycle":
      return "invalidConnectionCycle";
  }
}

function mapCustomNodeValidationIssueToKey(issue: CustomNodeValidationIssue): TranslationKey {
  switch (issue) {
    case "name_required":
      return "customNodeValidationNameRequired";
    case "name_too_long":
      return "customNodeValidationNameTooLong";
    case "invalid_category":
      return "customNodeValidationInvalidCategory";
    case "invalid_source_kind":
      return "customNodeValidationInvalidSourceKind";
    case "image_required":
      return "customNodeValidationImageRequired";
    case "dockerfile_required":
      return "customNodeValidationDockerfileRequired";
    case "dockerfile_missing_from":
      return "customNodeValidationDockerfileMissingFrom";
    case "invalid_command":
      return "customNodeValidationInvalidCommand";
    case "invalid_input_mode":
      return "customNodeValidationInvalidInputMode";
    case "invalid_output_mode":
      return "customNodeValidationInvalidOutputMode";
    case "invalid_artifact_type":
      return "customNodeValidationInvalidArtifactType";
    case "source_cannot_be_multi_input":
      return "customNodeValidationSourceCannotBeMultiInput";
  }
}

type NavItem = "Projects" | "Assets" | "Nodes" | "Workflows" | "Runs" | "Explore" | "Labels" | "Admin";

type BootstrapContext = {
  userId: string;
  workspace: { _id: string; name: string };
  project: { _id: string; name: string };
};

type ProjectSummary = {
  _id: string;
  name: string;
  description?: string;
  status?: string;
  createdAt?: string;
};

type AppProps = {
  apiBaseUrl: string;
};

type WorkflowPreflightResult = {
  ok: boolean;
  issues: Array<{
    severity: "error" | "warning";
    code: string;
    message: string;
    nodeId?: string;
    nodeDefinitionId?: string;
  }>;
  summary: {
    errorCount: number;
    warningCount: number;
  };
};

function translateStatus(status: string | undefined, t: ReturnType<typeof useI18n>["t"]) {
  switch (status) {
    case "success":
      return t("success");
    case "failed":
      return t("failed");
    case "running":
      return t("running");
    case "queued":
    case "pending":
      return t("queued");
    case "cancelled":
      return t("cancelled");
    default:
      return status ?? "unknown";
  }
}

function formatTaskSummary(task: any, t: ReturnType<typeof useI18n>["t"]) {
  if (task?.summary?.errorMessage) {
    return task.summary.errorMessage;
  }
  const outcome = translateStatus(task?.summary?.outcome ?? task?.status, t);
  const executor = task?.summary?.executorType ?? task?.executorType ?? "unknown";
  const assetCount = task?.summary?.assetCount ?? task?.assetIds?.length ?? 0;
  const artifactCount = task?.summary?.artifactIds?.length ?? task?.outputArtifactIds?.length ?? 0;
  return `${t("viaExecutor", { outcome, executor })}; ${t("assetCount", { count: assetCount })}; ${t("artifactCount", { count: artifactCount })}`;
}

function formatRunSummary(run: any, t: ReturnType<typeof useI18n>["t"]) {
  const totalTaskCount = run?.summary?.totalTaskCount ?? 0;
  const successCount = run?.summary?.taskCounts?.success ?? 0;
  const failedCount = run?.summary?.taskCounts?.failed ?? 0;
  const runningCount = run?.summary?.taskCounts?.running ?? 0;
  const cancelledCount = run?.summary?.taskCounts?.cancelled ?? 0;
  const stdoutLineCount = run?.summary?.stdoutLineCount ?? 0;
  const stderrLineCount = run?.summary?.stderrLineCount ?? 0;
  return [
    t("successCount", { count: successCount }),
    t("failedCount", { count: failedCount }),
    t("runningCount", { count: runningCount }),
    t("cancelledCount", { count: cancelledCount }),
    t("stdoutLines", { count: stdoutLineCount }),
    t("stderrLines", { count: stderrLineCount }),
    t("totalTasks", { count: totalTaskCount }),
  ].join(", ");
}

function formatExecutorConfigLabel(config?: Record<string, unknown>) {
  if (!config || Object.keys(config).length === 0) {
    return "none";
  }
  return JSON.stringify(config);
}

function getDefaultExecutorType(definition?: { defaultExecutorType?: "python" | "docker" | "http" } | null) {
  return definition?.defaultExecutorType ?? "python";
}

function getDefaultExecutorConfig(definition?: { defaultExecutorConfig?: Record<string, unknown> } | null) {
  return definition?.defaultExecutorConfig ? { ...definition.defaultExecutorConfig } : undefined;
}

function getEffectiveNodeRuntimeConfig(
  definition: { id: string; defaultExecutorType?: "python" | "docker" | "http"; defaultExecutorConfig?: Record<string, unknown> } | null,
  runtimeConfig: WorkflowNodeRuntimeConfig | undefined,
): WorkflowNodeRuntimeConfig {
  const executorType = runtimeConfig?.executorType ?? getDefaultExecutorType(definition);
  const executorConfig = {
    ...(getDefaultExecutorConfig(definition) ?? {}),
    ...(runtimeConfig?.executorConfig ?? {}),
  };

  return {
    definitionId: runtimeConfig?.definitionId ?? definition?.id,
    executorType,
    executorConfig: Object.keys(executorConfig).length > 0 ? executorConfig : undefined,
    codeHookSpec: runtimeConfig?.codeHookSpec,
    artifactType: runtimeConfig?.artifactType,
    artifactTitle: runtimeConfig?.artifactTitle,
  };
}

function usePathname() {
  const [pathname, setPathname] = useState(
    typeof window === "undefined" ? "/projects" : window.location.pathname || "/projects",
  );

  useEffect(() => {
    const handle = () => setPathname(window.location.pathname || "/assets");
    window.addEventListener("popstate", handle);
    return () => window.removeEventListener("popstate", handle);
  }, []);

  return pathname === "/" ? "/projects" : pathname;
}

function AppShell(props: {
  workspaceName: string;
  projectName: string;
  projectControl?: React.ReactNode;
  active: NavItem;
  children: React.ReactNode;
}) {
  const { language, setLanguage, t } = useI18n();
  const navItems: Array<{ label: NavItem; href: string; key: "navProjects" | "navAssets" | "navNodes" | "navWorkflows" | "navRuns" | "navExplore" | "navLabels" | "navAdmin" }> = [
    { label: "Projects", href: "/projects", key: "navProjects" },
    { label: "Assets", href: "/assets", key: "navAssets" },
    { label: "Nodes", href: "/nodes", key: "navNodes" },
    { label: "Workflows", href: "/workflows", key: "navWorkflows" },
    { label: "Runs", href: "/runs", key: "navRuns" },
    { label: "Explore", href: "/explore", key: "navExplore" },
    { label: "Labels", href: "/labels", key: "navLabels" },
    { label: "Admin", href: "/admin", key: "navAdmin" },
  ];

  return (
    <div className="app-shell">
      <header className="app-header">
        <div className="app-header__group">
          <div className="app-header__pill">
            <span className="app-header__label">{t("workspace")}</span>
            <strong>{props.workspaceName}</strong>
          </div>
          <div className="app-header__pill">
            <span className="app-header__label">{t("project")}</span>
            {props.projectControl ?? <strong>{props.projectName}</strong>}
          </div>
        </div>
        <div className="app-header__group">
          <div className="language-switcher" role="group" aria-label={t("language")}>
            <button
              type="button"
              className="button-secondary"
              data-active={String(language === "zh")}
              onClick={() => setLanguage("zh")}
            >
              {t("chinese")}
            </button>
            <button
              type="button"
              className="button-secondary"
              data-active={String(language === "en")}
              onClick={() => setLanguage("en")}
            >
              {t("english")}
            </button>
          </div>
          <span className="app-header__label">{t("runs")}</span>
          <span className="app-header__label">{t("localDev")}</span>
        </div>
      </header>
      <aside className="app-sidebar">
        <ul>
          {navItems.map((item) => (
            <li key={item.label}>
              <a
                href={item.href}
                data-active={String(item.label === props.active)}
                onClick={(event) => {
                  event.preventDefault();
                  navigateTo(item.href);
                }}
              >
                {t(item.key)}
              </a>
            </li>
          ))}
        </ul>
      </aside>
      <main className="app-main">{props.children}</main>
    </div>
  );
}

function ProjectsPage(props: {
  api: ApiClient;
  bootstrap: BootstrapContext;
  projects: ProjectSummary[];
  activeProjectId: string;
  onProjectCreated: (project: ProjectSummary) => Promise<void> | void;
  onProjectSelected: (projectId: string, nextPath?: string) => void;
}) {
  const { t } = useI18n();
  const [projectName, setProjectName] = useState("");
  const [projectDescription, setProjectDescription] = useState("");
  const [error, setError] = useState<string | null>(null);

  return (
    <div className="page-stack">
      <section className="panel">
        <h1>{t("projectsTitle")}</h1>
        <p>{t("projectsDescription")}</p>
        <div className="field-grid">
          <label>
            {t("projectNameLabel")}
            <input
              value={projectName}
              onChange={(event) => setProjectName(event.target.value)}
              placeholder="Embodied Delivery Project"
            />
          </label>
          <label>
            {t("projectDescriptionLabel")}
            <textarea
              rows={3}
              value={projectDescription}
              onChange={(event) => setProjectDescription(event.target.value)}
              placeholder="Customer-specific dataset conversion and delivery workflows"
            />
          </label>
        </div>
        <div className="button-row" style={{ marginTop: 12 }}>
          <button
            className="button-primary"
            disabled={projectName.trim().length === 0}
            onClick={async () => {
              setError(null);
              try {
                const project = await props.api.createProject({
                  workspaceId: props.bootstrap.workspace._id,
                  name: projectName.trim(),
                  description: projectDescription.trim() || undefined,
                  createdBy: props.bootstrap.userId,
                });
                setProjectName("");
                setProjectDescription("");
                await props.onProjectCreated(project);
              } catch (createError) {
                setError(
                  createError instanceof Error ? createError.message : t("failedCreateProject"),
                );
              }
            }}
          >
            {t("createProject")}
          </button>
        </div>
        {error ? <p>{error}</p> : null}
      </section>

      <section className="panel">
        <div className="list-grid">
          {props.projects.length === 0 ? (
            <p className="empty-state">{t("noProjectsYet")}</p>
          ) : (
            props.projects.map((project) => {
              const isActive = project._id === props.activeProjectId;
              return (
                <article key={project._id} className="asset-card" data-active={String(isActive)}>
                  <div className="toolbar">
                    <strong>{project.name}</strong>
                    {isActive ? (
                      <span className="status-pill" data-status="running">
                        {t("activeProject")}
                      </span>
                    ) : null}
                  </div>
                  <p>{project.description || t("notAvailable")}</p>
                  <p>{t("status")}: {translateStatus(project.status, t)}</p>
                  <p>{t("createdAt")}: {project.createdAt ?? t("notAvailable")}</p>
                  <div className="button-row" style={{ marginTop: 12 }}>
                    <button
                      className={isActive ? "button-secondary" : "button-primary"}
                      onClick={() => props.onProjectSelected(project._id, "/workflows")}
                    >
                      {t("openProject")}
                    </button>
                  </div>
                </article>
              );
            })
          )}
        </div>
      </section>
    </div>
  );
}

function parseCommandLines(value: string) {
  return value
    .split(/\r?\n/u)
    .map((line) => line.trim())
    .filter((line) => line.length > 0);
}

function AssetsPage(props: {
  api: ApiClient;
  bootstrap: BootstrapContext;
}) {
  const { t } = useI18n();
  const [sourcePath, setSourcePath] = useState("");
  const [storageName, setStorageName] = useState("");
  const [storageProvider, setStorageProvider] = useState<"local" | "minio" | "s3" | "bos" | "oss">("local");
  const [storageBucket, setStorageBucket] = useState("");
  const [storageEndpoint, setStorageEndpoint] = useState("");
  const [storageRegion, setStorageRegion] = useState("");
  const [storageBasePath, setStorageBasePath] = useState("");
  const [storageRootPath, setStorageRootPath] = useState("");
  const [datasetName, setDatasetName] = useState("");
  const [datasetDescription, setDatasetDescription] = useState("");
  const [datasetStoragePath, setDatasetStoragePath] = useState("");
  const [selectedDatasetAssetId, setSelectedDatasetAssetId] = useState("");
  const [selectedStorageConnectionId, setSelectedStorageConnectionId] = useState("");
  const [assets, setAssets] = useState<any[]>([]);
  const [storageConnections, setStorageConnections] = useState<any[]>([]);
  const [datasets, setDatasets] = useState<any[]>([]);
  const [error, setError] = useState<string | null>(null);

  const loadData = async () => {
    try {
      const [nextAssets, nextStorageConnections, nextDatasets] = await Promise.all([
        props.api.listAssets(props.bootstrap.project._id),
        props.api.listStorageConnections(props.bootstrap.workspace._id),
        props.api.listDatasets(props.bootstrap.project._id),
      ]);
      setAssets(nextAssets);
      setStorageConnections(nextStorageConnections);
      setDatasets(nextDatasets);
      setSelectedDatasetAssetId((previous) => {
        if (previous && nextAssets.some((asset) => asset._id === previous)) {
          return previous;
        }
        return nextAssets[0]?._id ?? "";
      });
      setSelectedStorageConnectionId((previous) => {
        if (previous && nextStorageConnections.some((connection) => connection._id === previous)) {
          return previous;
        }
        return nextStorageConnections[0]?._id ?? "";
      });
      setError(null);
    } catch (loadError) {
      setError(loadError instanceof Error ? loadError.message : t("failedLoadAssets"));
    }
  };

  useEffect(() => {
    void loadData();
  }, [props.bootstrap.project._id]);

  return (
    <div className="page-stack">
      <section className="panel">
        <h1>{t("assetsTitle")}</h1>
        <p>{t("assetsDescription")}</p>
        <div className="field-grid">
          <label>
            {t("localPath")}
            <input
              value={sourcePath}
              onChange={(event) => setSourcePath(event.target.value)}
              placeholder="/Users/longtaowu/workspace/emboldata/data"
            />
          </label>
        </div>
        <div className="button-row" style={{ marginTop: 12 }}>
          <button
            className="button-primary"
            onClick={async () => {
              setError(null);
              try {
                const asset = await props.api.registerLocalAsset({
                  workspaceId: props.bootstrap.workspace._id,
                  projectId: props.bootstrap.project._id,
                  sourcePath,
                });
                await props.api.probeAsset(asset._id);
                await loadData();
              } catch (registerError) {
                setError(
                  registerError instanceof Error
                    ? registerError.message
                    : t("failedRegisterAsset"),
                );
              }
            }}
          >
            {t("registerLocalPath")}
          </button>
        </div>
        {error ? <p>{error}</p> : null}
      </section>

      <section className="two-column">
        <section className="panel">
          <h2>{t("storageConnectionsTitle")}</h2>
          <p>{t("storageConnectionsDescription")}</p>
          <div className="field-grid">
            <label>
              {t("templateName")}
              <input
                value={storageName}
                onChange={(event) => setStorageName(event.target.value)}
                placeholder="Local Workspace Storage"
              />
            </label>
            <label>
              {t("storageProvider")}
              <select
                value={storageProvider}
                onChange={(event) =>
                  setStorageProvider(event.target.value as "local" | "minio" | "s3" | "bos" | "oss")
                }
              >
                <option value="local">local</option>
                <option value="minio">minio</option>
                <option value="s3">s3</option>
                <option value="bos">bos</option>
                <option value="oss">oss</option>
              </select>
            </label>
            <label>
              {t("bucket")}
              <input
                value={storageBucket}
                onChange={(event) => setStorageBucket(event.target.value)}
                placeholder="emboflow-datasets"
              />
            </label>
            <label>
              {t("endpoint")}
              <input
                value={storageEndpoint}
                onChange={(event) => setStorageEndpoint(event.target.value)}
                placeholder="oss-cn-hangzhou.aliyuncs.com"
              />
            </label>
            <label>
              {t("region")}
              <input
                value={storageRegion}
                onChange={(event) => setStorageRegion(event.target.value)}
                placeholder="cn-hangzhou"
              />
            </label>
            <label>
              {t("basePath")}
              <input
                value={storageBasePath}
                onChange={(event) => setStorageBasePath(event.target.value)}
                placeholder="datasets/project-a"
              />
            </label>
            <label>
              {t("rootPath")}
              <input
                value={storageRootPath}
                onChange={(event) => setStorageRootPath(event.target.value)}
                placeholder="/Users/longtaowu/workspace/emboldata"
              />
            </label>
          </div>
          <div className="button-row" style={{ marginTop: 12 }}>
            <button
              className="button-primary"
              onClick={async () => {
                setError(null);
                try {
                  await props.api.createStorageConnection({
                    workspaceId: props.bootstrap.workspace._id,
                    name: storageName || `${storageProvider} storage`,
                    provider: storageProvider,
                    bucket: storageBucket || undefined,
                    endpoint: storageEndpoint || undefined,
                    region: storageRegion || undefined,
                    basePath: storageBasePath || undefined,
                    rootPath: storageRootPath || undefined,
                  });
                  setStorageName("");
                  setStorageBucket("");
                  setStorageEndpoint("");
                  setStorageRegion("");
                  setStorageBasePath("");
                  setStorageRootPath("");
                  await loadData();
                } catch (createError) {
                  setError(
                    createError instanceof Error
                      ? createError.message
                      : t("failedCreateStorageConnection"),
                  );
                }
              }}
            >
              {t("createStorageConnection")}
            </button>
          </div>
          <div className="list-grid" style={{ marginTop: 16 }}>
            {storageConnections.length === 0 ? (
              <p className="empty-state">{t("noStorageConnectionsYet")}</p>
            ) : (
              storageConnections.map((connection) => (
                <article key={connection._id} className="asset-card">
                  <div className="toolbar">
                    <strong>{connection.name}</strong>
                    <span className="status-pill">{connection.provider}</span>
                  </div>
                  <p>{t("bucket")}: {connection.bucket ?? t("notAvailable")}</p>
                  <p>{t("endpoint")}: {connection.endpoint ?? connection.rootPath ?? t("notAvailable")}</p>
                  <p>{t("basePath")}: {connection.basePath ?? t("notAvailable")}</p>
                </article>
              ))
            )}
          </div>
        </section>

        <section className="panel">
          <h2>{t("datasetsTitle")}</h2>
          <p>{t("datasetsDescription")}</p>
          <div className="field-grid">
            <label>
              {t("datasetName")}
              <input
                value={datasetName}
                onChange={(event) => setDatasetName(event.target.value)}
                placeholder="Delivery Dataset"
              />
            </label>
            <label>
              {t("datasetDescription")}
              <input
                value={datasetDescription}
                onChange={(event) => setDatasetDescription(event.target.value)}
                placeholder="Project level dataset derived from source assets"
              />
            </label>
            <label>
              {t("sourceAsset")}
              <select
                value={selectedDatasetAssetId}
                onChange={(event) => setSelectedDatasetAssetId(event.target.value)}
              >
                {assets.length === 0 ? <option value="">{t("noAssetsAvailable")}</option> : null}
                {assets.map((asset) => (
                  <option key={asset._id} value={asset._id}>
                    {asset.displayName}
                  </option>
                ))}
              </select>
            </label>
            <label>
              {t("storageConnection")}
              <select
                value={selectedStorageConnectionId}
                onChange={(event) => setSelectedStorageConnectionId(event.target.value)}
              >
                {storageConnections.length === 0 ? <option value="">{t("noStorageConnectionsYet")}</option> : null}
                {storageConnections.map((connection) => (
                  <option key={connection._id} value={connection._id}>
                    {connection.name}
                  </option>
                ))}
              </select>
            </label>
            <label>
              {t("storagePathLabel")}
              <input
                value={datasetStoragePath}
                onChange={(event) => setDatasetStoragePath(event.target.value)}
                placeholder="delivery/dataset-v1"
              />
            </label>
          </div>
          <div className="button-row" style={{ marginTop: 12 }}>
            <button
              className="button-primary"
              disabled={!selectedDatasetAssetId || !selectedStorageConnectionId}
              onClick={async () => {
                setError(null);
                try {
                  await props.api.createDataset({
                    workspaceId: props.bootstrap.workspace._id,
                    projectId: props.bootstrap.project._id,
                    name:
                      datasetName ||
                      `Dataset from ${assets.find((asset) => asset._id === selectedDatasetAssetId)?.displayName ?? "asset"}`,
                    description: datasetDescription || undefined,
                    sourceAssetIds: [selectedDatasetAssetId],
                    storageConnectionId: selectedStorageConnectionId,
                    storagePath:
                      datasetStoragePath ||
                      `datasets/${datasetName ? datasetName.toLowerCase().replace(/\s+/gu, "-") : "dataset"}`,
                  });
                  setDatasetName("");
                  setDatasetDescription("");
                  setDatasetStoragePath("");
                  await loadData();
                } catch (createError) {
                  setError(
                    createError instanceof Error ? createError.message : t("failedCreateDataset"),
                  );
                }
              }}
            >
              {t("createDataset")}
            </button>
          </div>
          <div className="list-grid" style={{ marginTop: 16 }}>
            {datasets.length === 0 ? (
              <p className="empty-state">{t("noDatasetsYet")}</p>
            ) : (
              datasets.map((dataset) => (
                <article key={dataset._id} className="asset-card">
                  <strong>{dataset.name}</strong>
                  <p>{t("status")}: {translateStatus(dataset.status, t)}</p>
                  <p>{t("sourceAssets")}: {(dataset.sourceAssetIds ?? []).join(", ") || t("none")}</p>
                  <p>{t("storageConnection")}: {storageConnections.find((item) => item._id === dataset.storageConnectionId)?.name ?? dataset.storageConnectionId}</p>
                  <p>{t("storagePathLabel")}: {dataset.storagePath}</p>
                  <p>{t("latestDatasetVersion")}: {dataset.latestVersionNumber}</p>
                </article>
              ))
            )}
          </div>
        </section>
      </section>

      <section className="panel">
        <div className="list-grid">
          {assets.length === 0 ? (
            <p className="empty-state">{t("noAssetsYet")}</p>
          ) : (
            assets.map((asset) => (
              <article key={asset._id} className="asset-card">
                <div className="toolbar">
                  <a href={`/assets/${asset._id}`}>
                    <strong>{asset.displayName}</strong>
                  </a>
                  <span className="status-pill" data-status={asset.status}>
                    {translateStatus(asset.status, t)}
                  </span>
                </div>
                <p>{t("type")}: {asset.type}</p>
                <p>{t("source")}: {asset.sourceType}</p>
                <p>{t("detected")}: {(asset.detectedFormats ?? []).join(", ") || t("pending")}</p>
                <p>{t("topLevelEntries")}: {(asset.topLevelPaths ?? []).slice(0, 6).join(", ") || t("notAvailable")}</p>
              </article>
            ))
          )}
        </div>
      </section>
    </div>
  );
}

function NodesPage(props: {
  api: ApiClient;
  bootstrap: BootstrapContext;
}) {
  const { t } = useI18n();
  const [nodes, setNodes] = useState<any[]>([]);
  const [name, setName] = useState("");
  const [description, setDescription] = useState("");
  const [category, setCategory] = useState<"Transform" | "Inspect" | "Utility" | "Export">("Utility");
  const [sourceKind, setSourceKind] = useState<"image" | "dockerfile">("image");
  const [image, setImage] = useState("python:3.11-alpine");
  const [dockerfileContent, setDockerfileContent] = useState("");
  const [commandText, setCommandText] = useState("");
  const [inputMode, setInputMode] = useState<"single_asset_set" | "multi_asset_set">("single_asset_set");
  const [outputMode, setOutputMode] = useState<"report" | "asset_set" | "asset_set_with_report">("report");
  const [artifactType, setArtifactType] = useState<"json" | "directory" | "video">("json");
  const [error, setError] = useState<string | null>(null);
  const customNodeValidationIssues = useMemo(
    () =>
      validateCustomNodeDefinition({
        name,
        category,
        source:
          sourceKind === "image"
            ? {
                kind: "image",
                image,
                command: parseCommandLines(commandText),
              }
            : {
                kind: "dockerfile",
                dockerfileContent,
                command: parseCommandLines(commandText),
              },
        contract: {
          inputMode,
          outputMode,
          artifactType,
        },
      }),
    [artifactType, category, commandText, dockerfileContent, image, inputMode, name, outputMode, sourceKind],
  );

  const loadCustomNodes = useCallback(async () => {
    try {
      setNodes(await props.api.listCustomNodes(props.bootstrap.project._id));
    } catch (loadError) {
      setError(loadError instanceof Error ? loadError.message : t("failedLoadCustomNodes"));
    }
  }, [props.api, props.bootstrap.project._id, t]);

  useEffect(() => {
    void loadCustomNodes();
  }, [loadCustomNodes]);

  return (
    <div className="page-stack">
      <section className="panel">
        <h1>{t("nodesTitle")}</h1>
        <p>{t("nodesDescription")}</p>
        {error ? <p>{error}</p> : null}
        <div className="field-grid">
          <label>
            {t("customNodeName")}
            <input value={name} onChange={(event) => setName(event.target.value)} placeholder="Asset Merge" />
          </label>
          <label>
            {t("customNodeCategory")}
            <select value={category} onChange={(event) => setCategory(event.target.value as typeof category)}>
              <option value="Transform">Transform</option>
              <option value="Inspect">Inspect</option>
              <option value="Utility">Utility</option>
              <option value="Export">Export</option>
            </select>
          </label>
          <label>
            {t("customNodeDescription")}
            <textarea value={description} rows={3} onChange={(event) => setDescription(event.target.value)} />
          </label>
          <label>
            {t("customNodeSourceKind")}
            <select value={sourceKind} onChange={(event) => setSourceKind(event.target.value as typeof sourceKind)}>
              <option value="image">{t("customNodeSourceImage")}</option>
              <option value="dockerfile">{t("customNodeSourceDockerfile")}</option>
            </select>
          </label>
          <label>
            {t("customNodeInputMode")}
            <select value={inputMode} onChange={(event) => setInputMode(event.target.value as typeof inputMode)}>
              <option value="single_asset_set">{t("customNodeSingleAssetSet")}</option>
              <option value="multi_asset_set">{t("customNodeMultiAssetSet")}</option>
            </select>
          </label>
          <label>
            {t("customNodeOutputMode")}
            <select value={outputMode} onChange={(event) => setOutputMode(event.target.value as typeof outputMode)}>
              <option value="report">{t("customNodeReport")}</option>
              <option value="asset_set">{t("customNodeAssetSet")}</option>
              <option value="asset_set_with_report">{t("customNodeAssetSetWithReport")}</option>
            </select>
          </label>
          <label>
            {t("customNodeArtifactType")}
            <select value={artifactType} onChange={(event) => setArtifactType(event.target.value as typeof artifactType)}>
              <option value="json">json</option>
              <option value="directory">directory</option>
              <option value="video">video</option>
            </select>
          </label>
          <label>
            {t("customNodeCommand")}
            <textarea
              rows={4}
              value={commandText}
              onChange={(event) => setCommandText(event.target.value)}
              placeholder={["python3", "-c", "print('custom node')"].join("\n")}
            />
          </label>
          {sourceKind === "image" ? (
            <label>
              {t("customNodeImage")}
              <input value={image} onChange={(event) => setImage(event.target.value)} placeholder="python:3.11-alpine" />
            </label>
          ) : (
            <>
              <label>
                {t("customNodeDockerfile")}
                <textarea
                  rows={10}
                  value={dockerfileContent}
                  onChange={(event) => setDockerfileContent(event.target.value)}
                  placeholder={["FROM python:3.11-alpine", "CMD [\"python3\", \"-c\", \"print('custom node')\"]"].join("\n")}
                />
              </label>
              <label>
                {t("customNodeDockerfileUpload")}
                <input
                  type="file"
                  accept=".dockerfile,.Dockerfile,text/plain"
                  onChange={async (event) => {
                    const file = event.target.files?.[0];
                    if (!file) {
                      return;
                    }
                    setDockerfileContent(await file.text());
                  }}
                />
              </label>
            </>
          )}
        </div>
        <div className="button-row" style={{ marginTop: 12 }}>
          {customNodeValidationIssues.length > 0 ? (
            <div>
              {customNodeValidationIssues.map((issue) => (
                <p key={issue}>{t(mapCustomNodeValidationIssueToKey(issue))}</p>
              ))}
            </div>
          ) : null}
          <button
            className="button-primary"
            onClick={async () => {
              if (customNodeValidationIssues.length > 0) {
                setError(t(mapCustomNodeValidationIssueToKey(customNodeValidationIssues[0])));
                return;
              }
              try {
                setError(null);
                await props.api.createCustomNode({
                  workspaceId: props.bootstrap.workspace._id,
                  projectId: props.bootstrap.project._id,
                  name,
                  description,
                  category,
                  source:
                    sourceKind === "image"
                      ? {
                          kind: "image",
                          image,
                          command: parseCommandLines(commandText),
                        }
                      : {
                          kind: "dockerfile",
                          dockerfileContent,
                          command: parseCommandLines(commandText),
                        },
                  contract: {
                    inputMode,
                    outputMode,
                    artifactType,
                  },
                  createdBy: props.bootstrap.userId,
                });
                setName("");
                setDescription("");
                setCommandText("");
                if (sourceKind === "dockerfile") {
                  setDockerfileContent("");
                }
                await loadCustomNodes();
              } catch (createError) {
                setError(createError instanceof Error ? createError.message : t("failedCreateCustomNode"));
              }
            }}
            disabled={customNodeValidationIssues.length > 0}
          >
            {t("createCustomNode")}
          </button>
        </div>
      </section>

      <section className="panel">
        <h2>{t("nodesTitle")}</h2>
        {nodes.length === 0 ? (
          <p className="empty-state">{t("noCustomNodesYet")}</p>
        ) : (
          <div className="list-grid">
            {nodes.map((node) => (
              <article key={node._id} className="node-card">
                <strong>{node.name}</strong>
                <p>{node.description || t("none")}</p>
                <p>{t("category")}: {node.category}</p>
                <p>{t("customNodeSourceKind")}: {node.source?.kind}</p>
                <p>{t("customNodeInputMode")}: {node.contract?.inputMode}</p>
                <p>{t("customNodeOutputMode")}: {node.contract?.outputMode}</p>
                <p>{t("customNodeArtifactType")}: {node.contract?.artifactType}</p>
                <p>{t("definition")}: {node.definitionId}</p>
              </article>
            ))}
          </div>
        )}
      </section>
    </div>
  );
}

function AssetDetailPage(props: {
  api: ApiClient;
  assetId: string;
}) {
  const { t } = useI18n();
  const [asset, setAsset] = useState<any | null>(null);
  const [probeReport, setProbeReport] = useState<any | null>(null);
  const [artifactId, setArtifactId] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    void (async () => {
      try {
        const [assetData, reportData] = await Promise.all([
          props.api.getAsset(props.assetId),
          props.api.getProbeReport(props.assetId).catch(() => null),
        ]);
        setAsset(assetData);
        setProbeReport(reportData);
      } catch (loadError) {
        setError(loadError instanceof Error ? loadError.message : "Failed to load asset detail");
      }
    })();
  }, [props.assetId]);

  if (error) {
    return <section className="panel">{error}</section>;
  }
  if (!asset) {
    return <section className="panel">{t("loadingAssetDetail")}</section>;
  }

  return (
    <div className="page-stack">
      <section className="two-column">
        <div className="panel">
          <h1>{asset.displayName}</h1>
          <p>{t("assetId")}: {asset._id}</p>
          <p>{t("type")}: {asset.type}</p>
          <p>{t("status")}: {translateStatus(asset.status, t)}</p>
          <p>{t("sourcePath")}: {asset.sourcePath ?? t("notAvailable")}</p>
          <div className="button-row" style={{ marginTop: 12 }}>
            <button
              className="button-primary"
              onClick={async () => {
                const report = await props.api.probeAsset(asset._id);
                setProbeReport(report);
                const reloaded = await props.api.getAsset(asset._id);
                setAsset(reloaded);
              }}
            >
              {t("probeAgain")}
            </button>
            <button
              className="button-secondary"
              onClick={async () => {
                if (!probeReport) {
                  return;
                }
                const artifact = await props.api.createArtifact({
                  type: "json",
                  title: `Probe Report: ${asset.displayName}`,
                  producerType: "asset",
                  producerId: asset._id,
                  payload: probeReport.rawReport ?? {},
                });
                setArtifactId(artifact._id);
              }}
            >
              {t("createExploreArtifact")}
            </button>
            {artifactId ? <a href={`/explore/${artifactId}`}>{t("openExploreView")}</a> : null}
          </div>
        </div>

        <aside className="panel">
          <h2>{t("probeSummary")}</h2>
          {probeReport ? (
            <>
              <p>{t("detected")}: {(probeReport.detectedFormatCandidates ?? []).join(", ")}</p>
              <p>{t("warnings")}: {(probeReport.warnings ?? []).join(", ") || t("none")}</p>
              <p>
                {t("recommendedNodes")}: {(probeReport.recommendedNextNodes ?? []).join(", ") || "inspect_asset"}
              </p>
              <pre className="mono-block">
                {JSON.stringify(probeReport.structureSummary ?? {}, null, 2)}
              </pre>
            </>
          ) : (
            <p className="empty-state">{t("noProbeReportYet")}</p>
          )}
        </aside>
      </section>
    </div>
  );
}

function WorkflowsPage(props: {
  api: ApiClient;
  bootstrap: BootstrapContext;
}) {
  const { t } = useI18n();
  const [workflows, setWorkflows] = useState<any[]>([]);
  const [templates, setTemplates] = useState<any[]>([]);
  const [error, setError] = useState<string | null>(null);

  const load = async () => {
    try {
      const [nextWorkflows, nextTemplates] = await Promise.all([
        props.api.listWorkflows(props.bootstrap.project._id),
        props.api.listWorkflowTemplates({
          workspaceId: props.bootstrap.workspace._id,
          projectId: props.bootstrap.project._id,
        }),
      ]);
      setWorkflows(nextWorkflows);
      setTemplates(nextTemplates);
      setError(null);
    } catch (loadError) {
      setError(loadError instanceof Error ? loadError.message : t("failedLoadTemplates"));
    }
  };

  useEffect(() => {
    void load();
  }, [props.bootstrap.project._id, props.bootstrap.workspace._id]);

  return (
    <div className="page-stack">
      <section className="panel">
        <div className="toolbar">
          <h1 style={{ margin: 0 }}>{t("workflowsTitle")}</h1>
          <button
            className="button-primary"
            onClick={async () => {
              const workflow = await props.api.createWorkflow({
                workspaceId: props.bootstrap.workspace._id,
                projectId: props.bootstrap.project._id,
                name: t("workflowCreatedName", { count: workflows.length + 1 }),
              });
              navigateTo(`/workflows/${workflow._id}`);
              await load();
            }}
          >
            {t("createBlankWorkflow")}
          </button>
        </div>
        {error ? <p>{error}</p> : null}
      </section>

      <section className="two-column">
        <section className="panel">
          <h2>{t("workflowTemplatesTitle")}</h2>
          <p>{t("workflowTemplatesDescription")}</p>
          <div className="list-grid">
            {templates.length === 0 ? (
              <p className="empty-state">{t("noWorkflowTemplatesYet")}</p>
            ) : (
              templates.map((template) => (
                <article key={template._id} className="asset-card">
                  <div className="toolbar">
                    <strong>{template.name}</strong>
                    <span className="status-pill">{template.projectId ? t("project") : t("workspace")}</span>
                  </div>
                  <p>{template.description || t("notAvailable")}</p>
                  <p>{t("status")}: {translateStatus(template.status, t)}</p>
                  <div className="button-row" style={{ marginTop: 12 }}>
                    <button
                      className="button-primary"
                      onClick={async () => {
                        try {
                          setError(null);
                          const workflow = await props.api.createWorkflowFromTemplate({
                            templateId: template._id,
                            workspaceId: props.bootstrap.workspace._id,
                            projectId: props.bootstrap.project._id,
                            name: `${template.name} ${workflows.length + 1}`,
                            createdBy: props.bootstrap.userId,
                          });
                          navigateTo(`/workflows/${workflow._id}`);
                        } catch (createError) {
                          setError(
                            createError instanceof Error
                              ? createError.message
                              : t("failedCreateWorkflowFromTemplate"),
                          );
                        }
                      }}
                    >
                      {t("createWorkflowFromTemplate")}
                    </button>
                  </div>
                </article>
              ))
            )}
          </div>
        </section>

        <section className="panel">
          <div className="toolbar">
            <h2 style={{ margin: 0 }}>{t("workflowsTitle")}</h2>
          </div>
          <div className="list-grid" style={{ marginTop: 12 }}>
            {workflows.length === 0 ? (
              <p className="empty-state">{t("noWorkflowsYet")}</p>
            ) : (
              workflows.map((workflow) => (
                <article key={workflow._id} className="asset-card">
                  <a href={`/workflows/${workflow._id}`}>
                    <strong>{workflow.name}</strong>
                  </a>
                  <p>{t("status")}: {translateStatus(workflow.status, t)}</p>
                  <p>{t("latestVersion")}: {workflow.latestVersionNumber}</p>
                </article>
              ))
            )}
          </div>
        </section>
      </section>
    </div>
  );
}

function WorkflowEditorPage(props: {
  api: ApiClient;
  workflowId: string;
}) {
  const { language, t } = useI18n();
  const [workflow, setWorkflow] = useState<any | null>(null);
  const [nodes, setNodes] = useState<any[]>([]);
  const [assets, setAssets] = useState<any[]>([]);
  const [selectedAssetId, setSelectedAssetId] = useState<string | null>(null);
  const [versions, setVersions] = useState<any[]>([]);
  const [draft, setDraft] = useState<WorkflowDraft>(() => createDefaultWorkflowDraft());
  const [selectedNodeId, setSelectedNodeId] = useState("rename-folder");
  const [lastRunId, setLastRunId] = useState<string | null>(null);
  const [dirty, setDirty] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [templateName, setTemplateName] = useState("");
  const [templateDescription, setTemplateDescription] = useState("");
  const [savedTemplateName, setSavedTemplateName] = useState<string | null>(null);
  const [canvasFeedbackKey, setCanvasFeedbackKey] = useState<TranslationKey | null>(null);
  const [canvasDropActive, setCanvasDropActive] = useState(false);
  const [flowInstance, setFlowInstance] = useState<ReactFlowInstance<Node, Edge> | null>(null);
  const [preflightResult, setPreflightResult] = useState<WorkflowPreflightResult | null>(null);
  const [preflightBusy, setPreflightBusy] = useState(false);

  useEffect(() => {
    void (async () => {
      try {
        const workflowDefinition = await props.api.getWorkflowDefinition(props.workflowId);
        const [nodeDefs, workflowVersions, workflowAssets] = await Promise.all([
          props.api.listNodeDefinitions(workflowDefinition.projectId),
          props.api.listWorkflowVersions(props.workflowId),
          props.api.listAssets(workflowDefinition.projectId),
        ]);
        setWorkflow(workflowDefinition);
        setNodes(nodeDefs);
        setAssets(workflowAssets);
        setSelectedAssetId((previous) => {
          if (previous && workflowAssets.some((asset) => asset._id === previous)) {
            return previous;
          }
          return workflowAssets[0]?._id ?? null;
        });
        setVersions(workflowVersions);
        const nextDraft = workflowDraftFromVersion(workflowVersions[0] ?? null);
        setDraft(nextDraft);
        setSelectedNodeId(nextDraft.logicGraph.nodes[0]?.id ?? "rename-folder");
        setTemplateName(`${workflowDefinition.name} Template`);
        setTemplateDescription("");
        setSavedTemplateName(null);
        setDirty(false);
        setCanvasFeedbackKey(null);
        setPreflightResult(null);
      } catch (loadError) {
        setError(loadError instanceof Error ? loadError.message : t("failedLoadWorkflow"));
      }
    })();
  }, [props.workflowId, props.api, t]);

  const localizedNodes = useMemo(
    () => nodes.map((node) => localizeNodeDefinition(language, node)),
    [language, nodes],
  );
  const nodeDefinitionsById = useMemo(
    () => new Map(nodes.map((node) => [node.id, node])),
    [nodes],
  );

  const selectedNodeDefinitionId = useMemo(
    () => resolveDefinitionIdForNode(draft, selectedNodeId),
    [draft, selectedNodeId],
  );
  const selectedNodeRaw = useMemo(
    () => nodes.find((node) => node.id === selectedNodeDefinitionId) ?? null,
    [nodes, selectedNodeDefinitionId],
  );
  const selectedNode = useMemo(
    () => (selectedNodeRaw ? localizeNodeDefinition(language, selectedNodeRaw) : null),
    [language, selectedNodeRaw],
  );
  const selectedNodeSupportsCodeHook = useMemo(
    () => {
      if (typeof selectedNodeRaw?.supportsCodeHook === "boolean") {
        return selectedNodeRaw.supportsCodeHook;
      }
      return (
        selectedNodeRaw?.category === "Transform" ||
        selectedNodeRaw?.category === "Inspect" ||
        selectedNodeRaw?.category === "Utility"
      );
    },
    [selectedNodeRaw],
  );
  const selectedNodeRuntimeConfig = useMemo(
    () => getNodeRuntimeConfig(draft, selectedNodeId),
    [draft, selectedNodeId],
  );
  const selectedNodeEffectiveRuntimeConfig = useMemo(
    () => getEffectiveNodeRuntimeConfig(selectedNodeRaw, selectedNodeRuntimeConfig),
    [selectedNodeRaw, selectedNodeRuntimeConfig],
  );
  const selectedNodeContract = useMemo(
    () =>
      (selectedNodeEffectiveRuntimeConfig.executorConfig as {
        contract?: {
          inputMode?: string;
          outputMode?: string;
          artifactType?: string;
        };
      } | undefined)?.contract,
    [selectedNodeEffectiveRuntimeConfig],
  );
  const selectedNodeSourceKind = useMemo(
    () =>
      (selectedNodeEffectiveRuntimeConfig.executorConfig as {
        kind?: string;
      } | undefined)?.kind,
    [selectedNodeEffectiveRuntimeConfig],
  );
  const selectedNodeEnvelopePreview = useMemo(() => {
    if (
      !selectedNodeContract ||
      typeof selectedNodeContract.inputMode !== "string" ||
      typeof selectedNodeContract.outputMode !== "string" ||
      typeof selectedNodeContract.artifactType !== "string"
    ) {
      return null;
    }
    return buildCustomNodeEnvelopePreview({
      inputMode: selectedNodeContract.inputMode as "single_asset_set" | "multi_asset_set",
      outputMode: selectedNodeContract.outputMode as "report" | "asset_set" | "asset_set_with_report",
      artifactType: selectedNodeContract.artifactType as "json" | "directory" | "video",
    });
  }, [selectedNodeContract]);
  const canvasNodes = useMemo<Array<Node>>(
    () =>
      draft.logicGraph.nodes.map((node) => {
        const definitionId = resolveDefinitionIdForNode(draft, node.id);
        const definition =
          localizedNodes.find((candidate) => candidate.id === definitionId) ??
          localizeNodeDefinition(language, {
            id: definitionId,
            name: definitionId,
            description: definitionId,
            category: node.type,
          });
        const position = draft.visualGraph.nodePositions[node.id] ?? { x: 0, y: 0 };
        return {
          id: node.id,
          position,
          type:
            node.type === "source"
              ? "input"
              : node.type === "export"
                ? "output"
                : "default",
          className: `workflow-flow-node workflow-flow-node--${node.type}`,
          data: {
            label: (
              <div className="workflow-flow-node__body">
                <strong>{definition.name}</strong>
                <span>{node.id}</span>
              </div>
            ),
          },
        } satisfies Node;
      }),
    [draft, language, localizedNodes],
  );
  const canvasEdges = useMemo<Array<Edge>>(
    () =>
      draft.logicGraph.edges.map((edge) => ({
        id: `${edge.from}->${edge.to}`,
        source: edge.from,
        target: edge.to,
        type: "smoothstep",
        animated: selectedNodeId === edge.from || selectedNodeId === edge.to,
      })),
    [draft.logicGraph.edges, selectedNodeId],
  );

  const onCanvasNodesChange = useCallback((changes: NodeChange[]) => {
    const positionChanges = changes.filter(
      (change): change is NodeChange & { id: string; position: { x: number; y: number } } =>
        change.type === "position" && Boolean(change.position),
    );
    const selectedChange = changes.find(
      (change): change is NodeChange & { id: string; selected: boolean } =>
        change.type === "select" && typeof change.selected === "boolean" && change.selected,
    );

    if (selectedChange) {
      setSelectedNodeId(selectedChange.id);
    }
    if (positionChanges.length === 0) {
      return;
    }

    setDraft((current) =>
      positionChanges.reduce(
        (nextDraft, change) => setNodePosition(nextDraft, change.id, change.position),
        current,
      ),
    );
    setDirty(true);
  }, []);

  const onCanvasConnect = useCallback((connection: Connection) => {
    setDraft((current) => {
      const validation = canConnectNodesInDraft(current, connection.source, connection.target);
      if (!validation.ok) {
        setCanvasFeedbackKey(mapConnectionValidationReasonToKey(validation.reason));
        return current;
      }

      setCanvasFeedbackKey(null);
      setDirty(true);
      return connectNodesInDraft(current, connection.source, connection.target);
    });
  }, []);

  const onViewportChangeEnd = useCallback((viewport: Viewport) => {
    setDraft((current) =>
      setViewportInDraft(current, {
        x: viewport.x,
        y: viewport.y,
        zoom: viewport.zoom,
      }),
    );
    setDirty(true);
  }, []);

  const handleNodeLibraryDragStart = useCallback((event: React.DragEvent<HTMLButtonElement>, nodeId: string) => {
    event.dataTransfer.setData(NODE_LIBRARY_MIME, nodeId);
    event.dataTransfer.effectAllowed = "copy";
  }, []);

  const handleCanvasDragOver = useCallback((event: React.DragEvent<HTMLDivElement>) => {
    if (!event.dataTransfer.types.includes(NODE_LIBRARY_MIME)) {
      return;
    }
    event.preventDefault();
    event.dataTransfer.dropEffect = "copy";
    setCanvasDropActive(true);
  }, []);

  const handleCanvasDrop = useCallback((event: React.DragEvent<HTMLDivElement>) => {
    const definitionId = event.dataTransfer.getData(NODE_LIBRARY_MIME);
    if (!definitionId) {
      setCanvasDropActive(false);
      return;
    }

    event.preventDefault();
    const definition = nodeDefinitionsById.get(definitionId);
    if (!definition) {
      setCanvasDropActive(false);
      setCanvasFeedbackKey("invalidConnectionMissingEndpoint");
      return;
    }

    const droppedAt = flowInstance
      ? flowInstance.screenToFlowPosition({ x: event.clientX, y: event.clientY })
      : { x: event.clientX, y: event.clientY };
    const result = addNodeToDraftAtPosition(draft, definition, droppedAt);
    setDraft(result.draft);
    setSelectedNodeId(result.nodeId);
    setDirty(true);
    setCanvasFeedbackKey(null);
    setCanvasDropActive(false);
  }, [draft, flowInstance, nodeDefinitionsById]);

  const handleCanvasDragLeave = useCallback(() => {
    setCanvasDropActive(false);
  }, []);

  function updateSelectedNodeRuntimeConfig(
    nextConfig: WorkflowNodeRuntimeConfig | ((current: WorkflowNodeRuntimeConfig) => WorkflowNodeRuntimeConfig),
  ) {
    if (!selectedNodeId) {
      return;
    }
    const currentConfig = getEffectiveNodeRuntimeConfig(
      selectedNodeRaw,
      getNodeRuntimeConfig(draft, selectedNodeId),
    );
    const resolved = typeof nextConfig === "function" ? nextConfig(currentConfig) : nextConfig;
    setDraft(setNodeRuntimeConfig(draft, selectedNodeId, resolved));
    setDirty(true);
  }

  async function runWorkflowChecks(versionId: string) {
    if (!selectedAssetId) {
      setPreflightResult(null);
      setError(t("selectAssetBeforeRun"));
      return null;
    }

    setPreflightBusy(true);
    try {
      const result = await props.api.preflightRun({
        workflowDefinitionId: props.workflowId,
        workflowVersionId: versionId,
        assetIds: [selectedAssetId],
      });
      setPreflightResult(result);
      return result;
    } catch (preflightError) {
      setError(preflightError instanceof Error ? preflightError.message : t("checksBlocked"));
      return null;
    } finally {
      setPreflightBusy(false);
    }
  }

  async function saveCurrentDraft() {
    const version = await props.api.saveWorkflowVersion(
      props.workflowId,
      serializeWorkflowDraft(draft),
    );
    setVersions((previous) => [version, ...previous.filter((item) => item._id !== version._id)]);
    setDirty(false);
    if (selectedAssetId) {
      await runWorkflowChecks(version._id);
    } else {
      setPreflightResult(null);
    }
    return version;
  }

  useEffect(() => {
    if (dirty || !selectedAssetId || versions.length === 0) {
      return;
    }

    void runWorkflowChecks(versions[0]._id);
  }, [dirty, selectedAssetId, versions, props.workflowId]);

  return (
    <div className="page-stack">
      <section className="panel">
        <div className="toolbar">
          <h1 style={{ margin: 0 }}>{workflow?.name ?? t("workflowEditor")}</h1>
          <label style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <span>{t("runAsset")}</span>
            <select
              value={selectedAssetId ?? ""}
              onChange={(event) => setSelectedAssetId(event.target.value || null)}
            >
              {assets.length === 0 ? <option value="">{t("noAssetsAvailable")}</option> : null}
              {assets.map((asset) => (
                <option key={asset._id} value={asset._id}>
                  {asset.displayName}
                </option>
              ))}
            </select>
          </label>
          <button
            className="button-secondary"
            onClick={async () => {
              const latestVersion = dirty || versions.length === 0
                ? await saveCurrentDraft()
                : versions[0];
              await runWorkflowChecks(latestVersion._id);
            }}
            disabled={!selectedAssetId || preflightBusy}
          >
            {t("runChecks")}
          </button>
          <button
            className="button-primary"
            onClick={async () => {
              await saveCurrentDraft();
            }}
          >
            {t("saveWorkflowVersion")}
          </button>
          <button
            className="button-secondary"
            onClick={async () => {
              if (!selectedAssetId) {
                setError(t("selectAssetBeforeRun"));
                return;
              }
              const latestVersion = dirty || versions.length === 0
                ? await saveCurrentDraft()
                : versions[0];
              const preflight = await runWorkflowChecks(latestVersion._id);
              if (!preflight?.ok) {
                setError(preflight?.issues[0]?.message ?? t("checksBlocked"));
                return;
              }
              const run = await props.api.createRun({
                workflowDefinitionId: props.workflowId,
                workflowVersionId: latestVersion._id,
                assetIds: [selectedAssetId],
              });
              setLastRunId(run._id);
            }}
            disabled={!selectedAssetId}
          >
            {t("triggerWorkflowRun")}
          </button>
          <button
            className="button-secondary"
            onClick={() => {
              const latestDraft = workflowDraftFromVersion(versions[0] ?? null);
              setDraft(latestDraft);
              setSelectedNodeId(latestDraft.logicGraph.nodes[0]?.id ?? "rename-folder");
              setDirty(false);
            }}
          >
            {t("reloadLatestSaved")}
          </button>
          {lastRunId ? <a href={`/runs/${lastRunId}`}>{t("openLatestRun")}</a> : null}
        </div>
        {error ? <p>{error}</p> : null}
        <div style={{ marginTop: 12, display: "grid", gap: 8 }}>
          <div className="toolbar">
            <strong>{t("workflowChecks")}</strong>
            {preflightBusy ? <span>{t("running")}</span> : null}
          </div>
          {preflightResult ? (
            <>
              <p>
                {preflightResult.ok ? t("checksPassed") : t("checksBlocked")} ·{" "}
                {t("checkErrors", { count: preflightResult.summary.errorCount })} ·{" "}
                {t("checkWarnings", { count: preflightResult.summary.warningCount })}
              </p>
              {preflightResult.issues.length > 0 ? (
                <ul>
                  {preflightResult.issues.map((issue, index) => (
                    <li key={`${issue.code}-${issue.nodeId ?? "global"}-${index}`}>
                      {issue.message}
                      {issue.nodeId ? ` (${issue.nodeId})` : ""}
                    </li>
                  ))}
                </ul>
              ) : null}
            </>
          ) : (
            <p className="empty-state">{t("noChecksRunYet")}</p>
          )}
        </div>
      </section>

      <section className="editor-layout">
        <aside className="panel">
          <h2>{t("nodeLibrary")}</h2>
          <p className="empty-state">{t("nodeLibraryHint")}</p>
          <div className="list-grid">
            {localizedNodes.map((node) => (
              <button
                key={node.id}
                className="button-secondary workflow-node-library-item"
                draggable
                onDragStart={(event) => handleNodeLibraryDragStart(event, node.id)}
                onClick={() => {
                  const rawNode = nodeDefinitionsById.get(node.id) ?? node;
                  const result = addNodeToDraft(draft, rawNode);
                  setDraft(result.draft);
                  setSelectedNodeId(result.nodeId);
                  setDirty(true);
                  setCanvasFeedbackKey(null);
                }}
              >
                {node.name}
              </button>
            ))}
          </div>
        </aside>

        <section className="panel workflow-canvas-panel">
          <div className="toolbar">
            <h2 style={{ margin: 0 }}>{t("canvas")}</h2>
            <span className="app-header__label">{t("canvasHint")}</span>
            {canvasFeedbackKey ? (
              <span className="workflow-canvas-feedback" data-tone="danger">
                {t(canvasFeedbackKey)}
              </span>
            ) : null}
          </div>
          <div
            className="workflow-canvas-shell"
            data-drop-active={String(canvasDropActive)}
            onDragOver={handleCanvasDragOver}
            onDrop={handleCanvasDrop}
            onDragLeave={handleCanvasDragLeave}
          >
            {canvasDropActive ? (
              <div className="workflow-canvas-drop-hint">{t("dragNodeToCanvas")}</div>
            ) : null}
            <ReactFlow
              key={versions[0]?._id ?? workflow?._id ?? "workflow-canvas"}
              nodes={canvasNodes}
              edges={canvasEdges}
              defaultViewport={draft.visualGraph.viewport}
              onInit={setFlowInstance}
              onNodesChange={onCanvasNodesChange}
              onConnect={onCanvasConnect}
              onNodeClick={(_event, node) => setSelectedNodeId(node.id)}
              onMoveEnd={(_event, viewport) => onViewportChangeEnd(viewport)}
              fitView
              fitViewOptions={{ padding: 0.22 }}
              proOptions={{ hideAttribution: true }}
            >
              <MiniMap pannable zoomable />
              <Controls position="bottom-left" />
              <Background
                variant={BackgroundVariant.Dots}
                gap={18}
                size={1}
                color="#d4d4d8"
              />
            </ReactFlow>
          </div>
          <div className="workflow-canvas-footer">
            <p>
              {t("latestSavedVersions")}: {versions.length > 0 ? versions.map((item) => item.versionNumber).join(", ") : t("none")}
            </p>
            <p>{t("draftStatus")}: {dirty ? t("draftUnsaved") : t("draftSynced")}</p>
          </div>
        </section>

        <aside className="panel">
          <h2>{t("nodeConfiguration")}</h2>
          {selectedNode ? (
            <>
              <p><strong>{selectedNode.name}</strong></p>
              <p>{selectedNode.description}</p>
              <p>{t("category")}: {selectedNode.category}</p>
              <p>{t("definition")}: {selectedNodeDefinitionId}</p>
              {selectedNodeContract ? (
                <>
                  <p>{t("customNodeInputMode")}: {t(formatCustomNodeInputModeKey(selectedNodeContract.inputMode))}</p>
                  <p>{t("customNodeOutputMode")}: {t(formatCustomNodeOutputModeKey(selectedNodeContract.outputMode))}</p>
                  <p>{t("customNodeArtifactType")}: {selectedNodeContract.artifactType ?? t("none")}</p>
                  <p>{t("customNodeSourceKind")}: {t(formatCustomNodeSourceKindKey(selectedNodeSourceKind))}</p>
                  {selectedNodeEnvelopePreview ? (
                    <>
                      <label style={{ display: "grid", gap: 8 }}>
                        <span>{t("inputEnvelope")}</span>
                        <pre>{JSON.stringify(selectedNodeEnvelopePreview.input, null, 2)}</pre>
                      </label>
                      <label style={{ display: "grid", gap: 8 }}>
                        <span>{t("outputEnvelope")}</span>
                        <pre>{JSON.stringify(selectedNodeEnvelopePreview.output, null, 2)}</pre>
                      </label>
                    </>
                  ) : null}
                </>
              ) : null}
              <div className="field-grid">
                <label>
                  {t("executorType")}
                  <select
                    value={selectedNodeEffectiveRuntimeConfig.executorType ?? "python"}
                    onChange={(event) =>
                      updateSelectedNodeRuntimeConfig((current) => ({
                        ...current,
                        executorType: event.target.value as "python" | "docker" | "http",
                      }))
                    }
                  >
                    <option value="python">python</option>
                    <option value="docker">docker</option>
                    <option value="http">http</option>
                  </select>
                </label>
                <label>
                  {t("runtimeTarget")}
                  <input
                    value={
                      selectedNodeEffectiveRuntimeConfig.executorType === "http"
                        ? String(selectedNodeEffectiveRuntimeConfig.executorConfig?.url ?? "")
                        : selectedNodeEffectiveRuntimeConfig.executorType === "docker"
                          ? String(
                              selectedNodeEffectiveRuntimeConfig.executorConfig?.image ??
                                selectedNodeEffectiveRuntimeConfig.executorConfig?.imageTag ??
                                "",
                            )
                          : ""
                    }
                    placeholder={
                      selectedNodeEffectiveRuntimeConfig.executorType === "http"
                        ? "http://127.0.0.1:3010/mock-executor"
                        : selectedNodeEffectiveRuntimeConfig.executorType === "docker"
                          ? "python:3.11-alpine"
                          : "python executor uses inline hook or default"
                    }
                    onChange={(event) =>
                      updateSelectedNodeRuntimeConfig((current) => ({
                        ...current,
                        executorConfig:
                          current.executorType === "http"
                            ? {
                                ...(current.executorConfig ?? {}),
                                url: event.target.value,
                                method: "POST",
                              }
                            : current.executorType === "docker"
                              ? {
                                  ...(current.executorConfig ?? {}),
                                  image: event.target.value,
                                }
                              : current.executorConfig,
                      }))
                    }
                  />
                </label>
                <label>
                  {t("artifactTitle")}
                  <input
                    value={selectedNodeRuntimeConfig?.artifactTitle ?? ""}
                    placeholder="Task Result: validate-structure"
                    onChange={(event) =>
                      updateSelectedNodeRuntimeConfig((current) => ({
                        ...current,
                        artifactTitle: event.target.value,
                      }))
                    }
                  />
                </label>
              </div>
              {selectedNodeSupportsCodeHook ? (
                <label style={{ display: "grid", gap: 8 }}>
                  <span>{t("pythonCodeHook")}</span>
                  <textarea
                    rows={8}
                    value={selectedNodeRuntimeConfig?.codeHookSpec?.source ?? ""}
                    placeholder={[
                      "def process(task, context):",
                      "    return {'nodeId': task['nodeId'], 'ok': True}",
                    ].join("\n")}
                    onChange={(event) =>
                      updateSelectedNodeRuntimeConfig((current) => ({
                        ...current,
                        codeHookSpec: event.target.value.trim().length > 0
                          ? {
                              language: "python",
                              entrypoint: "process",
                              source: event.target.value,
                            }
                          : undefined,
                      }))
                    }
                  />
                </label>
              ) : (
                <p className="empty-state">{t("nodeNoHook")}</p>
              )}
              {selectedNodeId ? (
                <button
                  className="button-secondary"
                  style={{ marginTop: 12 }}
                  onClick={() => {
                    const next = removeNodeFromDraft(draft, selectedNodeId);
                    setDraft(next);
                    setSelectedNodeId(next.logicGraph.nodes[0]?.id ?? "");
                    setDirty(true);
                  }}
                >
                  {t("removeNode")}
                </button>
              ) : null}
            </>
          ) : (
            <p className="empty-state">{t("selectNode")}</p>
          )}
          <div className="template-save-section">
            <h3>{t("saveAsTemplate")}</h3>
            <div className="field-grid">
              <label>
                {t("templateName")}
                <input
                  value={templateName}
                  onChange={(event) => setTemplateName(event.target.value)}
                  placeholder={`${workflow?.name ?? "Workflow"} Template`}
                />
              </label>
              <label>
                {t("templateDescription")}
                <textarea
                  rows={4}
                  value={templateDescription}
                  onChange={(event) => setTemplateDescription(event.target.value)}
                  placeholder="Reusable project workflow for delivery normalization"
                />
              </label>
            </div>
            <div className="button-row" style={{ marginTop: 12 }}>
              <button
                className="button-primary"
                disabled={!workflow || templateName.trim().length === 0}
                onClick={async () => {
                  if (!workflow) {
                    return;
                  }
                  try {
                    setError(null);
                    const templatePayload = serializeWorkflowDraft(draft);
                    const template = await props.api.createWorkflowTemplate({
                      workspaceId: workflow.workspaceId,
                      projectId: workflow.projectId,
                      name: templateName.trim(),
                      description: templateDescription.trim() || undefined,
                      visualGraph: templatePayload.visualGraph,
                      logicGraph: templatePayload.logicGraph,
                      runtimeGraph: templatePayload.runtimeGraph,
                      pluginRefs: templatePayload.pluginRefs,
                      createdBy: workflow.createdBy ?? "local-user",
                    });
                    setSavedTemplateName(template.name);
                  } catch (createError) {
                    setError(
                      createError instanceof Error
                        ? createError.message
                        : t("failedCreateTemplate"),
                    );
                  }
                }}
              >
                {t("saveAsTemplate")}
              </button>
            </div>
            {savedTemplateName ? (
              <p className="empty-state">
                {t("templateSaved")}: {savedTemplateName}
              </p>
            ) : null}
          </div>
        </aside>
      </section>
    </div>
  );
}

function RunsIndexPage(props: {
  api: ApiClient;
  bootstrap: BootstrapContext;
}) {
  const { t } = useI18n();
  const [runs, setRuns] = useState<any[]>([]);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;
    let timer: ReturnType<typeof setTimeout> | undefined;

    const load = async () => {
      try {
        const nextRuns = await props.api.listRuns({
          projectId: props.bootstrap.project._id,
        });
        if (!cancelled) {
          setRuns(nextRuns);
          setError(null);
          timer = setTimeout(() => {
            void load();
          }, 1500);
        }
      } catch (loadError) {
        if (!cancelled) {
          setError(loadError instanceof Error ? loadError.message : t("failedLoadRuns"));
        }
      }
    };

    void load();
    return () => {
      cancelled = true;
      if (timer) {
        clearTimeout(timer);
      }
    };
  }, [props.api, props.bootstrap.project._id]);

  return (
    <div className="page-stack">
      <section className="panel">
        <h1>{t("runsTitle")}</h1>
        <p>{t("runsDescription")}</p>
        {error ? <p>{error}</p> : null}
      </section>
      <section className="panel">
        <div className="list-grid">
          {runs.length === 0 ? (
            <p className="empty-state">{t("noRunsYet")}</p>
          ) : (
            runs.map((run) => (
              <article key={run._id} className="asset-card">
                <a href={`/runs/${run._id}`}>
                  <strong>{run.workflowName ?? run.workflowDefinitionId}</strong>
                </a>
                <p>{t("status")}: {translateStatus(run.status, t)}</p>
                <p>{t("inputAssets")}: {(run.assetIds ?? []).join(", ") || t("none")}</p>
                <p>{t("createdAt")}: {run.createdAt}</p>
              </article>
            ))
          )}
        </div>
      </section>
    </div>
  );
}

function RunDetailPage(props: {
  api: ApiClient;
  runId: string;
}) {
  const { t } = useI18n();
  const [run, setRun] = useState<any | null>(null);
  const [tasks, setTasks] = useState<any[]>([]);
  const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
  const [artifacts, setArtifacts] = useState<any[]>([]);
  const [error, setError] = useState<string | null>(null);
  const [refreshKey, setRefreshKey] = useState(0);

  useEffect(() => {
    let cancelled = false;
    let timer: ReturnType<typeof setTimeout> | undefined;

    const load = async () => {
      try {
        const [runData, taskData] = await Promise.all([
          props.api.getRun(props.runId),
          props.api.listRunTasks(props.runId),
        ]);
        if (cancelled) {
          return;
        }
        setRun(runData);
        setTasks(taskData);
        setError(null);
        if (runData.status === "queued" || runData.status === "running") {
          timer = setTimeout(() => {
            void load();
          }, 1000);
        }
      } catch (loadError) {
        if (!cancelled) {
          setError(loadError instanceof Error ? loadError.message : t("failedLoadRunDetail"));
        }
      }
    };

    void load();
    return () => {
      cancelled = true;
      if (timer) {
        clearTimeout(timer);
      }
    };
  }, [props.api, props.runId, refreshKey]);

  useEffect(() => {
    if (tasks.length === 0) {
      setSelectedTaskId(null);
      return;
    }
    if (selectedTaskId && tasks.some((task) => task._id === selectedTaskId)) {
      return;
    }
    setSelectedTaskId(tasks[0]?._id ?? null);
  }, [tasks, selectedTaskId]);

  const selectedTask = useMemo(
    () => tasks.find((task) => task._id === selectedTaskId) ?? tasks[0] ?? null,
    [tasks, selectedTaskId],
  );

  useEffect(() => {
    if (!selectedTask?._id) {
      setArtifacts([]);
      return;
    }
    void (async () => {
      try {
        setArtifacts(await props.api.listArtifactsByProducer("run_task", selectedTask._id));
      } catch (loadError) {
        setError(loadError instanceof Error ? loadError.message : t("failedLoadTaskArtifacts"));
      }
    })();
  }, [props.api, selectedTask?._id, (selectedTask?.outputArtifactIds ?? []).join(","), t]);

  if (error) {
    return <section className="panel">{error}</section>;
  }
  if (!run) {
    return <section className="panel">{t("loadingRun")}</section>;
  }

  return (
    <div className="page-stack">
      <section className="panel">
        <h1>{t("runDetail")}</h1>
        <p>Run ID: {run._id}</p>
        <p>{t("workflow")}: {run.workflowName ?? run.workflowDefinitionId}</p>
        <p>{t("status")}: {translateStatus(run.status, t)}</p>
        <p>
          {t("inputAssets")}:{" "}
          {(run.assetIds ?? []).length > 0
            ? run.assetIds.map((assetId: string) => assetId).join(", ")
            : t("none")}
        </p>
        <p>{t("startedAt")}: {run.startedAt ?? t("notAvailable")}</p>
        <p>{t("finishedAt")}: {run.finishedAt ?? t("notAvailable")}</p>
        <p>{t("runDuration")}: {typeof run.durationMs === "number" ? `${run.durationMs} ms` : t("notAvailable")}</p>
        <p>{t("runSummary")}: {formatRunSummary(run, t)}</p>
        <div className="button-row" style={{ marginTop: 12 }}>
          {run.status === "queued" || run.status === "running" ? (
            <button
              className="button-secondary"
              onClick={async () => {
                try {
                  setError(null);
                  await props.api.cancelRun(run._id);
                  setRefreshKey((value) => value + 1);
                } catch (actionError) {
                  setError(actionError instanceof Error ? actionError.message : t("failedCancelRun"));
                }
              }}
            >
              {t("cancelRun")}
            </button>
          ) : null}
          {run.status !== "queued" && run.status !== "running" ? (
            <button
              className="button-primary"
              onClick={async () => {
                try {
                  setError(null);
                  const retriedRun = await props.api.retryRun(run._id);
                  window.history.pushState({}, "", `/runs/${retriedRun._id}`);
                  window.dispatchEvent(new PopStateEvent("popstate"));
                } catch (actionError) {
                  setError(actionError instanceof Error ? actionError.message : t("failedRetryRun"));
                }
              }}
            >
              {t("retryRun")}
            </button>
          ) : null}
        </div>
      </section>
      <section className="two-column">
        <div className="panel">
          <h2>{t("runGraph")}</h2>
          <div className="list-grid">
            {tasks.map((task) => (
              <article
                key={task._id}
                className="task-card"
                data-selected={String(selectedTask?._id === task._id)}
                onClick={() => setSelectedTaskId(task._id)}
                style={{ cursor: "pointer" }}
              >
                <div className="toolbar">
                  <strong>{task.nodeId}</strong>
                  <span className="status-pill" data-status={task.status}>
                    {translateStatus(task.status, t)}
                  </span>
                </div>
                <p>{t("type")}: {task.nodeType}</p>
                <p>{t("boundAssets")}: {(task.assetIds ?? []).join(", ") || t("none")}</p>
              </article>
            ))}
          </div>
        </div>
        <aside className="panel">
          <h2>{t("selectedTask")}</h2>
          {selectedTask ? (
            <>
              <p>Node: {selectedTask.nodeId}</p>
              <p>{t("status")}: {translateStatus(selectedTask.status, t)}</p>
              <p>{t("definition")}: {selectedTask.nodeDefinitionId ?? t("notAvailable")}</p>
              <p>{t("executor")}: {selectedTask.executorType}</p>
              <p>{t("executorConfig")}: {formatExecutorConfigLabel(selectedTask.executorConfig)}</p>
              <p>
                {t("codeHook")}:{" "}
                {selectedTask.codeHookSpec
                  ? `${selectedTask.codeHookSpec.language}:${selectedTask.codeHookSpec.entrypoint ?? "process"}`
                  : t("none")}
              </p>
              <p>{t("inputAssets")}: {(selectedTask.assetIds ?? []).join(", ") || t("none")}</p>
              <p>{t("startedAt")}: {selectedTask.startedAt ?? t("notAvailable")}</p>
              <p>{t("finishedAt")}: {selectedTask.finishedAt ?? t("notAvailable")}</p>
              <p>
                {t("duration")}:{" "}
                {typeof selectedTask.durationMs === "number"
                  ? `${selectedTask.durationMs} ms`
                  : t("notAvailable")}
              </p>
              <p>{t("summary")}: {formatTaskSummary(selectedTask, t)}</p>
              {selectedTask.errorMessage ? <p>{t("error")}: {selectedTask.errorMessage}</p> : null}
              {selectedTask.status === "failed" || selectedTask.status === "cancelled" ? (
                <div className="button-row" style={{ marginTop: 12 }}>
                  <button
                    className="button-primary"
                    onClick={async () => {
                      try {
                        setError(null);
                        await props.api.retryRunTask(run._id, selectedTask._id);
                        setRefreshKey((value) => value + 1);
                      } catch (actionError) {
                        setError(
                          actionError instanceof Error
                            ? actionError.message
                            : t("failedRetryTask"),
                        );
                      }
                    }}
                  >
                    {t("retryTask")}
                  </button>
                </div>
              ) : null}
              <p>{t("artifacts")}: {artifacts.length}</p>
              {artifacts.length > 0 ? (
                <ul>
                  {artifacts.map((artifact) => (
                    <li key={artifact._id}>
                      <a href={`/explore/${artifact._id}`}>{artifact.title ?? artifact._id}</a>
                    </li>
                  ))}
                </ul>
              ) : (
                <p className="empty-state">{t("noTaskArtifactsYet")}</p>
              )}
              <div className="page-stack">
                <div>
                  <strong>{t("stdout")}</strong>
                  {(selectedTask.stdoutLines ?? []).length > 0 ? (
                    <ul>
                      {(selectedTask.stdoutLines ?? []).map((line: string, index: number) => (
                        <li key={`${selectedTask._id}-stdout-${index}`}>{line}</li>
                      ))}
                    </ul>
                  ) : (
                    <p className="empty-state">{t("noStdout")}</p>
                  )}
                </div>
                <div>
                  <strong>{t("stderr")}</strong>
                  {(selectedTask.stderrLines ?? []).length > 0 ? (
                    <ul>
                      {(selectedTask.stderrLines ?? []).map((line: string, index: number) => (
                        <li key={`${selectedTask._id}-stderr-${index}`}>{line}</li>
                      ))}
                    </ul>
                  ) : (
                    <p className="empty-state">{t("noStderr")}</p>
                  )}
                </div>
                <div>
                  <strong>{t("executionLog")}</strong>
                  {(selectedTask.logLines ?? []).length > 0 ? (
                    <ul>
                      {(selectedTask.logLines ?? []).map((line: string, index: number) => (
                        <li key={`${selectedTask._id}-log-${index}`}>{line}</li>
                      ))}
                    </ul>
                  ) : (
                    <p className="empty-state">{t("noTaskLogs")}</p>
                  )}
                </div>
                {selectedTask.lastResultPreview ? (
                  <div>
                    <strong>{t("resultPreview")}</strong>
                    <pre className="mono-block">
                      {JSON.stringify(selectedTask.lastResultPreview, null, 2)}
                    </pre>
                  </div>
                ) : null}
              </div>
              <pre className="mono-block">{JSON.stringify(selectedTask, null, 2)}</pre>
            </>
          ) : (
            <p className="empty-state">{t("noTasksCreated")}</p>
          )}
        </aside>
      </section>
    </div>
  );
}

function ExplorePage(props: {
  api: ApiClient;
  artifactId: string | null;
}) {
  const { t } = useI18n();
  const [artifact, setArtifact] = useState<any | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (!props.artifactId) {
      return;
    }
    void (async () => {
      try {
        setArtifact(await props.api.getArtifact(props.artifactId!));
      } catch (loadError) {
        setError(loadError instanceof Error ? loadError.message : t("failedLoadArtifact"));
      }
    })();
  }, [props.artifactId, props.api, t]);

  if (!props.artifactId) {
    return (
      <section className="panel">
        <h1>{t("exploreTitle")}</h1>
        <p className="empty-state">{t("exploreEmpty")}</p>
      </section>
    );
  }

  if (error) {
    return <section className="panel">{error}</section>;
  }
  if (!artifact) {
    return <section className="panel">{t("loadingArtifact")}</section>;
  }

  return (
    <div className="page-stack">
      <section className="panel">
        <h1>{artifact.title}</h1>
        <p>Artifact ID: {artifact._id}</p>
        <p>{t("type")}: {artifact.type}</p>
        <pre className="mono-block">{JSON.stringify(artifact.payload, null, 2)}</pre>
      </section>
    </div>
  );
}

export function App(props: AppProps) {
  const { t } = useI18n();
  const api = useMemo(() => new ApiClient(props.apiBaseUrl), [props.apiBaseUrl]);
  const pathname = usePathname();
  const [bootstrap, setBootstrap] = useState<BootstrapContext | null>(null);
  const [projects, setProjects] = useState<ProjectSummary[]>([]);
  const [activeProjectId, setActiveProjectId] = useState("");
  const [error, setError] = useState<string | null>(null);

  const syncProjects = useCallback(
    async (context: BootstrapContext, preferredProjectId?: string) => {
      const nextProjects = (await api.listProjects(context.workspace._id)) as ProjectSummary[];
      setProjects(nextProjects);

      const storageKey = getActiveProjectStorageKey(context.workspace._id);
      const storedProjectId =
        typeof window === "undefined" ? null : window.localStorage.getItem(storageKey);
      const resolvedProject =
        nextProjects.find((project) => project._id === preferredProjectId) ??
        nextProjects.find((project) => project._id === storedProjectId) ??
        nextProjects.find((project) => project._id === context.project._id) ??
        nextProjects[0];

      const resolvedProjectId = resolvedProject?._id ?? context.project._id;
      setActiveProjectId(resolvedProjectId);
      if (typeof window !== "undefined") {
        window.localStorage.setItem(storageKey, resolvedProjectId);
      }
    },
    [api],
  );

  useEffect(() => {
    void (async () => {
      try {
        const context = await api.bootstrapDev();
        setBootstrap(context);
        await syncProjects(context, context.project._id);
      } catch (bootstrapError) {
        setError(
          bootstrapError instanceof Error ? bootstrapError.message : t("failedBootstrap"),
        );
      }
    })();
  }, [api, syncProjects, t]);

  if (error) {
    return <section className="panel">{error}</section>;
  }
  if (!bootstrap) {
    return <section className="panel">{t("bootstrappingLocalWorkspace")}</section>;
  }

  const activeProject =
    projects.find((project) => project._id === activeProjectId) ?? {
      _id: bootstrap.project._id,
      name: bootstrap.project.name,
    };
  const activeBootstrap: BootstrapContext = {
    ...bootstrap,
    project: {
      _id: activeProject._id,
      name: activeProject.name,
    },
  };

  const handleProjectSelected = (projectId: string, nextPath?: string) => {
    setActiveProjectId(projectId);
    if (typeof window !== "undefined") {
      window.localStorage.setItem(
        getActiveProjectStorageKey(activeBootstrap.workspace._id),
        projectId,
      );
    }
    if (nextPath) {
      navigateTo(nextPath);
      return;
    }
    navigateTo(normalizePathnameForProjectSwitch(pathname));
  };

  const handleProjectCreated = async (project: ProjectSummary) => {
    if (!bootstrap) {
      return;
    }
    await syncProjects(bootstrap, project._id);
    navigateTo("/workflows");
  };

  const assetMatch = pathname.match(/^\/assets\/([^/]+)$/);
  const workflowMatch = pathname.match(/^\/workflows\/([^/]+)$/);
  const runMatch = pathname.match(/^\/runs\/([^/]+)$/);
  const exploreMatch = pathname.match(/^\/explore\/([^/]+)$/);

  let active: NavItem = "Projects";
  let content: React.ReactNode = (
    <ProjectsPage
      api={api}
      bootstrap={activeBootstrap}
      projects={projects}
      activeProjectId={activeProject._id}
      onProjectCreated={handleProjectCreated}
      onProjectSelected={handleProjectSelected}
    />
  );

  if (pathname === "/workflows") {
    active = "Workflows";
    content = <WorkflowsPage api={api} bootstrap={activeBootstrap} />;
  } else if (pathname === "/nodes") {
    active = "Nodes";
    content = <NodesPage api={api} bootstrap={activeBootstrap} />;
  } else if (workflowMatch) {
    active = "Workflows";
    content = <WorkflowEditorPage api={api} workflowId={workflowMatch[1]} />;
  } else if (pathname === "/runs") {
    active = "Runs";
    content = <RunsIndexPage api={api} bootstrap={activeBootstrap} />;
  } else if (runMatch) {
    active = "Runs";
    content = <RunDetailPage api={api} runId={runMatch[1]} />;
  } else if (exploreMatch) {
    active = "Explore";
    content = <ExplorePage api={api} artifactId={exploreMatch[1]} />;
  } else if (pathname === "/explore") {
    active = "Explore";
    content = <ExplorePage api={api} artifactId={null} />;
  } else if (assetMatch) {
    active = "Assets";
    content = <AssetDetailPage api={api} assetId={assetMatch[1]} />;
  } else if (pathname === "/assets") {
    active = "Assets";
    content = <AssetsPage api={api} bootstrap={activeBootstrap} />;
  }

  return (
    <AppShell
      workspaceName={activeBootstrap.workspace.name}
      projectName={activeBootstrap.project.name}
      projectControl={
        <select
          className="app-header__select"
          value={activeProject._id}
          onChange={(event) => handleProjectSelected(event.target.value)}
        >
          {(projects.length > 0 ? projects : [activeProject]).map((project) => (
            <option key={project._id} value={project._id}>
              {project.name}
            </option>
          ))}
        </select>
      }
      active={active}
    >
      {content}
    </AppShell>
  );
}
