/* global React, Icon, Button, Select, useApiResource, useAuth, useToast, apiFetch, LoadingSpinner, EmptyState, useEscapeToClose */

// ============================================================
// Product MCPs — customer-facing endpoints surface.
//   list → cards (mcp.<org_slug>.armature.tech/<slug>)
//   detail → header, setup readiness, runtime health, API docs,
//            API Connection, MCP access keys
//   create → 4-step modal
//   settings / API Connection → modals
// Wires to /api/product-mcps. Visual language ports the Product
// MCP design; all classes namespaced `pmcp-`.
// ============================================================

const { useState: usePmcpState, useCallback: usePmcpCallback } = React;

const PMCP_STATUS_META = {
  live: { cls: 'live', label: 'Live' },
  needs_setup: { cls: 'needs_setup', label: 'Needs setup' },
  paused: { cls: 'paused', label: 'Paused' },
  error: { cls: 'error', label: 'Error' },
};

const PMCP_AUTH_METHODS = [
  { id: 'bearer', title: 'Bearer token', sub: 'Sent as Authorization: Bearer <token>.' },
  { id: 'token', title: 'Token scheme', sub: 'Sent as Authorization: Token <token>.' },
  { id: 'api_key_header', title: 'API key header', sub: 'Sent in a custom request header.' },
  { id: 'api_key_query', title: 'API key query param', sub: 'Appended to the upstream request URL.' },
];

const PMCP_GITHUB_DEFAULT_REF = 'refs/heads/main';
const PMCP_GITHUB_OPENAPI_MODE = 'path';

function pmcpFmtNum(n) {
  if (n === null || n === undefined) return '—';
  return Number(n).toLocaleString('en-US');
}

function pmcpFmtPct(n) {
  if (n === null || n === undefined) return '—';
  return `${n}%`;
}

function pmcpFmtMs(n) {
  if (n === null || n === undefined) return '—';
  return `${n}ms`;
}

function pmcpRelativeTime(iso) {
  if (!iso) return '—';
  const then = new Date(iso).getTime();
  if (!Number.isFinite(then)) return '—';
  const diff = Date.now() - then;
  if (diff < 60_000) return 'just now';
  const mins = Math.floor(diff / 60_000);
  if (mins < 60) return `${mins}m ago`;
  const hours = Math.floor(mins / 60);
  if (hours < 24) return `${hours}h ago`;
  const days = Math.floor(hours / 24);
  return `${days}d ago`;
}

function readPmcpOpenApiFile(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(String(reader.result || ''));
    reader.onerror = () => reject(new Error('Could not read OpenAPI file'));
    reader.readAsText(file);
  });
}

const PMCP_CREATE_DRAFT_KEY = 'armature.productMcp.createDraft.v1';

function readPmcpCreateDraft() {
  try {
    return JSON.parse(window.localStorage?.getItem(PMCP_CREATE_DRAFT_KEY) || '{}') || {};
  } catch (_error) {
    return {};
  }
}

function writePmcpCreateDraft(draft) {
  try {
    window.localStorage?.setItem(PMCP_CREATE_DRAFT_KEY, JSON.stringify(draft || {}));
  } catch (_error) {
    // localStorage can be unavailable in private contexts.
  }
}

function clearPmcpCreateDraft() {
  try {
    window.localStorage?.removeItem(PMCP_CREATE_DRAFT_KEY);
  } catch (_error) {
    // noop
  }
}

async function startPmcpGithubInstall(params = {}) {
  const query = new URLSearchParams({ format: 'json' });
  for (const [key, value] of Object.entries(params)) {
    if (value !== undefined && value !== null && value !== '') query.set(key, String(value));
  }
  const result = await apiFetch(`/api/github/app/install?${query.toString()}`);
  if (!result?.url) throw new Error('GitHub install URL was not returned.');
  window.location.assign(result.url);
}

// ---- Shared presentational helpers ----------------------------------------

function PmcpStatusPill({ status, size = 'md' }) {
  const m = PMCP_STATUS_META[status] || PMCP_STATUS_META.needs_setup;
  return (
    <span className={`pmcp-spill ${m.cls}`} style={size === 'lg' ? { fontSize: 11.5, padding: '3px 10px' } : undefined}>
      <span className="dot" />{m.label}
    </span>
  );
}

function PmcpCopyButton({ value, label = 'Copy', variant = 'icon', onCopied }) {
  const [copied, setCopied] = usePmcpState(false);
  const copy = (e) => {
    if (e) e.stopPropagation();
    try { navigator.clipboard?.writeText(value); } catch { /* noop */ }
    setCopied(true);
    if (onCopied) onCopied();
    setTimeout(() => setCopied(false), 1300);
  };
  if (variant === 'btn') {
    return (
      <Button size="sm" onClick={copy}>
        <Icon name={copied ? 'check' : 'copy'} size={12} />
        {copied ? 'Copied' : label}
      </Button>
    );
  }
  return (
    <button type="button" className="pmcp-copy-btn" onClick={copy} aria-label={label} title={label}>
      <Icon name={copied ? 'check' : 'copy'} size={12} />
    </button>
  );
}

function PmcpUrlChip({ url, showByod = false, onCopied }) {
  return (
    <div className="pmcp-url-chip" style={{ flex: 1, minWidth: 0 }}>
      <Icon name="globe" size={13} style={{ color: 'var(--text-3)', flexShrink: 0 }} />
      <span className="u">{(url || '').replace('https://', '')}</span>
      <PmcpCopyButton value={url} label="Copy URL" onCopied={onCopied} />
      {showByod && <span className="pmcp-soon-pill">BYOD soon</span>}
    </div>
  );
}

function PmcpGithubMark() {
  return (
    <span className="pmcp-github-mark" aria-hidden="true">
      <Icon name="github" size={16} />
    </span>
  );
}

function PmcpDisabledAction({ reason, children }) {
  if (!reason) return children;
  return (
    <span
      className="ui-tooltip disabled-create-action pmcp-disabled-action"
      data-tooltip={reason}
      tabIndex={0}
      aria-label={reason}
      title={reason}>
      {children}
    </span>
  );
}

function pmcpCleanOpenApiPath(value) {
  return String(value || '').trim();
}

function pmcpCleanGithubForm(form) {
  return {
    ...form,
    repositoryFullName: String(form?.repositoryFullName || '').trim(),
    productionRef: String(form?.productionRef || '').trim(),
    openapiPath: pmcpCleanOpenApiPath(form?.openapiPath),
  };
}

// ---- List ------------------------------------------------------------------

function PmcpMark({ name }) {
  const initials = String(name || '')
    .replace(/API/gi, '').trim().split(/\s+/).filter(Boolean).map((w) => w[0]).slice(0, 2).join('') || '··';
  return <div className="pmcp-mark">{initials}</div>;
}

function PmcpCard({ row, onOpen }) {
  const toast = useToast();
  const connOk = row.authenticationStatus === 'configured';
  return (
    <div className="pmcp-card" onClick={() => onOpen(row.id)}>
      <div className="pmcp-card-hd">
        <PmcpMark name={row.name} />
        <div style={{ minWidth: 0 }}>
          <div className="pmcp-name">{row.name}</div>
          <div className="pmcp-slug">/{row.sourceSlug}</div>
        </div>
        <PmcpStatusPill status={row.status} />
      </div>
      <div className="pmcp-card-url">
        <PmcpUrlChip url={row.publicMcpUrl} onCopied={() => toast.show({ tone: 'ok', title: 'Public MCP URL copied' })} />
      </div>
      <div className="pmcp-metric-strip">
        <div className="pmcp-metric-cell">
          <div className="pmcp-metric-k">Last docs sync</div>
          <div className="pmcp-metric-v">{pmcpRelativeTime(row.lastDocsSyncAt)}</div>
        </div>
        <div className="pmcp-metric-cell">
          <div className="pmcp-metric-k">Requests · 24h</div>
          <div className="pmcp-metric-v">{row.requests24h > 0 ? pmcpFmtNum(row.requests24h) : '—'}</div>
        </div>
        <div className="pmcp-metric-cell">
          <div className="pmcp-metric-k">API connection</div>
          <div className="pmcp-metric-v" style={{ color: connOk ? 'var(--green)' : 'var(--amber)', display: 'flex', alignItems: 'center', gap: 5 }}>
            <Icon name={connOk ? 'check' : 'alert'} size={12} />
            {connOk ? 'Configured' : 'Missing'}
          </div>
        </div>
      </div>
      <div className="pmcp-card-foot">
        <PmcpCopyButton value={row.publicMcpUrl} label="Copy URL" variant="btn"
          onCopied={() => toast.show({ tone: 'ok', title: 'Public MCP URL copied' })} />
        <div className="pmcp-spacer" />
        <Button size="sm" variant="primary" onClick={(e) => { e.stopPropagation(); onOpen(row.id); }}>
          <Icon name="externalLink" size={13} />Open
        </Button>
      </div>
    </div>
  );
}

function ProductMcpListView({ rows, loading, error, onOpen, onCreate }) {
  if (loading) {
    return (
      <div className="code inline-loading" style={{ margin: '40px auto' }}>
        <LoadingSpinner size="sm" label="Loading Product MCPs" decorative /> Loading Product MCPs…
      </div>
    );
  }
  if (error) {
    return <EmptyState icon="alert" title="Couldn't load Product MCPs" body={error.message || 'Try again in a moment.'} />;
  }
  if (!rows || rows.length === 0) {
    return (
      <div className="pmcp-empty">
        <div style={{ width: 44, height: 44, border: '1px solid var(--border-strong)', display: 'grid', placeItems: 'center', margin: '0 auto 16px', color: 'var(--text-3)' }}>
          <Icon name="globe" size={20} />
        </div>
        <div style={{ fontFamily: 'var(--font-display)', fontSize: 21, fontWeight: 500, letterSpacing: '-0.02em', marginBottom: 8 }}>
          No Product MCPs yet
        </div>
        <div style={{ fontSize: 13.5, color: 'var(--text-2)', maxWidth: 440, margin: '0 auto 20px', lineHeight: 1.55 }}>
          Turn your product API into an MCP endpoint your customers can use. Upload your API docs,
          connect your upstream API, and we generate the capabilities.
        </div>
        <Button variant="primary" onClick={onCreate}><Icon name="plus" size={13} />New Product MCP</Button>
      </div>
    );
  }
  return (
    <div className="pmcp-grid">
      {rows.map((row) => <PmcpCard key={row.id} row={row} onOpen={onOpen} />)}
    </div>
  );
}

// ---- Detail: readiness -----------------------------------------------------

function PmcpReadyIcon({ status }) {
  const ico = status === 'ready' ? 'check' : (status === 'pending' ? 'circle' : 'alert');
  return <span className={`pmcp-ready-ico ${status}`}><Icon name={ico} size={16} /></span>;
}

function pmcpGithubRunIsActive(run) {
  return Boolean(run && (run.status === 'queued' || run.status === 'running'));
}

function pmcpGithubRunStatusChangedAt(run) {
  return run?.updatedAt || run?.startedAt || run?.createdAt || null;
}

function pmcpGithubRunAgeMs(run) {
  const statusChangedAt = new Date(pmcpGithubRunStatusChangedAt(run) || '').getTime();
  if (!Number.isFinite(statusChangedAt)) return 0;
  return Math.max(0, Date.now() - statusChangedAt);
}

function pmcpGithubRunQueuedTooLong(run) {
  return Boolean(run?.status === 'queued' && pmcpGithubRunAgeMs(run) > 2 * 60_000);
}

function pmcpGithubRunElapsedLabel(run) {
  const label = pmcpRelativeTime(pmcpGithubRunStatusChangedAt(run));
  return label.endsWith(' ago') ? label.slice(0, -4) : label;
}

function pmcpGithubRunTargetLabel(run) {
  return [
    run?.repositoryFullName,
    run?.ref,
    run?.openapiPath,
  ].filter(Boolean).join(' · ');
}

function pmcpGithubRunPhaseLabel(run) {
  const phase = String(run?.phase || '').trim();
  if (!run) return 'No docs sync has run yet';
  if (pmcpGithubRunQueuedTooLong(run)) return 'The docs worker has not started yet';
  if (run.status === 'queued' && !phase) return 'Waiting for the docs worker to start';
  if (run.status === 'failed') return run.errorMessage || 'Docs sync failed';
  if (phase === 'discover_openapi') return 'Finding the OpenAPI file';
  if (phase === 'generate_bundle') return 'Generating MCP capabilities';
  if (phase === 'publish_github_report') return 'Publishing GitHub status';
  if (phase === 'promote_bundle') return 'Promoting generated docs';
  if (run.status === 'succeeded') return 'Docs sync succeeded';
  return phase ? `Processing ${phase.replace(/_/g, ' ')}` : run.status;
}

function pmcpGithubRunStatusLabel(run) {
  const phase = String(run?.phase || '').trim();
  if (!run) return 'Not run';
  if (pmcpGithubRunQueuedTooLong(run)) return 'Worker not started';
  if (run.status === 'queued') return 'Checking';
  if (run.status === 'running') {
    if (phase === 'discover_openapi') return 'Finding docs';
    if (phase === 'generate_bundle') return 'Generating';
    if (phase === 'publish_github_report') return 'Publishing';
    if (phase === 'promote_bundle') return 'Promoting';
    return 'Running';
  }
  if (run.status === 'succeeded') return 'Succeeded';
  if (run.status === 'failed') return 'Failed';
  if (run.status === 'superseded') return 'Superseded';
  if (run.status === 'canceled') return 'Canceled';
  return String(run.status || 'Unknown');
}

function pmcpGithubRunStatusTone(run) {
  if (!run) return 'pending';
  if (pmcpGithubRunQueuedTooLong(run)) return 'warn';
  if (run.status === 'succeeded') return 'ok';
  if (run.status === 'failed') return 'danger';
  if (run.status === 'superseded' || run.status === 'canceled') return 'warn';
  if (pmcpGithubRunIsActive(run)) return 'info';
  return 'pending';
}

function pmcpGithubRunSupportText(run) {
  if (!run) return '';
  const sha = run.sha?.slice(0, 7) || 'commit';
  const target = pmcpGithubRunTargetLabel(run);
  const prefix = target ? `${sha} · ${target}` : sha;
  if (pmcpGithubRunQueuedTooLong(run)) {
    return `${prefix} · Queued for ${pmcpGithubRunElapsedLabel(run)}. Worker has not started. This page is checking every 5 seconds.`;
  }
  if (pmcpGithubRunIsActive(run)) {
    return `${prefix} · ${pmcpGithubRunPhaseLabel(run)}. This page is checking every 5 seconds.`;
  }
  return `${prefix} · ${pmcpGithubRunPhaseLabel(run)} · ${pmcpRelativeTime(run.createdAt)}`;
}

function PmcpGithubRunStatusPill({ run }) {
  const loading = pmcpGithubRunIsActive(run) && !pmcpGithubRunQueuedTooLong(run);
  const tone = pmcpGithubRunStatusTone(run);
  return (
    <span className={`pmcp-run-pill ${tone}`} aria-live={loading ? 'polite' : undefined}>
      {loading ? <span className="pmcp-run-spinner" aria-hidden="true" /> : <span className="pmcp-run-pill-dot" />}
      <span>{pmcpGithubRunStatusLabel(run)}</span>
    </span>
  );
}

function pmcpGithubInitialSyncOk(status) {
  return !status || status === 'queued' || status === 'already_running' || status === 'already_succeeded';
}

function pmcpGithubInitialSyncOkTitle(status) {
  if (status === 'already_running') return 'GitHub connected · docs sync already running';
  if (status === 'already_succeeded') return 'GitHub connected · docs already current';
  return 'GitHub connected · initial docs sync queued';
}

function PmcpSetupProgressPanel({ detail, githubResource, onDetailReload }) {
  const github = githubResource.data || {};
  const githubReloadRef = React.useRef(githubResource.reload);
  const detailReloadRef = React.useRef(onDetailReload);
  const productionRun = github.lastProductionDeploy || null;
  const run = productionRun;
  const docsSyncActive = pmcpGithubRunIsActive(run);
  const endpointLive = detail.isActive === true || detail.status === 'live';
  const blockers = (detail.setupReadiness || []).filter((row) => row.status !== 'ready');
  const domainBlocker = blockers.find((row) => row.key === 'armatureOwnedDomainReady');
  const docsBlocker = blockers.find((row) => row.key === 'apiDocsUploaded' || row.key === 'capabilitiesGenerated');
  React.useEffect(() => {
    githubReloadRef.current = githubResource.reload;
  }, [githubResource.reload]);
  React.useEffect(() => {
    detailReloadRef.current = onDetailReload;
  }, [onDetailReload]);
  React.useEffect(() => {
    if (!docsSyncActive) return undefined;
    const timer = setInterval(() => {
      githubReloadRef.current();
      if (typeof detailReloadRef.current === 'function') detailReloadRef.current();
    }, 5000);
    return () => clearInterval(timer);
  }, [docsSyncActive]);
  if (!github.connected && blockers.length === 0) return null;
  const workerStalled = pmcpGithubRunQueuedTooLong(run);
  const nonEndpointBlockers = blockers.filter((row) => row.key !== 'publicEndpointLive');
  const activationUnavailable = detail.actions?.canActivate === false && !endpointLive;
  const setupPending = nonEndpointBlockers.length > 0 || activationUnavailable;
  const setupReadyOrLive = !workerStalled && !docsSyncActive && run?.status !== 'failed' && !setupPending;
  const pendingCopy = nonEndpointBlockers.length > 0
    ? `Complete ${nonEndpointBlockers.map((row) => row.label).join(', ')} before activation.`
    : 'Activation is not available yet. Review setup readiness below.';
  const runTarget = [
    run?.repositoryFullName,
    run?.ref,
    run?.openapiPath,
  ].filter(Boolean).join(' · ');
  const title = workerStalled
    ? 'GitHub docs sync has not started'
    : docsSyncActive
      ? 'GitHub docs sync in progress'
      : run?.status === 'failed'
        ? 'GitHub docs sync failed'
        : docsBlocker
          ? 'Waiting for generated API docs'
          : domainBlocker
            ? 'Domain setup needs Armature support'
            : setupPending
              ? 'Setup checks still pending'
              : endpointLive
                ? 'Product MCP is live'
                : 'Setup is ready for activation';
  const tone = workerStalled || run?.status === 'failed' || domainBlocker ? 'warn' : ((docsSyncActive || setupPending) ? 'info' : 'ok');
  const icon = workerStalled || run?.status === 'failed' || domainBlocker ? 'alert' : ((docsSyncActive || setupPending) ? 'refresh' : 'check');
  const runCopy = setupReadyOrLive
    ? endpointLive
      ? `Public endpoint is live and serving${runTarget ? ` · ${runTarget}` : ''}`
      : `Click Activate to start serving from the MCP URL${runTarget ? ` · ${runTarget}` : ''}`
    : null;
  return (
    <div className={`pmcp-callout ${tone}`} style={{ marginBottom: 18 }}>
      <Icon name={icon} size={14} style={{ flexShrink: 0, marginTop: 1 }} />
      <div style={{ minWidth: 0 }}>
        <div className="pmcp-callout-title-row">
          <b>{title}</b>
          {run && <PmcpGithubRunStatusPill run={run} />}
        </div>
        {run ? (
          <div style={{ marginTop: 4 }}>
            {runCopy || (
              <>
                {workerStalled
                  ? `Queued for ${pmcpGithubRunElapsedLabel(run)}. Worker has not started. This page is checking every 5 seconds.`
                  : pmcpGithubRunPhaseLabel(run)}
                {run.repositoryFullName ? ` · ${run.repositoryFullName}` : ''}
                {run.ref ? ` · ${run.ref}` : ''}
                {run.openapiPath ? ` · ${run.openapiPath}` : ''}
              </>
            )}
          </div>
        ) : (
          <div style={{ marginTop: 4 }}>
            {setupReadyOrLive
              ? endpointLive
                ? 'Public endpoint is live and serving.'
                : 'Click Activate to start serving from the MCP URL.'
              : setupPending
                ? pendingCopy
                : 'Connect GitHub or upload OpenAPI docs to generate capabilities.'}
          </div>
        )}
        {domainBlocker && (
          <div style={{ marginTop: 4 }}>Armature-owned domain provisioning failed. Customers cannot fix this; Armature must correct the domain setup before this MCP can go live.</div>
        )}
      </div>
    </div>
  );
}

const PMCP_ACTION_ICON = {
  upload_docs: 'upload',
  configure_authentication: 'lock',
  activate: 'play',
};
const PMCP_ACTION_LABEL = {
  upload_docs: 'Upload docs',
  configure_authentication: 'Configure auth',
  activate: 'Activate',
};

function pmcpActivationBlocker(detail) {
  const blockers = (detail.setupReadiness || []).filter((row) => row.status !== 'ready' && row.key !== 'publicEndpointLive');
  if (blockers.length === 0) return '';
  return `Activate is blocked until ${blockers.map((row) => row.label).join(', ')}.`;
}

function PmcpActivationPanel({ detail, busy, onActivate }) {
  const endpointLive = detail.isActive === true || detail.status === 'live';
  const readyToActivate = detail.actions?.canActivate === true && !endpointLive;
  if (!readyToActivate && !endpointLive) return null;
  return (
    <div className={`pmcp-activation-panel ${endpointLive ? 'live' : 'ready'}`} role="status" aria-live="polite">
      <div className="pmcp-activation-icon">
        <Icon name={endpointLive ? 'check' : 'play'} size={16} />
      </div>
      <div className="pmcp-activation-copy">
        <div className="pmcp-activation-kicker">{endpointLive ? 'Active endpoint' : 'Final step'}</div>
        <div className="pmcp-activation-title">
          {endpointLive ? 'Product MCP is live and serving' : 'Ready to activate'}
        </div>
        <div className="pmcp-activation-sub">
          {endpointLive
            ? 'The public MCP URL is live. Docs, credentials, domain, and capabilities are ready.'
            : 'Setup checks are complete, but the public MCP URL is not serving yet. Activate it to go live.'}
        </div>
      </div>
      {endpointLive ? (
        <PmcpStatusPill status="live" size="lg" />
      ) : (
        <Button variant="primary" loading={busy} loadingLabel="Activating..." onClick={onActivate}>
          <Icon name="play" size={13} />Activate endpoint
        </Button>
      )}
    </div>
  );
}

function PmcpReadinessSection({ detail, onAction, githubConnected = false }) {
  const rows = detail.setupReadiness || [];
  const ready = rows.filter((r) => r.status === 'ready').length;
  const todo = rows.filter((r) => r.status !== 'ready');
  const endpointLive = detail.isActive === true || detail.status === 'live';
  const visibleRows = todo.length > 0 ? todo : [{
    key: 'all-ready',
    status: 'ready',
    label: endpointLive ? 'Live' : 'Ready to activate',
    detail: endpointLive
      ? 'All setup checks complete and the endpoint is serving'
      : 'All setup checks complete. Activate to start serving.',
  }];
  return (
    <div className="pmcp-sec">
      <div className="pmcp-sec-hd">
        <h2 className="pmcp-sec-title">Setup readiness</h2>
        <span className="pmcp-page-foot-note">{ready}/{rows.length} ready</span>
      </div>
      <div className="pmcp-summary-card">
        {visibleRows.map((r) => (
          <div className="pmcp-summary-row" key={r.key}>
            <PmcpReadyIcon status={r.status} />
            <div style={{ minWidth: 0 }}>
              <div className="pmcp-summary-label">{r.label}</div>
              <div className="pmcp-summary-sub">{r.detail}</div>
            </div>
            <div>
              {r.action
                ? (() => {
                    const activateDisabled = r.action === 'activate' && detail.actions?.canActivate === false;
                    const disabledReason = activateDisabled ? pmcpActivationBlocker(detail) : '';
                    const actionLabel = r.action === 'upload_docs' && githubConnected
                      ? 'Review GitHub sync'
                      : (PMCP_ACTION_LABEL[r.action] || 'Fix');
                    const iconName = r.action === 'upload_docs' && githubConnected
                      ? 'github'
                      : (PMCP_ACTION_ICON[r.action] || 'settings');
                    return (
                      <PmcpDisabledAction reason={disabledReason}>
                        <Button
                          size="sm"
                          variant="primary"
                          disabled={activateDisabled}
                          onClick={() => onAction(r.action)}>
                          <Icon name={iconName} size={13} />{actionLabel}
                        </Button>
                      </PmcpDisabledAction>
                    );
                  })()
                : <span className="pmcp-ready-state">{r.status === 'ready' ? (todo.length > 0 ? 'Ready' : endpointLive ? 'Live' : 'Ready') : r.status === 'warning' ? 'Warning' : 'Pending'}</span>}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ---- Detail: runtime health ------------------------------------------------

function PmcpCollapsibleSection({ title, meta, children, defaultOpen = false }) {
  return (
    <details className="pmcp-collapse" open={defaultOpen}>
      <summary>
        <span>{title}</span>
        {meta && <span className="pmcp-page-foot-note">{meta}</span>}
        <Icon name="chevronDown" size={14} />
      </summary>
      <div className="pmcp-collapse-body">{children}</div>
    </details>
  );
}

function PmcpRuntimeHealthSection({ detail }) {
  const h = detail.runtimeHealth || {};
  const tiles = [
    { k: 'Requests · 24h', v: h.requests24h > 0 ? pmcpFmtNum(h.requests24h) : '—', sub: h.requests24h > 0 ? 'across all callers' : 'no traffic yet' },
    { k: 'Success rate', v: pmcpFmtPct(h.successRate), sub: 'last 24h', tone: h.successRate != null ? 'var(--green)' : null },
    { k: 'P95 latency', v: pmcpFmtMs(h.p95LatencyMs), sub: 'upstream + runtime' },
  ];
  return (
    <PmcpCollapsibleSection title="Runtime" meta="last 24h · UTC">
      <div className="pmcp-health-grid">
        {tiles.map((t) => (
          <div className="pmcp-health-tile" key={t.k}>
            <div className="pmcp-health-k">{t.k}</div>
            <div className="pmcp-health-v" style={t.tone ? { color: t.tone } : undefined}>{t.v}</div>
            <div className="pmcp-health-sub">{t.sub}</div>
          </div>
        ))}
      </div>
    </PmcpCollapsibleSection>
  );
}

// ---- Detail: API docs ------------------------------------------------------

function PmcpApiDocsSection({ detail, githubState = null, githubLoading = false, onUpload, onCreateUploadKey }) {
  const d = detail.apiDocs || {};
  const githubConnected = githubState?.connected === true;
  const githubUnknown = githubLoading && !githubState;
  const integration = githubState?.integration || {};
  const productionRun = githubState?.lastProductionDeploy || null;
  const githubRun = pmcpGithubRunIsActive(productionRun)
    ? productionRun
    : productionRun?.status === 'failed'
      ? productionRun
      : productionRun || null;
  const githubRunNeedsAttention = Boolean(githubRun && (
    pmcpGithubRunIsActive(githubRun)
    || pmcpGithubRunQueuedTooLong(githubRun)
    || githubRun.status === 'failed'
  ));
  const pendingGithub = githubConnected && !d.lastDocsSyncAt && !githubRunNeedsAttention;
  const source = githubUnknown
    ? 'Checking docs source'
    : githubRunNeedsAttention
      ? `GitHub sync ${pmcpGithubRunStatusLabel(githubRun).toLowerCase()}`
      : pendingGithub
        ? 'GitHub sync pending'
        : d.docsSource === 'ci_sync'
          ? 'GitHub sync'
          : (d.docsSource === 'manual_upload' ? 'Manual upload' : 'No docs yet');
  const warnings = Number(d.warningsCount || 0);
  const githubSub = [
    integration.repositoryFullName,
    integration.productionRef,
    integration.openapiPath,
  ].filter(Boolean).join(' · ');
  const subcopy = githubUnknown
    ? 'Checking whether this Product MCP is backed by GitHub.'
    : githubRunNeedsAttention
      ? pmcpGithubRunSupportText(githubRun)
      : pendingGithub
        ? `Waiting for GitHub docs sync${githubSub ? ` · ${githubSub}` : ''}`
        : null;
  const hideManualActions = githubUnknown || githubConnected;
  return (
    <PmcpCollapsibleSection title="API docs" meta={`${d.capabilitiesCount ?? 0} capabilities`}>
      <div className="pmcp-summary-card">
        <div className="pmcp-summary-row">
          <PmcpReadyIcon status={githubRun?.status === 'failed' ? 'blocked' : (d.lastDocsSyncAt ? (warnings > 0 ? 'warning' : 'ready') : 'pending')} />
          <div style={{ minWidth: 0 }}>
            <div className="pmcp-summary-label">{source} · {d.capabilitiesCount ?? 0} capabilities</div>
            <div className={`pmcp-summary-sub ${githubRunNeedsAttention ? 'pmcp-run-sub' : ''}`}>
              {subcopy
                ? subcopy
                : (
                    <>
                      Synced {pmcpRelativeTime(d.lastDocsSyncAt)}
                      {warnings > 0 ? ` · ${warnings} warning${warnings === 1 ? '' : 's'}` : ''}
                      {d.repository ? ` · ${d.repository}${d.branch ? `@${d.branch}` : ''}` : ''}
                    </>
                  )}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', justifyContent: 'flex-end' }}>
            {hideManualActions ? (
              githubRun
                ? <PmcpGithubRunStatusPill run={githubRun} />
                : <span className="pmcp-ready-state">{githubUnknown ? 'Checking' : 'GitHub'}</span>
            ) : (
              <>
                <Button size="sm" onClick={onCreateUploadKey}><Icon name="key" size={13} />Create upload key</Button>
                <Button size="sm" onClick={onUpload}><Icon name="upload" size={13} />Upload OpenAPI</Button>
              </>
            )}
          </div>
        </div>
      </div>
    </PmcpCollapsibleSection>
  );
}

// ---- Detail: GitHub integration -------------------------------------------

function PmcpGithubSection({ detail, githubResource }) {
  const toast = useToast();
  const resource = githubResource;
  const state = resource.data || {};
  const integration = state.integration || {};
  const query = new URLSearchParams(window.location.search);
  const [form, setForm] = usePmcpState({
    githubInstallationId: query.get('githubInstallationId') || integration.githubInstallationId || '',
    githubInstallationGrant: query.get('githubInstallationGrant') || '',
    githubRepositoryId: integration.githubRepositoryId || '',
    repositoryFullName: integration.repositoryFullName || '',
    productionRef: integration.productionRef || PMCP_GITHUB_DEFAULT_REF,
    openapiMode: PMCP_GITHUB_OPENAPI_MODE,
    openapiPath: integration.openapiPath || '',
    prCommentsEnabled: integration.prCommentsEnabled !== false,
    confirmProductionDeploy: true,
  });
  const [busyAction, setBusyAction] = usePmcpState(null);
  const [installing, setInstalling] = usePmcpState(false);
  const connected = state.connected === true;
  const connectError = pmcpGithubConnectError(form);
  const busy = Boolean(busyAction);
  const canConnect = !busy && !installing && !connectError;
  const syncButtonLabel = state.lastProductionDeploy || state.lastPreview ? 'Rerun' : 'Sync now';

  const reload = resource.reload;
  const connect = async () => {
    if (connectError) {
      toast.show({ tone: 'bad', title: 'Check GitHub fields', description: connectError });
      return;
    }
    setBusyAction('connect');
    try {
      const result = await apiFetch(`/api/product-mcps/${detail.id}/github/connect`, {
        method: 'POST',
        body: JSON.stringify({ ...pmcpCleanGithubForm(form), openapiMode: PMCP_GITHUB_OPENAPI_MODE, confirmedProductionRef: String(form.productionRef || '').trim() }),
      });
      const initialSyncStatus = result?.initialSync?.status;
      if (!pmcpGithubInitialSyncOk(initialSyncStatus)) {
        toast.show({
          tone: 'warn',
          title: 'GitHub connected',
          description: result.initialSync.error || 'Initial docs sync could not start. Try Sync now from the GitHub section.',
        });
      } else {
        toast.show({ tone: 'ok', title: pmcpGithubInitialSyncOkTitle(initialSyncStatus) });
      }
      await reload();
    } catch (error) {
      toast.show({ tone: 'bad', title: 'GitHub connection failed', description: error.message });
    } finally {
      setBusyAction(null);
    }
  };
  const update = async (body, title) => {
    setBusyAction('update');
    try {
      await apiFetch(`/api/product-mcps/${detail.id}/github`, { method: 'PATCH', body: JSON.stringify(body) });
      toast.show({ tone: 'ok', title });
      await reload();
    } catch (error) {
      toast.show({ tone: 'bad', title: 'GitHub update failed', description: error.message });
    } finally {
      setBusyAction(null);
    }
  };
  const rerun = async () => {
    setBusyAction('rerun');
    try {
      const result = await apiFetch(`/api/product-mcps/${detail.id}/github/rerun`, { method: 'POST', body: JSON.stringify({}) });
      if (result?.status === 'already_running') {
        toast.show({ tone: 'ok', title: 'GitHub docs sync already running' });
      } else if (result?.status === 'already_succeeded') {
        toast.show({ tone: 'ok', title: 'GitHub docs already current' });
      } else {
        toast.show({ tone: 'ok', title: 'GitHub docs sync queued' });
      }
      await reload();
    } catch (error) {
      toast.show({ tone: 'bad', title: 'Rerun failed', description: error.message });
    } finally {
      setBusyAction(null);
    }
  };
  const disconnect = async () => {
    setBusyAction('disconnect');
    try {
      await apiFetch(`/api/product-mcps/${detail.id}/github`, { method: 'DELETE' });
      toast.show({ tone: 'ok', title: 'GitHub disconnected' });
      await reload();
    } catch (error) {
      toast.show({ tone: 'bad', title: 'Disconnect failed', description: error.message });
    } finally {
      setBusyAction(null);
    }
  };
  const install = async () => {
    setInstalling(true);
    try {
      await startPmcpGithubInstall({
        productMcpId: detail.id,
        returnTo: `/product-mcps?id=${detail.id}`,
      });
    } catch (error) {
      toast.show({ tone: 'bad', title: 'GitHub install failed', description: error.message });
    } finally {
      setInstalling(false);
    }
  };

  return (
    <div className="pmcp-sec" id="pmcp-github-section">
      <div className="pmcp-sec-hd">
        <h2 className="pmcp-sec-title">GitHub</h2>
        {connected && <span className="badge badge-success"><span className="dot" />Connected</span>}
      </div>
      {resource.loading && !resource.data ? (
        <div className="pmcp-summary-card">
          <div className="pmcp-summary-row">
            <LoadingSpinner size="sm" label="Loading GitHub integration" decorative />
            <div className="pmcp-summary-sub">Loading GitHub integration…</div>
          </div>
        </div>
      ) : connected ? (
        <div className="pmcp-summary-card">
          <div className="pmcp-summary-row">
            <PmcpReadyIcon status={integration.status === 'active' ? 'ready' : 'warning'} />
            <div style={{ minWidth: 0 }}>
              <div className="pmcp-summary-label">{integration.repositoryFullName}</div>
              <div className="pmcp-summary-sub">
                Production {integration.productionRef} · {integration.openapiPath || 'OpenAPI path missing'}
              </div>
            </div>
            <Button size="sm" onClick={rerun} disabled={busy} loading={busyAction === 'rerun'} loadingLabel="Queueing">
              <Icon name="refresh" size={13} />{syncButtonLabel}
            </Button>
          </div>
          <div className="pmcp-summary-row">
            <PmcpReadyIcon status={pmcpGithubRunIconStatus(state.lastPreview)} />
            <div style={{ minWidth: 0 }}>
              <div className="pmcp-summary-label pmcp-run-label">
                <span>Last PR preview</span>
                <PmcpGithubRunStatusPill run={state.lastPreview} />
              </div>
              <div className="pmcp-summary-sub pmcp-run-sub">{state.lastPreview ? pmcpGithubRunSupportText(state.lastPreview) : 'Pull request updates will appear here.'}</div>
            </div>
          </div>
          <div className="pmcp-summary-row">
            <PmcpReadyIcon status={pmcpGithubRunIconStatus(state.lastProductionDeploy)} />
            <div style={{ minWidth: 0 }}>
              <div className="pmcp-summary-label pmcp-run-label">
                <span>Last production deploy</span>
                <PmcpGithubRunStatusPill run={state.lastProductionDeploy} />
              </div>
              <div className="pmcp-summary-sub pmcp-run-sub">{state.lastProductionDeploy ? pmcpGithubRunSupportText(state.lastProductionDeploy) : 'Production pushes promote docs when they succeed.'}</div>
            </div>
            <div className="pmcp-summary-actions">
            <label className="pmcp-toggle-line">
              <input type="checkbox" checked={integration.prCommentsEnabled !== false}
                disabled={busy}
                onChange={(e) => update({ prCommentsEnabled: e.target.checked }, e.target.checked ? 'PR comments enabled' : 'PR comments disabled')} />
              PR comments
            </label>
            <Button size="sm" onClick={disconnect} disabled={busy} loading={busyAction === 'disconnect'} loadingLabel="Disconnecting"><Icon name="trash" size={13} />Disconnect</Button>
            </div>
          </div>
        </div>
      ) : (
        <div className="pmcp-card-plain" style={{ padding: 0 }}>
          <div className="pmcp-callout" style={{ border: 0, borderBottom: '1px solid var(--border)' }}>
            <Icon name="gitBranch" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
            <div>{state.setupText || 'Connect GitHub to preview MCP capability changes on pull requests and deploy docs automatically when production changes.'}</div>
          </div>
          <div className="pmcp-github-form">
            <button
              type="button"
              className={`pmcp-github-install-cta ${form.githubInstallationId ? 'is-installed' : ''}`}
              disabled={installing}
              onClick={install}>
              {installing ? <LoadingSpinner size="sm" label="Opening GitHub" /> : <PmcpGithubMark />}
              <span className="pmcp-github-install-copy">
                <span>{form.githubInstallationId ? 'GitHub App installed' : 'Install GitHub App'}</span>
                <span>{form.githubInstallationId ? 'Change repository access if needed.' : 'Required before Armature can read a repository.'}</span>
              </span>
              <span className="pmcp-github-install-state">{form.githubInstallationId ? 'Installed' : 'Required'}</span>
            </button>
            {form.githubInstallationId && (
              <div className="pmcp-callout ok">
                <Icon name="check" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
                <div>GitHub App installed. Enter the repository and docs settings to connect it.</div>
              </div>
            )}
            <div className="pmcp-form-grid">
              <label className="pmcp-field">
                <span className="pmcp-field-label">Repository</span>
                <input className="pmcp-field-input mono" required placeholder="owner/repo" value={form.repositoryFullName} onChange={(e) => setForm({ ...form, repositoryFullName: e.target.value })} />
              </label>
              <label className="pmcp-field">
                <span className="pmcp-field-label">Production ref</span>
                <input className="pmcp-field-input mono" required value={form.productionRef} onChange={(e) => setForm({ ...form, productionRef: e.target.value })} />
              </label>
            </div>
            <label className="pmcp-field">
              <span className="pmcp-field-label">OpenAPI path</span>
              <input className="pmcp-field-input mono" required value={form.openapiPath} onChange={(e) => setForm({ ...form, openapiPath: pmcpCleanOpenApiPath(e.target.value) })} placeholder=".armature/openapi.json" />
              <div className="pmcp-field-hint">Explicit file path to the committed bundled OpenAPI JSON/YAML file in this repository.</div>
            </label>
            <label className="pmcp-toggle-line">
              <input type="checkbox" checked={form.prCommentsEnabled} onChange={(e) => setForm({ ...form, prCommentsEnabled: e.target.checked })} />
              Update pull request comments
            </label>
            <label className="pmcp-toggle-line">
              <input type="checkbox" checked={form.confirmProductionDeploy} onChange={(e) => setForm({ ...form, confirmProductionDeploy: e.target.checked })} />
              Deploy Product MCP docs automatically when {form.productionRef || 'the production ref'} receives a push
            </label>
            {connectError && <div className="pmcp-field-hint">{connectError}</div>}
            <Button variant="primary" onClick={connect} disabled={!canConnect} loading={busyAction === 'connect'} loadingLabel="Connecting"><Icon name="link" size={13} />Connect repository</Button>
          </div>
        </div>
      )}
    </div>
  );
}

function pmcpGithubRunIconStatus(run) {
  if (!run) return 'pending';
  if (pmcpGithubRunQueuedTooLong(run)) return 'warning';
  if (run.status === 'succeeded') return 'ready';
  if (run.status === 'failed') return 'blocked';
  if (run.status === 'superseded' || run.status === 'canceled') return 'warning';
  return 'pending';
}

function pmcpGithubConnectError(form) {
  if (!String(form.githubInstallationId || '').trim() || !String(form.githubInstallationGrant || '').trim()) return 'Install the GitHub App from this page before connecting.';
  if (!/^\d+$/.test(String(form.githubInstallationId || '').trim())) return 'GitHub installation ID must be numeric.';
  const repositoryId = String(form.githubRepositoryId || '').trim();
  if (repositoryId && !/^\d+$/.test(repositoryId)) return 'Repository ID must be numeric when provided.';
  if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(String(form.repositoryFullName || '').trim())) return 'Repository must be owner/repo.';
  if (!String(form.productionRef || '').trim()) return 'Production ref is required.';
  if (!String(form.openapiPath || '').trim()) return 'OpenAPI path is required.';
  if (form.confirmProductionDeploy !== true) return 'Confirm production docs deployment before connecting.';
  return '';
}

function pmcpAuthFormError(form) {
  if (form.validationMode === 'presence_only' && !form.confirmRisk) return 'Confirm the presence-only security risk or choose validation before tools.';
  if (form.validationMode === 'validate_before_tools' && !form.validationEndpoint.trim()) return 'Validation endpoint is required.';
  if ((form.method === 'api_key_header' || form.method === 'api_key_query') && !form.credentialName.trim()) return 'Credential name is required.';
  return '';
}

function pmcpCreateNextBlocker({ step, name, slug, docsMode, docsText, githubError, authForm }) {
  if (step === 1) {
    if (!name.trim()) return 'Product MCP name is required.';
    if (!slug) return 'Slug is required.';
  }
  if (step === 2) {
    if (docsMode === 'github' && githubError) return githubError;
    if (docsMode === 'upload' && !docsText.trim()) return 'Choose or paste an OpenAPI document.';
  }
  if (step === 3) return pmcpAuthFormError(authForm);
  return '';
}

// ---- Detail: API Connection (authentication) -------------------------------

function PmcpAuthLabel(auth) {
  if (!auth || auth.status !== 'configured') return 'Not configured';
  if (auth.credentialLocation === 'query') return 'API key query param';
  if (String(auth.credentialName || '').toLowerCase() === 'authorization') {
    if (auth.tokenFormat === 'bearer') return 'Bearer token';
    if (auth.tokenFormat === 'token') return 'Token scheme';
  }
  return 'API key header';
}

function PmcpApiConnectionSection({ detail, onConfigure }) {
  const auth = detail.authentication || {};
  const configured = auth.status === 'configured';
  const state = auth.validationState || (auth.validationMode === 'presence_only' ? 'presence_only' : 'not_validated');
  const validationLabel = state === 'passing'
    ? 'validation passing'
    : state === 'failing'
      ? 'validation failing'
      : state === 'presence_only'
        ? 'presence only'
        : 'not yet validated';
  const validationSub = state === 'presence_only'
    ? 'Security warning: credentials are only checked for presence'
    : `Security step: validates at ${auth.validationEndpoint || 'your endpoint'} before tools are exposed`;
  const iconStatus = state === 'passing' ? 'ready' : (state === 'not_validated' ? 'pending' : 'warning');
  return (
    <PmcpCollapsibleSection title="API Connection" meta={configured ? 'configured' : 'missing'}>
      {detail.securityWarning && (
        <div className="pmcp-callout danger" style={{ marginBottom: 14 }}>
          <Icon name="alert" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
          <div><b>Keys are not validated.</b> {detail.securityWarning.message}</div>
        </div>
      )}
      {!configured ? (
        <div className="pmcp-card-plain" style={{ padding: 0 }}>
          <div className="pmcp-callout warn" style={{ border: 0, borderBottom: '1px solid var(--amber-soft-border)' }}>
            <Icon name="alert" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
            <div><b>No API Connection configured.</b> We will not expose tools until caller credentials can be checked.</div>
          </div>
          <div style={{ padding: '16px 18px' }}>
            <Button variant="primary" onClick={onConfigure}><Icon name="lock" size={13} />Configure API Connection</Button>
          </div>
        </div>
      ) : (
        <div className="pmcp-summary-card">
          <div className="pmcp-summary-row">
            <PmcpReadyIcon status={iconStatus} />
            <div style={{ minWidth: 0 }}>
              <div className="pmcp-summary-label">{PmcpAuthLabel(auth)} · {validationLabel}</div>
              <div className="pmcp-summary-sub">{validationSub}. Same credential forwarded, never stored.</div>
            </div>
            <Button size="sm" onClick={onConfigure}><Icon name="settings" size={13} />Edit</Button>
          </div>
        </div>
      )}
    </PmcpCollapsibleSection>
  );
}

// ---- Detail header ---------------------------------------------------------

function PmcpDetailHeader({ detail, onBack, onPauseResume, onSettings, busy }) {
  const toast = useToast();
  const paused = detail.status === 'paused';
  const copyUrl = () => toast.show({ tone: 'ok', title: 'Public MCP URL copied' });
  return (
    <div className="pmcp-detail-head">
      <div className="pmcp-detail-main">
        <button onClick={onBack} className="pmcp-detail-back">
          <Icon name="chevronLeft" size={13} /> Product MCPs
        </button>
        <div className="pmcp-detail-title-row">
          <h1>{detail.name}</h1>
          <PmcpStatusPill status={detail.status} />
        </div>
        <div className="pmcp-url-box">
          <div className="pmcp-url-box-text">
            <span className="pmcp-url-box-label">MCP URL</span>
            <Icon name="globe" size={13} style={{ color: 'var(--text-3)', flexShrink: 0 }} />
            <code>{detail.publicMcpUrl}</code>
          </div>
          <PmcpCopyButton value={detail.publicMcpUrl} label="Copy MCP URL" onCopied={copyUrl} />
        </div>
      </div>
      <div className="pmcp-detail-actions">
        <Button size="sm" onClick={onPauseResume} disabled={busy}>
          <Icon name={paused ? 'play' : 'pause'} size={13} />{paused ? 'Resume' : 'Pause'}
        </Button>
        <Button size="sm" onClick={onSettings}><Icon name="settings" size={13} />Settings</Button>
      </div>
    </div>
  );
}

// ---- Detail view -----------------------------------------------------------

function ProductMcpDetailView({ id, onBack, onDeleted, setupNotice }) {
  const toast = useToast();
  const resource = useApiResource(`/api/product-mcps/${id}`, [id]);
  const githubResource = useApiResource(`/api/product-mcps/${id}/github`, [id]);
  const [busy, setBusy] = usePmcpState(false);
  const [modal, setModal] = usePmcpState(null); // 'settings' | 'connection' | 'upload' | 'upload-key'
  const detail = resource.data;

  const reload = resource.reload;

  const patch = usePmcpCallback(async (body, successMsg) => {
    setBusy(true);
    try {
      await apiFetch(`/api/product-mcps/${id}`, { method: 'PATCH', body: JSON.stringify(body) });
      if (successMsg) toast.show({ tone: 'ok', title: successMsg });
      await reload();
    } catch (error) {
      toast.show({ tone: 'bad', title: 'Update failed', description: error.message });
    } finally {
      setBusy(false);
    }
  }, [id, reload, toast]);

  const pauseResume = usePmcpCallback(() => {
    if (!detail) return;
    const paused = detail.status === 'paused';
    patch({ isActive: paused }, paused ? 'Endpoint resumed' : 'Endpoint paused');
  }, [detail, patch]);

  const handleAction = usePmcpCallback((action) => {
    if (action === 'activate') patch({ isActive: true }, 'Endpoint activated · now live');
    else if (action === 'configure_authentication') setModal('connection');
    else if (action === 'upload_docs' && githubResource.data?.connected === true) {
      document.getElementById('pmcp-github-section')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
    } else if (action === 'upload_docs') setModal('upload');
  }, [githubResource.data?.connected, patch]);

  if (resource.loading && !detail) {
    return (
      <div className="code inline-loading" style={{ margin: '40px auto' }}>
        <LoadingSpinner size="sm" label="Loading Product MCP" decorative /> Loading…
      </div>
    );
  }
  if (resource.error || !detail) {
    return (
      <div>
        <button onClick={onBack} style={{ background: 'transparent', border: 0, cursor: 'pointer', marginBottom: 16, color: 'var(--text-3)', fontFamily: 'var(--font-mono)', fontSize: 11.5, textTransform: 'uppercase', letterSpacing: '0.06em' }}>
          <Icon name="chevronLeft" size={13} /> Product MCPs
        </button>
        <EmptyState icon="alert" title="Couldn't load this Product MCP" body={resource.error?.message || 'It may have been deleted.'} />
      </div>
    );
  }

  return (
    <div>
      <PmcpDetailHeader
        detail={detail}
        busy={busy}
        onBack={onBack}
        onPauseResume={pauseResume}
        onSettings={() => setModal('settings')} />
      <div style={{ height: 18 }} />
      {setupNotice?.errors?.length > 0 && (
        <div className="pmcp-callout danger" style={{ marginBottom: 18 }}>
          <Icon name="alert" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
          <div style={{ minWidth: 0, flex: 1 }}>
            <div><b>Created, but setup needs attention.</b></div>
            <div style={{ marginTop: 4 }}>
              {setupNotice.errors.map((err) => (
                <div key={err.key}>{err.label}: {err.message}</div>
              ))}
            </div>
          </div>
          {setupNotice.errors.some((err) => err.key === 'apiDocs') && (
            <Button size="sm" onClick={() => setModal('upload')}><Icon name="upload" size={13} />Retry upload</Button>
          )}
        </div>
      )}
      <PmcpSetupProgressPanel detail={detail} githubResource={githubResource} onDetailReload={reload} />
      <PmcpActivationPanel detail={detail} busy={busy} onActivate={() => handleAction('activate')} />
      <PmcpReadinessSection detail={detail} githubConnected={githubResource.data?.connected === true} onAction={handleAction} />
      <PmcpRuntimeHealthSection detail={detail} />
      <PmcpApiDocsSection
        detail={detail}
        githubState={githubResource.data}
        githubLoading={githubResource.loading}
        onUpload={() => setModal('upload')}
        onCreateUploadKey={() => setModal('upload-key')} />
      <PmcpGithubSection detail={detail} githubResource={githubResource} />
      <PmcpApiConnectionSection detail={detail} onConfigure={() => setModal('connection')} />
      <PmcpDeleteSection detail={detail} onDeleted={onDeleted} />

      {modal === 'settings' && (
        <PmcpSettingsModal detail={detail} onClose={() => setModal(null)} onSaved={reload} onDeleted={onDeleted} />
      )}
      {modal === 'connection' && (
        <PmcpConnectionModal detail={detail} onClose={() => setModal(null)} onSaved={reload} />
      )}
      {modal === 'upload' && (
        <PmcpUploadModal detail={detail} onClose={() => setModal(null)} onUploaded={reload} />
      )}
      {modal === 'upload-key' && (
        <PmcpUploadKeyModal detail={detail} onClose={() => setModal(null)} />
      )}
    </div>
  );
}

// ---- Modal shell -----------------------------------------------------------

function PmcpModalShell({ title, sub, onClose, children, foot, width = 620, closeDisabled = false }) {
  useEscapeToClose({ enabled: true, disabled: closeDisabled, onClose });
  return (
    <div className="pmcp-modal-backdrop" onMouseDown={(e) => { if (!closeDisabled && e.target === e.currentTarget) onClose(); }}>
      <div className="pmcp-modal" role="dialog" aria-modal="true" style={{ maxWidth: width }}>
        <div className="pmcp-modal-head">
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="pmcp-modal-title">{title}</div>
            {sub && <div style={{ fontSize: 12, color: 'var(--text-3)', marginTop: 2 }}>{sub}</div>}
          </div>
          <button className="pmcp-icon-btn" onClick={onClose} disabled={closeDisabled} aria-label="Close"><Icon name="x" size={14} /></button>
        </div>
        <div className="pmcp-modal-body">{children}</div>
        <div className="pmcp-modal-foot">{foot}</div>
      </div>
    </div>
  );
}

// ---- Auth method form (shared by create + connection modal) ---------------

function pmcpAuthDefaults(detail) {
  const a = detail?.authentication;
  if (a && a.status === 'configured') {
    let method = 'bearer';
    if (a.credentialLocation === 'query') method = 'api_key_query';
    else if (a.tokenFormat === 'token' && String(a.credentialName || '').toLowerCase() === 'authorization') method = 'token';
    else if (String(a.credentialName || '').toLowerCase() !== 'authorization' || a.tokenFormat !== 'bearer') method = 'api_key_header';
    return {
      method,
      credentialName: a.credentialName || 'Authorization',
      validationMode: a.validationMode || 'validate_before_tools',
      validationEndpoint: a.validationEndpoint || '',
      confirmRisk: false,
    };
  }
  return { method: 'bearer', credentialName: 'Authorization', validationMode: 'validate_before_tools', validationEndpoint: '', confirmRisk: false };
}

function pmcpAuthBodyFromForm(form) {
  const credentialLocation = form.method === 'api_key_query' ? 'query' : 'header';
  const tokenFormat = form.method === 'bearer' ? 'bearer' : form.method === 'token' ? 'token' : 'raw';
  let credentialName = form.credentialName;
  if (form.method === 'bearer' || form.method === 'token') credentialName = 'Authorization';
  return {
    credentialLocation,
    credentialName,
    tokenFormat,
    upstreamForwarding: { mode: 'same' },
    validationMode: form.validationMode,
    validationEndpoint: form.validationMode === 'validate_before_tools' ? form.validationEndpoint : undefined,
    confirmPresenceOnlyRisk: form.validationMode === 'presence_only' ? form.confirmRisk : undefined,
  };
}

function PmcpAuthMethodForm({ form, setForm }) {
  const needsName = form.method === 'api_key_header' || form.method === 'api_key_query';
  return (
    <>
      <div className="pmcp-field-label">Auth method</div>
      <div style={{ marginBottom: 14 }}>
        {PMCP_AUTH_METHODS.map((m) => (
          <div key={m.id} className={`pmcp-radio-card ${form.method === m.id ? 'sel' : ''}`} onClick={() => setForm({ ...form, method: m.id })}>
            <span className="pmcp-radio-dot" />
            <div>
              <div className="pmcp-radio-title">{m.title}</div>
              <div className="pmcp-radio-sub">{m.sub}</div>
            </div>
            <div />
          </div>
        ))}
        <div className="pmcp-radio-card disabled">
          <span className="pmcp-radio-dot" />
          <div>
            <div className="pmcp-radio-title">OAuth</div>
            <div className="pmcp-radio-sub">Delegated OAuth to your upstream provider.</div>
          </div>
          <span className="pmcp-soon-pill">Coming soon</span>
        </div>
      </div>
      {needsName && (
        <div className="pmcp-field">
          <label className="pmcp-field-label">{form.method === 'api_key_header' ? 'Header name' : 'Query parameter name'}</label>
          <input className="pmcp-field-input mono" value={form.credentialName}
            onChange={(e) => setForm({ ...form, credentialName: e.target.value })}
            placeholder={form.method === 'api_key_header' ? 'X-API-Key' : 'api_key'} />
        </div>
      )}
      <div className="pmcp-field">
        <label className="pmcp-field-label">Credential validation</label>
        <Select
          ariaLabel="Validation mode"
          value={form.validationMode}
          onChange={(v) => setForm({ ...form, validationMode: v, confirmRisk: false })}
          options={[
            { value: 'validate_before_tools', label: 'Validate before serving tools (recommended)' },
            { value: 'presence_only', label: 'Presence only — do not validate (less secure)' },
          ]} />
      </div>
      {form.validationMode === 'validate_before_tools' && (
        <div className="pmcp-field">
          <label className="pmcp-field-label">Validation endpoint</label>
          <input className="pmcp-field-input mono" value={form.validationEndpoint}
            onChange={(e) => setForm({ ...form, validationEndpoint: e.target.value })}
            placeholder="/me  or  https://api.example.com/me" />
          <div className="pmcp-field-hint">We call this endpoint with the caller's credential before serving any tool. A 2xx allows the request; 401/403 rejects it.</div>
        </div>
      )}
      {form.validationMode === 'presence_only' && (
        <div className="pmcp-callout danger" style={{ marginBottom: 14 }}>
          <Icon name="alert" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
          <div>
            <b>Keys won't be validated.</b> Anyone with this MCP URL can provide a fake credential and inspect your API docs/capabilities.
            <label style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 8, color: 'var(--text)' }}>
              <input type="checkbox" checked={form.confirmRisk} onChange={(e) => setForm({ ...form, confirmRisk: e.target.checked })} />
              I understand the risk and want to enable presence-only.
            </label>
          </div>
        </div>
      )}
    </>
  );
}

// ---- Connection modal ------------------------------------------------------

function PmcpConnectionModal({ detail, onClose, onSaved }) {
  const toast = useToast();
  const [form, setForm] = usePmcpState(() => pmcpAuthDefaults(detail));
  const [saving, setSaving] = usePmcpState(false);
  const valid = (form.validationMode !== 'presence_only' || form.confirmRisk)
    && (form.validationMode !== 'validate_before_tools' || form.validationEndpoint.trim().length > 0)
    && (!(form.method === 'api_key_header' || form.method === 'api_key_query') || form.credentialName.trim().length > 0);

  const save = async () => {
    setSaving(true);
    try {
      await apiFetch(`/api/product-mcps/${detail.id}/authentication`, {
        method: 'PATCH',
        body: JSON.stringify(pmcpAuthBodyFromForm(form)),
      });
      toast.show({ tone: 'ok', title: 'API Connection saved' });
      await onSaved();
      onClose();
    } catch (error) {
      toast.show({ tone: 'bad', title: 'Could not save', description: error.message });
    } finally {
      setSaving(false);
    }
  };

  return (
    <PmcpModalShell title="API Connection" sub="Security: validate callers before tools run" onClose={onClose}
      foot={<>
        <Button variant="ghost" onClick={onClose}>Cancel</Button>
        <div className="pmcp-spacer" />
        <Button variant="primary" disabled={!valid || saving} loading={saving} onClick={save}>
          <Icon name="lock" size={13} />Save connection
        </Button>
      </>}>
      <div className="pmcp-sec-intro" style={{ marginBottom: 16 }}>
        <strong className="pmcp-security-kicker">Security step</strong>: callers present their own product API credential to the MCP. Armature validates it before tools run, then forwards that same credential to your API.
      </div>
      <PmcpAuthMethodForm form={form} setForm={setForm} />
    </PmcpModalShell>
  );
}

// ---- Settings modal --------------------------------------------------------

function PmcpDeleteSection({ detail, onDeleted, compact = false, onCloseAfterDelete = null, onDeletingChange = null }) {
  const toast = useToast();
  const [confirmDel, setConfirmDel] = usePmcpState(false);
  const [deleting, setDeleting] = usePmcpState(false);
  const [deleteError, setDeleteError] = usePmcpState('');
  const setDeleteBusy = (value) => {
    setDeleting(value);
    if (typeof onDeletingChange === 'function') onDeletingChange(value);
  };

  const doDelete = async () => {
    setDeleteBusy(true);
    setDeleteError('');
    let result;
    try {
      result = await apiFetch(`/api/product-mcps/${detail.id}`, { method: 'DELETE' });
    } catch (error) {
      setDeleteError(error.message || 'Could not delete this Product MCP');
      toast.show({ tone: 'bad', title: 'Could not delete', description: error.message });
      setDeleteBusy(false);
      return;
    }

    if (result?.domainTeardown?.status === 'failed') {
      toast.show({
        tone: 'ok',
        title: 'Product MCP deleted',
        description: 'The endpoint is retired. Armature will finish any remaining domain cleanup.',
      });
    } else {
      toast.show({ tone: 'ok', title: 'Product MCP deleted' });
    }
    if (typeof onDeleted === 'function') {
      try {
        await onDeleted(detail.id);
      } catch (_error) {
        toast.show({ tone: 'warn', title: 'Deleted. Refresh if the list looks stale.' });
      }
    }
    setDeleteBusy(false);
    if (typeof onCloseAfterDelete === 'function') onCloseAfterDelete();
  };

  const startDeleteConfirmation = () => {
    setDeleteError('');
    setConfirmDel(true);
  };

  return (
    <div className={compact ? '' : 'pmcp-sec pmcp-delete-sec'}>
      {!compact && (
        <div className="pmcp-sec-hd">
          <h2 className="pmcp-sec-title">Delete Product MCP</h2>
        </div>
      )}
      <div className={`pmcp-danger-zone ${deleting ? 'is-busy' : ''}`}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--red)' }}>
            {confirmDel ? 'Confirm deletion' : 'Delete this Product MCP'}
          </div>
          <div style={{ fontSize: 12, color: 'var(--text-2)', marginTop: 2, lineHeight: 1.45 }}>
            {confirmDel
              ? 'Click Confirm delete to retire the endpoint and remove it from this workspace.'
              : "The public URL stops resolving. This can't be undone."}
          </div>
        </div>
        {confirmDel
          ? <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
              <Button size="sm" variant="ghost" disabled={deleting} onClick={() => setConfirmDel(false)}>Cancel</Button>
              <Button size="sm" variant="danger" loading={deleting} loadingLabel="Deleting" disabled={deleting} onClick={doDelete}>
                <Icon name="trash" size={13} />Confirm delete
              </Button>
            </div>
          : <Button size="sm" variant="danger" onClick={startDeleteConfirmation}><Icon name="trash" size={13} />Delete</Button>}
      </div>
      {confirmDel && !deleting && !deleteError && (
        <div className="pmcp-callout warn" style={{ marginTop: 10 }}>
          <Icon name="alert" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
          <div>This is the final confirmation step. The endpoint remains unchanged until you click Confirm delete.</div>
        </div>
      )}
      {deleting && (
        <div className="pmcp-callout info" style={{ marginTop: 10 }} role="status" aria-live="polite">
          <LoadingSpinner size="sm" label="Deleting Product MCP" />
          <div>
            <b>Deleting Product MCP...</b>
            <div>The endpoint is being retired. You will return to Product MCPs when it finishes.</div>
          </div>
        </div>
      )}
      {deleteError && <div className="pmcp-callout danger" style={{ marginTop: 10 }}><Icon name="alert" size={14} /><div>{deleteError}</div></div>}
    </div>
  );
}

function PmcpSettingsModal({ detail, onClose, onSaved, onDeleted }) {
  const toast = useToast();
  const [name, setName] = usePmcpState(detail.name);
  const [slug, setSlug] = usePmcpState(detail.sourceSlug);
  const [baseUrl, setBaseUrl] = usePmcpState(detail.baseUrl || '');
  const [authForm, setAuthForm] = usePmcpState(() => pmcpAuthDefaults(detail));
  const [saving, setSaving] = usePmcpState(false);
  const [deleting, setDeleting] = usePmcpState(false);
  const initialAuthForm = React.useMemo(() => pmcpAuthDefaults(detail), [detail.id]);
  const authChanged = JSON.stringify(pmcpAuthBodyFromForm(authForm)) !== JSON.stringify(pmcpAuthBodyFromForm(initialAuthForm));
  const authError = authChanged ? pmcpAuthFormError(authForm) : '';
  const baseDomain = (() => {
    try {
      return new URL(detail.publicMcpUrl).host;
    } catch (_error) {
      return (detail.publicMcpUrl || '').replace(/^https?:\/\//, '').replace(/\/.*$/, '');
    }
  })();

  const save = async () => {
    if (authError) {
      toast.show({ tone: 'bad', title: 'Check API Connection', description: authError });
      return;
    }
    setSaving(true);
    let settingsSaved = false;
    try {
      await apiFetch(`/api/product-mcps/${detail.id}`, {
        method: 'PATCH',
        body: JSON.stringify({ name, sourceSlug: slug, baseUrl: baseUrl || null }),
      });
      settingsSaved = true;
      if (authChanged) {
        await apiFetch(`/api/product-mcps/${detail.id}/authentication`, {
          method: 'PATCH',
          body: JSON.stringify(pmcpAuthBodyFromForm(authForm)),
        });
      }
      toast.show({ tone: 'ok', title: 'Settings saved' });
      await onSaved();
      onClose();
    } catch (error) {
      if (settingsSaved && authChanged) {
        await onSaved().catch(() => {});
        toast.show({
          tone: 'bad',
          title: 'API Connection not saved',
          description: `Name, slug, and Base API URL were saved. ${error.message}`,
        });
        return;
      }
      toast.show({ tone: 'bad', title: 'Could not save', description: error.message });
    } finally {
      setSaving(false);
    }
  };

  return (
    <PmcpModalShell title="Settings" sub={detail.name} onClose={onClose} closeDisabled={deleting}
      foot={<>
        <Button variant="ghost" onClick={onClose} disabled={saving || deleting}>Cancel</Button>
        <div className="pmcp-spacer" />
        <Button variant="primary" loading={saving} disabled={saving || deleting || Boolean(authError)} onClick={save}>Save changes</Button>
      </>}>
      <div className="pmcp-field">
        <label className="pmcp-field-label">Product MCP name</label>
        <input className="pmcp-field-input" value={name} onChange={(e) => setName(e.target.value)} />
      </div>
      <div className="pmcp-field">
        <label className="pmcp-field-label">Slug</label>
        <input className="pmcp-field-input mono" value={slug} onChange={(e) => setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9_-]/g, ''))} />
      </div>
      <div className="pmcp-field">
        <label className="pmcp-field-label">Generated domain</label>
        <div className="pmcp-field-prefix">
          <span className="pre">{baseDomain}/</span>
          <input value={slug} readOnly style={{ color: 'var(--text-3)' }} />
        </div>
      </div>
      <div className="pmcp-field">
        <label className="pmcp-field-label">Custom domain</label>
        <div className="pmcp-field-prefix" style={{ opacity: 0.6 }}>
          <input placeholder="mcp.yourbrand.com" readOnly disabled />
          <span className="pre" style={{ borderRight: 0, borderLeft: '1px solid var(--border)' }}>
            <span className="pmcp-soon-pill" style={{ border: 0, background: 'transparent' }}>BYOD soon</span>
          </span>
        </div>
        <div className="pmcp-field-hint">Bring your own domain is coming soon.</div>
      </div>
      <div className="pmcp-field">
        <label className="pmcp-field-label">Base API URL</label>
        <input className="pmcp-field-input mono" value={baseUrl} onChange={(e) => setBaseUrl(e.target.value)} placeholder="https://api.example.com/v1" />
      </div>
      <div className="pmcp-sec" style={{ marginTop: 8, marginBottom: 0 }}>
        <div className="pmcp-sec-hd">
          <h2 className="pmcp-sec-title">API Connection</h2>
          <span className="pmcp-page-foot-note">{authChanged ? 'Unsaved changes' : PmcpAuthLabel(detail.authentication)}</span>
        </div>
        <div className="pmcp-sec-intro" style={{ marginBottom: 16 }}>
          Validate caller credentials before the Product MCP serves tools. For Armature API keys, use <code>/api/armature/v1/org</code>, not <code>/api/me</code>.
        </div>
        <PmcpAuthMethodForm form={authForm} setForm={setAuthForm} />
        {authError && (
          <div className="pmcp-callout warn" style={{ marginTop: -4, marginBottom: 14 }}>
            <Icon name="alert" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
            <div>{authError}</div>
          </div>
        )}
      </div>
      <div style={{ marginTop: 8 }}>
        <div className="pmcp-field-label" style={{ color: 'var(--red)' }}>Danger zone</div>
        <PmcpDeleteSection detail={detail} compact onCloseAfterDelete={onClose} onDeleted={onDeleted} onDeletingChange={setDeleting} />
      </div>
    </PmcpModalShell>
  );
}

// ---- Upload modal ----------------------------------------------------------

function PmcpUploadModal({ detail, onClose, onUploaded }) {
  const toast = useToast();
  const [text, setText] = usePmcpState('');
  const [fileName, setFileName] = usePmcpState('');
  const [uploading, setUploading] = usePmcpState(false);
  const [result, setResult] = usePmcpState(null);

  const chooseFile = async (event) => {
    const file = event.target.files?.[0];
    if (!file) return;
    try {
      const content = await readPmcpOpenApiFile(file);
      setText(content);
      setFileName(file.name);
      setResult(null);
    } catch (error) {
      toast.show({ tone: 'bad', title: 'Could not read file', description: error.message });
    } finally {
      event.target.value = '';
    }
  };

  const upload = async () => {
    setUploading(true);
    setResult(null);
    try {
      const resp = await apiFetch(`/api/product-mcps/${detail.id}/api-docs`, {
        method: 'POST',
        body: JSON.stringify({ openapi: text, source: 'manual_upload' }),
      });
      setResult({ ok: true, ...resp.apiDocs });
      toast.show({ tone: 'ok', title: 'API docs uploaded', description: `${resp.apiDocs.capabilitiesCount} capabilities` });
      await onUploaded();
    } catch (error) {
      setResult({ ok: false, message: error.message });
      toast.show({ tone: 'bad', title: 'Upload failed', description: error.message });
    } finally {
      setUploading(false);
    }
  };

  return (
    <PmcpModalShell title="Upload OpenAPI" sub={detail.name} onClose={onClose}
      foot={<>
        <Button variant="ghost" onClick={onClose}>Close</Button>
        <div className="pmcp-spacer" />
        <Button variant="primary" loading={uploading} disabled={!text.trim() || uploading} onClick={upload}>
          <Icon name="upload" size={13} />Upload OpenAPI
        </Button>
      </>}>
      <div className="pmcp-sec-intro" style={{ marginBottom: 14 }}>
        Paste an OpenAPI document or choose a JSON/YAML file. We'll validate it and regenerate the
        capabilities your customers' agents call.
      </div>
      <div className="pmcp-field" style={{ marginBottom: 12 }}>
        <label className="pmcp-field-label">OpenAPI file</label>
        <input
          className="pmcp-field-input"
          type="file"
          accept=".json,.yaml,.yml,application/json,application/yaml,text/yaml,text/x-yaml"
          onChange={chooseFile} />
        <div className="pmcp-field-hint">{fileName ? `Loaded ${fileName}` : 'Choose a .json, .yaml, or .yml file, or paste below.'}</div>
      </div>
      <textarea
        className="pmcp-field-input mono"
        style={{ minHeight: 220, resize: 'vertical', width: '100%' }}
        placeholder={'{\n  "openapi": "3.1.0",\n  "info": { "title": "...", "version": "1.0.0" },\n  "paths": { ... }\n}'}
        value={text}
        onChange={(e) => setText(e.target.value)} />
      {result && result.ok && (
        <div className="pmcp-callout ok" style={{ marginTop: 14 }}>
          <Icon name="check" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
          <div><b>Valid OpenAPI.</b> {result.capabilitiesCount} capabilities detected, {result.warningsCount} warning{result.warningsCount === 1 ? '' : 's'}.</div>
        </div>
      )}
      {result && !result.ok && (
        <div className="pmcp-callout danger" style={{ marginTop: 14 }}>
          <Icon name="alert" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
          <div><b>Validation failed.</b> {result.message}</div>
        </div>
      )}
    </PmcpModalShell>
  );
}

// ---- Upload key modal ------------------------------------------------------

function PmcpUploadKeyModal({ detail, onClose }) {
  const toast = useToast();
  const defaultName = `${detail.name || detail.sourceSlug} OpenAPI upload`;
  const [name, setName] = usePmcpState(defaultName);
  const [creating, setCreating] = usePmcpState(false);
  const [token, setToken] = usePmcpState('');
  const endpoint = `${window.location.origin}/api/openapi-artifacts/upload`;
  const environment = detail.environment || 'production';
  const create = async () => {
    setCreating(true);
    setToken('');
    try {
      const result = await apiFetch('/api/settings/api-keys', {
        method: 'POST',
        body: JSON.stringify({ name: name.trim(), purpose: 'managed_mcp_upload' }),
      });
      setToken(result.token || '');
      toast.show({ tone: 'ok', title: 'Upload key created' });
    } catch (error) {
      toast.show({ tone: 'bad', title: 'Could not create upload key', description: error.message });
    } finally {
      setCreating(false);
    }
  };
  const copyToken = () => toast.show({ tone: 'ok', title: 'Upload key copied' });
  return (
    <PmcpModalShell title="OpenAPI upload key" sub={detail.name} onClose={onClose}
      foot={<>
        <Button variant="ghost" onClick={onClose}>Close</Button>
        <div className="pmcp-spacer" />
        <Button variant="primary" loading={creating} disabled={!name.trim() || creating || Boolean(token)} onClick={create}>
          <Icon name="key" size={13} />Create key
        </Button>
      </>}>
      <div className="pmcp-callout warn" style={{ marginBottom: 14 }}>
        <Icon name="alert" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
        <div><b>Upload key only.</b> Use this from CI to upload OpenAPI docs. It is not the caller credential your customers pass to the MCP.</div>
      </div>
      <div className="pmcp-field" style={{ marginBottom: 12 }}>
        <label className="pmcp-field-label">Key name</label>
        <input
          className="pmcp-field-input"
          value={name}
          disabled={creating || Boolean(token)}
          onChange={(event) => setName(event.target.value)}
          placeholder="OpenAPI upload key" />
      </div>
      <div className="pmcp-summary-card" style={{ marginBottom: 12 }}>
        <div className="pmcp-summary-row">
          <PmcpReadyIcon status="pending" />
          <div style={{ minWidth: 0 }}>
            <div className="pmcp-summary-label">CI upload target</div>
            <div className="pmcp-summary-sub mono">{endpoint}</div>
          </div>
        </div>
        <div className="pmcp-summary-row">
          <PmcpReadyIcon status="pending" />
          <div style={{ minWidth: 0 }}>
            <div className="pmcp-summary-label">Source</div>
            <div className="pmcp-summary-sub mono">{detail.sourceSlug} · {environment}</div>
          </div>
        </div>
        <div className="pmcp-summary-row">
          <PmcpReadyIcon status="warning" />
          <div style={{ minWidth: 0 }}>
            <div className="pmcp-summary-label">Request signing</div>
            <div className="pmcp-summary-sub">Send Authorization plus x-armature-timestamp and x-armature-signature = HMAC-SHA256(key, timestamp.body).</div>
          </div>
        </div>
      </div>
      {token ? (
        <div className="pmcp-callout ok">
          <Icon name="check" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
          <div style={{ minWidth: 0, flex: 1 }}>
            <div><b>Shown once.</b> Store this token in the matching dev or prod CI secret.</div>
            <div className="settings-token-row" style={{ marginTop: 10 }}>
              <div className="code api-key-token">{token}</div>
              <PmcpCopyButton value={token} label="Copy token" variant="btn" onCopied={copyToken} />
            </div>
          </div>
        </div>
      ) : (
        <div className="pmcp-sec-intro">
          Admins and owners can create scoped upload keys. Existing upload keys still live in Settings, but this path creates the right key for this Product MCP.
        </div>
      )}
    </PmcpModalShell>
  );
}

// ---- Create flow (4-step modal) -------------------------------------------

const PMCP_STEPS = [
  { n: 1, label: 'Name' },
  { n: 2, label: 'API docs' },
  { n: 3, label: 'API Connection' },
  { n: 4, label: 'Review' },
];

function pmcpSlugify(s) {
  return String(s || '').toLowerCase().trim().replace(/api/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 80) || '';
}

function PmcpStepper({ current }) {
  return (
    <div className="pmcp-stepper">
      {PMCP_STEPS.map((s, i) => (
        <React.Fragment key={s.n}>
          {i > 0 && <span className="pmcp-step-bar" />}
          <div className={`pmcp-step ${current === s.n ? 'active' : ''} ${current > s.n ? 'done' : ''}`}>
            <span className="pmcp-step-dot">{current > s.n ? <Icon name="check" size={12} /> : s.n}</span>
            <span className="pmcp-step-label">{s.label}</span>
          </div>
        </React.Fragment>
      ))}
    </div>
  );
}

function ProductMcpCreateModal({ baseDomain, githubSetup, onClose, onCreated }) {
  const toast = useToast();
  const draft = readPmcpCreateDraft();
  const [step, setStep] = usePmcpState((githubSetup?.githubInstallationId || githubSetup?.setupError) ? 2 : 1);
  const [name, setName] = usePmcpState(draft.name || '');
  const [slug, setSlug] = usePmcpState(draft.slug || '');
  const [slugTouched, setSlugTouched] = usePmcpState(draft.slugTouched === true);
  const [baseUrl, setBaseUrl] = usePmcpState(draft.baseUrl || '');
  const [docsMode, setDocsMode] = usePmcpState(githubSetup?.githubInstallationId ? 'github' : (draft.docsMode || 'github'));
  const [docsText, setDocsText] = usePmcpState(draft.docsText || '');
  const [docsFileName, setDocsFileName] = usePmcpState('');
  const [authForm, setAuthForm] = usePmcpState(() => draft.authForm || pmcpAuthDefaults(null));
  const [githubForm, setGithubForm] = usePmcpState({
    githubInstallationId: githubSetup?.githubInstallationId || draft.githubForm?.githubInstallationId || '',
    githubInstallationGrant: githubSetup?.githubInstallationGrant || draft.githubForm?.githubInstallationGrant || '',
    githubRepositoryId: draft.githubForm?.githubRepositoryId || '',
    repositoryFullName: draft.githubForm?.repositoryFullName || '',
    productionRef: draft.githubForm?.productionRef || PMCP_GITHUB_DEFAULT_REF,
    openapiMode: PMCP_GITHUB_OPENAPI_MODE,
    openapiPath: pmcpCleanOpenApiPath(draft.githubForm?.openapiPath),
    prCommentsEnabled: draft.githubForm?.prCommentsEnabled !== false,
    confirmProductionDeploy: draft.githubForm?.confirmProductionDeploy !== false,
  });
  const [submitting, setSubmitting] = usePmcpState(false);
  const [githubInstalling, setGithubInstalling] = usePmcpState(false);
  useEscapeToClose({ enabled: true, disabled: submitting || githubInstalling, onClose });
  const githubError = pmcpGithubConnectError(githubForm);
  const authError = pmcpAuthFormError(authForm);

  const canNext = step === 1 ? name.trim().length > 0 && slug.length > 0
    : step === 2 ? (docsMode === 'ci' || (docsMode === 'github' ? !githubError : docsText.trim().length > 0))
    : step === 3 ? !authError
    : true;
  const nextBlocker = canNext ? '' : pmcpCreateNextBlocker({
    step,
    name,
    slug,
    docsMode,
    docsText,
    githubError,
    authForm,
  });

  const chooseDocsFile = async (event) => {
    const file = event.target.files?.[0];
    if (!file) return;
    try {
      const content = await readPmcpOpenApiFile(file);
      setDocsMode('upload');
      setDocsText(content);
      setDocsFileName(file.name);
    } catch (error) {
      toast.show({ tone: 'bad', title: 'Could not read file', description: error.message });
    } finally {
      event.target.value = '';
    }
  };

  const persistCreateDraft = () => {
    writePmcpCreateDraft({
      name,
      slug,
      slugTouched,
      baseUrl,
      docsMode: 'github',
      docsText,
      authForm,
      githubForm: pmcpCleanGithubForm(githubForm),
    });
  };

  const installGithub = async () => {
    persistCreateDraft();
    setGithubInstalling(true);
    try {
      await startPmcpGithubInstall({
        returnTo: '/product-mcps?create=1',
        repositorySelection: 'select',
      });
    } catch (error) {
      toast.show({ tone: 'bad', title: 'GitHub install failed', description: error.message });
    } finally {
      setGithubInstalling(false);
    }
  };

  const submit = async () => {
    setSubmitting(true);
    try {
      // 1) create the Product MCP (code-mode enabled, not live yet)
      const created = await apiFetch('/api/product-mcps', {
        method: 'POST',
        body: JSON.stringify({ name: name.trim(), sourceSlug: slug, baseUrl: baseUrl || null }),
      });
      const id = created.id;
      const setupErrors = [];
      // 2) upload docs if provided
      if (docsMode === 'upload' && docsText.trim()) {
        try {
          await apiFetch(`/api/product-mcps/${id}/api-docs`, {
            method: 'POST',
            body: JSON.stringify({ openapi: docsText, source: 'manual_upload' }),
          });
        } catch (error) {
          setupErrors.push({ key: 'apiDocs', label: 'API docs upload', message: error.message });
          toast.show({ tone: 'warn', title: 'Created, but docs upload failed', description: error.message });
        }
      }
      // 3) connect GitHub if selected
      if (docsMode === 'github') {
        try {
          const githubResult = await apiFetch(`/api/product-mcps/${id}/github/connect`, {
            method: 'POST',
            body: JSON.stringify({ ...pmcpCleanGithubForm(githubForm), openapiMode: PMCP_GITHUB_OPENAPI_MODE, confirmedProductionRef: String(githubForm.productionRef || '').trim() }),
          });
          if (!pmcpGithubInitialSyncOk(githubResult?.initialSync?.status)) {
            setupErrors.push({
              key: 'github',
              label: 'GitHub docs sync',
              message: githubResult.initialSync.error || 'GitHub was connected, but the initial docs sync could not start.',
            });
          }
        } catch (error) {
          setupErrors.push({ key: 'github', label: 'GitHub', message: error.message });
          toast.show({ tone: 'warn', title: 'Created, but GitHub setup failed', description: error.message });
        }
      }
      // 4) configure authentication
      try {
        await apiFetch(`/api/product-mcps/${id}/authentication`, {
          method: 'PATCH',
          body: JSON.stringify(pmcpAuthBodyFromForm(authForm)),
        });
      } catch (error) {
        setupErrors.push({ key: 'authentication', label: 'API Connection', message: error.message });
        toast.show({ tone: 'warn', title: 'Created, but auth setup failed', description: error.message });
      }
      toast.show({ tone: setupErrors.length > 0 ? 'warn' : 'ok', title: setupErrors.length > 0 ? `${name.trim()} created · setup incomplete` : `${name.trim()} created` });
      clearPmcpCreateDraft();
      onCreated(id, setupErrors.length > 0 ? { errors: setupErrors } : null);
    } catch (error) {
      toast.show({ tone: 'bad', title: 'Could not create', description: error.message });
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <div className="pmcp-modal-backdrop" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="pmcp-modal" role="dialog" aria-modal="true">
        <div className="pmcp-modal-head">
          <div style={{ flex: 1 }}><div className="pmcp-modal-title">New Product MCP</div></div>
          <button className="pmcp-icon-btn" onClick={onClose} aria-label="Close"><Icon name="x" size={14} /></button>
        </div>
        <div style={{ padding: '16px 24px', borderBottom: '1px solid var(--border)' }}>
          <PmcpStepper current={step} />
        </div>
        <div className="pmcp-modal-body">
          {step === 1 && (
            <div>
              <div className="pmcp-field">
                <label className="pmcp-field-label">Product MCP name</label>
                <input className="pmcp-field-input" autoFocus placeholder="e.g. Payments API" value={name}
                  onChange={(e) => { setName(e.target.value); if (!slugTouched) setSlug(pmcpSlugify(e.target.value)); }} />
              </div>
              <div className="pmcp-field">
                <label className="pmcp-field-label">Slug</label>
                <input className="pmcp-field-input mono" placeholder="payments" value={slug}
                  onChange={(e) => { setSlug(pmcpSlugify(e.target.value)); setSlugTouched(true); }} />
              </div>
              <div className="pmcp-field" style={{ marginBottom: 0 }}>
                <label className="pmcp-field-label">Generated domain</label>
                <div className="pmcp-field-prefix">
                  <span className="pre">{baseDomain}/</span>
                  <input value={slug} readOnly style={{ color: 'var(--text-3)' }} />
                </div>
                <div className="pmcp-field-hint">Custom domain (<b>BYOD</b>) is coming soon — you'll be able to point your own host here.</div>
              </div>
            </div>
          )}
          {step === 2 && (
            <div>
              {githubSetup?.setupError && (
                <div className="pmcp-callout danger" style={{ marginBottom: 14 }}>
                  <Icon name="alert" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
                  <div>GitHub install did not finish. Try installing the GitHub App again.</div>
                </div>
              )}
              <div className="pmcp-field-label">API docs source</div>
              <div style={{ marginBottom: 14 }}>
                <div className={`pmcp-radio-card ${docsMode === 'github' ? 'sel' : ''}`} onClick={() => setDocsMode('github')}>
                  <span className="pmcp-radio-dot" />
                  <div>
                    <div className="pmcp-radio-title">Connect GitHub repo</div>
                    <div className="pmcp-radio-sub">Install the GitHub App, choose the repo, and keep OpenAPI docs in sync from commits.</div>
                  </div>
                  <PmcpGithubMark />
                </div>
                <div className={`pmcp-radio-card ${docsMode === 'upload' ? 'sel' : ''}`} onClick={() => setDocsMode('upload')}>
                  <span className="pmcp-radio-dot" />
                  <div>
                    <div className="pmcp-radio-title">Upload OpenAPI file</div>
                    <div className="pmcp-radio-sub">Paste or choose a JSON/YAML OpenAPI file. We'll validate it immediately.</div>
                  </div>
                  <div />
                </div>
                <div className={`pmcp-radio-card ${docsMode === 'ci' ? 'sel' : ''}`} onClick={() => setDocsMode('ci')}>
                  <span className="pmcp-radio-dot" />
                  <div>
                    <div className="pmcp-radio-title">Use CI upload endpoint</div>
                    <div className="pmcp-radio-sub">Create an endpoint and generate a scoped upload key after creation.</div>
                  </div>
                  <div />
                </div>
              </div>
              {docsMode === 'github' ? (
                <div className="pmcp-card-plain" style={{ padding: 0 }}>
                  <div className={`pmcp-callout ${githubForm.githubInstallationId ? 'ok' : 'info'}`} style={{ border: 0, borderBottom: '1px solid var(--border)' }}>
                    {githubForm.githubInstallationId ? <Icon name="check" size={14} style={{ flexShrink: 0, marginTop: 1 }} /> : <PmcpGithubMark />}
                    <div>
                      {githubForm.githubInstallationId
                        ? 'GitHub App installed. Enter the repository and docs settings to finish creating this Product MCP.'
                        : 'Install the GitHub App now. You will return to this wizard to finish the repo settings before the Product MCP is created.'}
                    </div>
                  </div>
                  <div className="pmcp-github-form">
                    <button
                      type="button"
                      className={`pmcp-github-install-cta ${githubForm.githubInstallationId ? 'is-installed' : ''}`}
                      disabled={githubInstalling}
                      onClick={installGithub}>
                      {githubInstalling ? <LoadingSpinner size="sm" label="Opening GitHub" /> : <PmcpGithubMark />}
                      <span className="pmcp-github-install-copy">
                        <span>{githubForm.githubInstallationId ? 'GitHub App installed' : 'Install GitHub App'}</span>
                        <span>{githubForm.githubInstallationId ? 'Change repository access if needed.' : 'Required before you can continue with GitHub.'}</span>
                      </span>
                      <span className="pmcp-github-install-state">{githubForm.githubInstallationId ? 'Installed' : 'Required'}</span>
                    </button>
                    <div className="pmcp-form-grid">
                      <label className="pmcp-field">
                        <span className="pmcp-field-label">Repository</span>
                        <input className="pmcp-field-input mono" required placeholder="owner/repo" value={githubForm.repositoryFullName} onChange={(e) => setGithubForm({ ...githubForm, repositoryFullName: e.target.value })} />
                      </label>
                      <label className="pmcp-field">
                        <span className="pmcp-field-label">Production ref</span>
                        <input className="pmcp-field-input mono" required value={githubForm.productionRef} onChange={(e) => setGithubForm({ ...githubForm, productionRef: e.target.value })} />
                      </label>
                    </div>
                    <label className="pmcp-field">
                      <span className="pmcp-field-label">OpenAPI path</span>
                      <input className="pmcp-field-input mono" required value={githubForm.openapiPath} onChange={(e) => setGithubForm({ ...githubForm, openapiPath: pmcpCleanOpenApiPath(e.target.value) })} placeholder=".armature/openapi.json" />
                      <div className="pmcp-field-hint">Explicit file path to the committed bundled OpenAPI JSON/YAML file in this repository.</div>
                    </label>
                    <label className="pmcp-toggle-line">
                      <input type="checkbox" checked={githubForm.prCommentsEnabled} onChange={(e) => setGithubForm({ ...githubForm, prCommentsEnabled: e.target.checked })} />
                      Update pull request comments
                    </label>
                    <label className="pmcp-toggle-line">
                      <input type="checkbox" checked={githubForm.confirmProductionDeploy} onChange={(e) => setGithubForm({ ...githubForm, confirmProductionDeploy: e.target.checked })} />
                      Deploy Product MCP docs automatically when {githubForm.productionRef || 'the production ref'} receives a push
                    </label>
                    {githubError && <div className="pmcp-field-hint">{githubError}</div>}
                  </div>
                </div>
              ) : docsMode === 'upload' ? (
                <>
                  <div className="pmcp-field">
                    <label className="pmcp-field-label">OpenAPI file</label>
                    <input
                      className="pmcp-field-input"
                      type="file"
                      accept=".json,.yaml,.yml,application/json,application/yaml,text/yaml,text/x-yaml"
                      onChange={chooseDocsFile} />
                    <div className="pmcp-field-hint">{docsFileName ? `Loaded ${docsFileName}` : 'Choose a .json, .yaml, or .yml file, or paste below.'}</div>
                  </div>
                  <div className="pmcp-field" style={{ marginBottom: 0 }}>
                    <label className="pmcp-field-label">OpenAPI document</label>
                    <textarea className="pmcp-field-input mono" style={{ minHeight: 170, resize: 'vertical', width: '100%' }}
                      placeholder={'{ "openapi": "3.1.0", "info": {...}, "paths": {...} }'}
                      value={docsText} onChange={(e) => { setDocsText(e.target.value); setDocsFileName(''); }} />
                  </div>
                </>
              ) : (
                <div className="pmcp-callout info">
                  <Icon name="info" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
                  <div>Create this Product MCP first, then generate a scoped upload key from its API docs section. CI will upload OpenAPI docs to Armature with that key.</div>
                </div>
              )}
            </div>
          )}
          {step === 3 && (
            <div>
              <div className="pmcp-sec-intro" style={{ marginBottom: 16 }}>
                <strong className="pmcp-security-kicker">Security step</strong>: validate caller credentials before tools are exposed. Armature forwards that same credential to your API.
              </div>
              <div className="pmcp-field">
                <label className="pmcp-field-label">Base API URL</label>
                <input className="pmcp-field-input mono" placeholder="https://api.example.com/v1" value={baseUrl} onChange={(e) => setBaseUrl(e.target.value)} />
              </div>
              <PmcpAuthMethodForm form={authForm} setForm={setAuthForm} />
            </div>
          )}
          {step === 4 && (
            <div>
              <div className="pmcp-ready-list">
                <PmcpReviewRow ok={name.trim().length > 0} label="Name & domain" sub={`${baseDomain}/${slug}`} />
                <PmcpReviewRow
                  ok={docsMode === 'upload' ? docsText.trim().length > 0 : (docsMode === 'github' ? !githubError : true)}
                  label="API docs"
                  sub={docsMode === 'github'
                    ? `Will sync from GitHub · ${githubForm.repositoryFullName || 'repository required'} · ${githubForm.productionRef || 'ref required'} · ${githubForm.openapiPath || 'path required'}`
                    : docsMode === 'upload'
                      ? (docsFileName ? `OpenAPI file · ${docsFileName}` : 'OpenAPI pasted')
                      : 'CI upload key will be created after setup'}
                  pendingLabel={docsMode === 'ci' ? 'Later' : 'Required'} />
                <PmcpReviewRow ok label="API Connection" sub={`${PmcpAuthLabel({ status: 'configured', ...pmcpAuthBodyFromForm(authForm) })} · ${authForm.validationMode === 'presence_only' ? 'presence only' : 'validation endpoint configured'}`} />
              </div>
              <div className="pmcp-callout info" style={{ marginTop: 14 }}>
                <Icon name="info" size={14} style={{ flexShrink: 0, marginTop: 1 }} />
                <div>GitHub docs will sync as soon as this Product MCP is created. The endpoint stays <b>Needs setup</b> until docs are generated, the API connection is validated, and the Armature domain is ready.</div>
              </div>
            </div>
          )}
        </div>
        <div className="pmcp-modal-foot">
          {step > 1
            ? <Button onClick={() => setStep(step - 1)}><Icon name="chevronLeft" size={13} />Back</Button>
            : <Button variant="ghost" onClick={onClose}>Cancel</Button>}
          <div className="pmcp-spacer" />
          <span className="pmcp-page-foot-note">Step {step} of 4</span>
          {step < 4
            ? (
                <PmcpDisabledAction reason={nextBlocker}>
                  <Button variant="primary" disabled={!canNext} onClick={() => setStep(step + 1)}>Next</Button>
                </PmcpDisabledAction>
              )
            : <Button variant="primary" loading={submitting} loadingLabel="Creating" onClick={submit}><Icon name="play" size={13} />Create endpoint</Button>}
        </div>
        {step < 4 && nextBlocker && (
          <div className="pmcp-modal-disabled-reason" role="status">
            <Icon name="alert" size={13} />{nextBlocker}
          </div>
        )}
      </div>
    </div>
  );
}

function PmcpReviewRow({ ok, label, sub, pendingLabel = 'Later' }) {
  return (
    <div className="pmcp-ready-row">
      <span className={`pmcp-ready-ico ${ok ? 'ready' : 'pending'}`}><Icon name={ok ? 'check' : 'circle'} size={16} /></span>
      <div>
        <div className="pmcp-ready-label">{label}</div>
        <div className="pmcp-ready-sub">{sub}</div>
      </div>
      <span className="pmcp-ready-state" style={{ color: ok ? 'var(--green)' : 'var(--text-3)' }}>{ok ? 'Configured' : pendingLabel}</span>
    </div>
  );
}

// ---- Page root -------------------------------------------------------------

function ProductMcpsPage({ navigate, queryString }) {
  const auth = useAuth();
  const list = useApiResource('/api/product-mcps', []);
  const params = new URLSearchParams(queryString || '');
  const selectedId = params.get('id');
  const githubSetup = {
    githubInstallationId: params.get('githubInstallationId') || '',
    githubInstallationGrant: params.get('githubInstallationGrant') || '',
    setupAction: params.get('githubSetupAction') || '',
    setupError: params.get('githubSetupError') || '',
  };
  const shouldOpenCreate = !selectedId && (
    params.get('create') === '1'
    || Boolean(githubSetup.githubInstallationId)
    || Boolean(githubSetup.setupError)
  );
  const [creating, setCreating] = usePmcpState(shouldOpenCreate);
  const [setupNotice, setSetupNotice] = usePmcpState(null);

  const orgSlug = auth?.me?.organization?.slug || 'your-org';
  const baseDomain = `mcp.${orgSlug}.armature.tech`;

  const open = (id) => { setSetupNotice(null); navigate(`/product-mcps?id=${id}`); };
  const back = () => { setSetupNotice(null); navigate('/product-mcps'); };
  const deleted = usePmcpCallback(async () => {
    setSetupNotice(null);
    await list.reload();
    navigate('/product-mcps');
  }, [list.reload, navigate]);
  const closeCreate = () => {
    clearPmcpCreateDraft();
    setCreating(false);
    if (params.get('create') || githubSetup.githubInstallationId || githubSetup.setupError) navigate('/product-mcps');
  };

  if (selectedId) {
    return (
      <div className="page-inner">
        <ProductMcpDetailView id={selectedId} onBack={back} onDeleted={deleted} setupNotice={setupNotice?.id === selectedId ? setupNotice : null} />
      </div>
    );
  }

  return (
    <div className="page-inner">
      <div className="page-header">
        <div>
          <h1 className="page-title">Product MCPs</h1>
          <div className="page-subtitle">Turn your product API into an MCP endpoint your customers can use.</div>
        </div>
        <div className="page-actions">
          <Button variant="primary" onClick={() => setCreating(true)}><Icon name="plus" size={13} />New Product MCP</Button>
        </div>
      </div>
      <ProductMcpListView
        rows={list.data?.rows}
        loading={list.loading}
        error={list.error}
        onOpen={open}
        onCreate={() => setCreating(true)} />
      {creating && (
        <ProductMcpCreateModal
          baseDomain={baseDomain}
          githubSetup={githubSetup}
          onClose={closeCreate}
          onCreated={(id, notice) => {
            setCreating(false);
            setSetupNotice(notice ? { id, ...notice } : null);
            list.reload();
            navigate(`/product-mcps?id=${id}`);
          }} />
      )}
    </div>
  );
}

window.ProductMcpsPage = ProductMcpsPage;
