// LaunchStepper — the Launch progress ladder (Slice 4). Composite, pure render:
// one row per launch step, driven entirely by the `steps` array the page polls in.
// It NEVER fetches — the LaunchPage owns the 1.5s `getLaunch` poll and hands the
// steps + an `onRetry` callback down.
//
// Props:
//   steps    — LaunchStep[] (server shape): { id, status, startedAt?, endedAt?,
//              log: string[], result? }; status ∈ pending|running|succeeded|failed
//   onRetry  — (stepId) => void | null; the [Retry from this step] control renders
//              ONLY on the FIRST failed step (resume-forward — there is no rollback)
//
// Per row: a status icon (pending neutral ring · running Spinner · succeeded emerald
// CheckCircle · failed red AlertCircle), the operator label, the duration once the
// step has ended, and its log — the last 3 lines while healthy, the FULL log inside
// a red panel once failed.

// Step id → operator label. Strings are load-bearing (the brief pins them exactly).
const LAUNCH_STEP_LABELS = {
  pm_apply: 'Write PM board',
  al_instantiate: 'Create Assembly Line project',
  github_repo: 'Create GitHub repository',
  railway_connect: 'Connect Railway project',
  webhooks: 'Register webhooks',
  verify: 'Verify launch',
};

// Spinner — the chat-panel.jsx:34 pattern (that Spinner is file-local, not on
// window, so we replicate the two-line markup here).
const LS_Spinner = () => (
  <span className="inline-block w-3.5 h-3.5 border-2 border-neutral-300 border-t-neutral-600 rounded-full animate-spin" />
);

// Human duration between startedAt/endedAt; null until the step has ended.
function LS_duration(step) {
  if (!step || !step.startedAt || !step.endedAt) return null;
  const ms = new Date(step.endedAt).getTime() - new Date(step.startedAt).getTime();
  if (!Number.isFinite(ms) || ms < 0) return null;
  if (ms < 1000) return `${ms}ms`;
  const s = ms / 1000;
  if (s < 60) return `${s.toFixed(1)}s`;
  const m = Math.floor(s / 60);
  const rem = Math.round(s % 60);
  return `${m}m ${rem}s`;
}

const LS_StatusIcon = ({ status }) => {
  if (status === 'running') return <LS_Spinner />;
  if (status === 'succeeded') return <window.CheckCircle size={18} className="text-emerald-500" />;
  if (status === 'failed') return <window.AlertCircle size={18} className="text-red-500" />;
  return <span className="inline-block w-3.5 h-3.5 rounded-full border-2 border-neutral-300" />; // pending
};

const LaunchStepper = ({ steps, onRetry }) => {
  const list = Array.isArray(steps) ? steps : [];
  const firstFailed = (window.LaunchModel && window.LaunchModel.firstFailedStep(list)) || null;

  if (list.length === 0) {
    return <div className="text-sm text-neutral-400 italic">No launch steps yet.</div>;
  }

  return (
    <ol className="space-y-0">
      {list.map((step, i) => {
        const label = LAUNCH_STEP_LABELS[step.id] || step.id;
        const dur = LS_duration(step);
        const failed = step.status === 'failed';
        const isFirstFailed = failed && firstFailed && firstFailed.id === step.id;
        const log = Array.isArray(step.log) ? step.log : [];
        const shownLog = failed ? log : log.slice(-3);
        const last = i === list.length - 1;
        return (
          <li key={step.id || i} className="flex gap-3">
            {/* icon rail + connector */}
            <div className="flex flex-col items-center">
              <div className="w-6 h-6 flex items-center justify-center shrink-0">
                <LS_StatusIcon status={step.status} />
              </div>
              {!last && <div className="w-px flex-1 min-h-[1rem] bg-neutral-200 my-1" />}
            </div>

            {/* label + duration + log */}
            <div className="flex-1 min-w-0 pb-5">
              <div className="flex items-center gap-2 flex-wrap">
                <span className={`font-semibold ${failed ? 'text-red-700' : 'text-neutral-800'}`}>{label}</span>
                {step.status === 'running' && <span className="text-xs text-indigo-500 font-medium">running…</span>}
                {dur && <span className="text-xs text-neutral-400">{dur}</span>}
              </div>

              {failed ? (
                <div className="mt-2 rounded-lg border border-red-200 bg-red-50 p-3">
                  {shownLog.length > 0 ? (
                    <pre className="font-mono text-xs text-red-700 whitespace-pre-wrap break-words">{shownLog.join('\n')}</pre>
                  ) : (
                    <div className="text-xs text-red-700 font-medium">This step failed.</div>
                  )}
                  {isFirstFailed && onRetry && (
                    <button
                      type="button"
                      onClick={() => onRetry(step.id)}
                      className="mt-3 inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold rounded-lg bg-red-600 text-white hover:bg-red-700 active:scale-95"
                    >
                      <window.RefreshCw size={13} />
                      Retry from this step
                    </button>
                  )}
                </div>
              ) : (
                shownLog.length > 0 && (
                  <pre className="mt-1 font-mono text-xs text-neutral-500 whitespace-pre-wrap break-words">{shownLog.join('\n')}</pre>
                )
              )}
            </div>
          </li>
        );
      })}
    </ol>
  );
};

if (typeof window !== 'undefined') {
  window.LaunchStepper = LaunchStepper;
  window.LAUNCH_STEP_LABELS = LAUNCH_STEP_LABELS;
}
