/* Harbour execution timeline — direct port of the product code. */

const SECTION_MARKER_X = 18;
const ITEM_MARKER_X = 42;
const ITEM_MARKER_INDENT = 16;
const MAX_NESTING_DEPTH = 3;
const MARKER_TOP = 14;
const SEGMENT_LENGTH = 120;
const PIXELS_PER_SECOND = 110;
const MIN_ANIMATION_DURATION_MS = 1400;

const SAMPLE_PROJECTION = {
  turnId: "turn-42",
  status: "running",
  sections: [
    {
      key: "intake",
      label: "Turn · 42 intake",
      description: "Request arrives. Harbour hydrates memory and fans out work to the fleet.",
      emptyMessage: "No inbound events yet.",
      items: [
        {
          id: "req-1",
          kind: "turn",
          depth: 0,
          label: "request.received",
          supportingText: "refactor wsTransport error recovery + add replay tests",
          badges: ["user", "ws"],
          status: "running",
          timestamp: "2026-04-19T14:22:03-07:00",
          turnId: "turn-42",
          selected: false,
          detailItems: [
            { label: "Route", value: "wsServer → OrchestrationEngine" },
            { label: "Contract", value: "packages/contracts/src/ws.ts · typed request" },
          ],
          expanded: false,
        },
        {
          id: "hy-1",
          kind: "receipt",
          depth: 1,
          label: "memory.hydrated",
          supportingText: "3 blocks retrieved · saved 14.2k tokens",
          badges: ["hydrate"],
          status: "completed",
          timestamp: "2026-04-19T14:22:04-07:00",
          receiptId: "rcpt-hy-1",
          selected: false,
          detailItems: [
            { label: "Blocks", value: "0x7a2f9c · 0x12b801 · 0xe9041c" },
            { label: "Source", value: "prior checkpoint (turn · 41)" },
          ],
          expanded: false,
        },
        {
          id: "fan-1",
          kind: "receipt",
          depth: 1,
          label: "fanout → 3 agents",
          supportingText: "claude · codex · patch · opencode · verify",
          badges: ["parallel"],
          status: "running",
          timestamp: "2026-04-19T14:22:05-07:00",
          receiptId: "rcpt-fan-1",
          selected: false,
          detailItems: [
            { label: "Worker", value: "DrainableWorker · provider.runtime.ingest" },
            { label: "Parallelism", value: "3 providers, ordered commit on return" },
          ],
          expanded: false,
        },
      ],
    },
    {
      key: "work",
      label: "Work · in-flight",
      description: "Three agents execute in parallel, each writing receipts to shared memory.",
      emptyMessage: "No active work.",
      items: [
        {
          id: "claude-1",
          kind: "receipt",
          depth: 1,
          label: "claude · plan.drafted",
          supportingText: "7 steps · 2 risk flags",
          badges: ["plan"],
          status: "completed",
          timestamp: "2026-04-19T14:22:11-07:00",
          receiptId: "rcpt-claude-1",
          selected: true,
          detailItems: [
            { label: "Block", value: "0x7a2f9c · plan/refactor" },
            { label: "Reused", value: "retrieved + revised from prior ckpt" },
          ],
          expanded: true,
        },
        {
          id: "codex-1",
          kind: "receipt",
          depth: 1,
          label: "codex · patch.applied",
          supportingText: "4 files · 142 ++ / 38 --",
          badges: ["diff"],
          status: "running",
          timestamp: "2026-04-19T14:22:18-07:00",
          receiptId: "rcpt-codex-1",
          selected: false,
          detailItems: [
            { label: "Block", value: "0xc3fd81 · diff/ordered-push" },
            { label: "Write", value: "new block written to memory" },
          ],
          expanded: false,
        },
        {
          id: "opencode-1",
          kind: "receipt",
          depth: 1,
          label: "opencode · verify",
          supportingText: "unit 38 ✓ · integ 12 ✓ · snapshot 4 ✓",
          badges: ["tests"],
          status: "completed",
          timestamp: "2026-04-19T14:22:24-07:00",
          receiptId: "rcpt-opencode-1",
          selected: false,
          detailItems: [
            { label: "Ran", value: "pnpm test · apps/server" },
            { label: "Receipt", value: "RuntimeReceiptBus → verify.complete" },
          ],
          expanded: false,
        },
      ],
    },
    {
      key: "emit",
      label: "Emit · quiesced",
      description: "Harbour seals the receipts, signs the block, and returns quiescence.",
      emptyMessage: "Waiting on agent completion.",
      items: [
        {
          id: "seal-1",
          kind: "receipt",
          depth: 0,
          label: "receipts.signed",
          supportingText: "3 agents · 12 receipts · merkle 0xf1…c4b",
          badges: ["seal"],
          status: "completed",
          timestamp: "2026-04-19T14:22:26-07:00",
          receiptId: "rcpt-seal-1",
          selected: false,
          detailItems: [
            { label: "Merkle root", value: "0xf1a29b…c4b9" },
            { label: "Signed by", value: "CheckpointReactor · host=local" },
          ],
          expanded: false,
        },
        {
          id: "qui-1",
          kind: "summary",
          depth: 0,
          label: "quiesced",
          supportingText: "ready for turn · 43",
          badges: [],
          status: "completed",
          timestamp: "2026-04-19T14:22:26-07:00",
          selected: false,
          detailItems: [
            { label: "Signal", value: "runtime.receipt · turn.quiesced" },
            { label: "Projection", value: "orchestration.push → browser" },
          ],
          expanded: false,
        },
      ],
    },
  ],
};

function fmtTime(iso) {
  try {
    const d = new Date(iso);
    const time = new Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "2-digit" }).format(
      d,
    );
    const full = new Intl.DateTimeFormat(undefined, {
      month: "short",
      day: "numeric",
      hour: "numeric",
      minute: "2-digit",
    }).format(d);
    return { time, full };
  } catch {
    return { time: iso, full: iso };
  }
}

function buildSnakePath(points) {
  const first = points[0];
  if (!first) return "";
  let d = `M ${first.x} ${first.y}`;
  for (let i = 1; i < points.length; i++) {
    const prev = points[i - 1];
    const curr = points[i];
    if (!prev || !curr) continue;
    const dx = curr.x - prev.x;
    const dy = curr.y - prev.y;
    if (Math.abs(dx) < 0.5) {
      // vertical
      d += ` L ${curr.x} ${curr.y}`;
    } else {
      // snake: half vertical to midpoint, curve sideways, continue vertical
      const midY = prev.y + dy * 0.6;
      const curveR = 10;
      const cx1 = prev.x;
      const cy1 = midY - curveR * Math.sign(dx) * 0.2;
      const cx2 = curr.x;
      const cy2 = midY + curveR * Math.sign(dx) * 0.2;
      d += ` L ${prev.x} ${midY - 2}`;
      d += ` C ${cx1} ${midY + curveR}, ${cx2} ${midY - curveR}, ${curr.x} ${midY + 2}`;
      d += ` L ${curr.x} ${curr.y}`;
    }
  }
  return d;
}

function OrchestrationSection() {
  // Mutable projection state so rows can expand, collapse, and be selected.
  const [projection, setProjection] = React.useState(SAMPLE_PROJECTION);

  const onToggle = React.useCallback((entryId) => {
    setProjection((prev) => ({
      ...prev,
      sections: prev.sections.map((s) => ({
        ...s,
        items: s.items.map((it) => {
          const isTarget = it.id === entryId;
          if (!isTarget) return { ...it, selected: false };
          const nextExpanded = !it.expanded;
          return { ...it, expanded: nextExpanded, selected: nextExpanded };
        }),
      })),
    }));
  }, []);

  return (
    <section className="block" id="timeline" data-screen-label="orchestration">
      <div className="wrap">
        <div style={{ display: "grid", gridTemplateColumns: "1fr", gap: 40 }}>
          <div style={{ maxWidth: 820, margin: "0 auto", textAlign: "center" }}>
            <div className="eyebrow">§ Orchestration layer</div>
            <h2 className="section-h" style={{ marginTop: 16 }}>
              One turn. <em>Every agent.</em>
            </h2>
            <p className="lede" style={{ marginTop: 18 }}>
              A request arrives over WebSocket.{" "}
              <code style={{ fontFamily: "var(--mono)", fontSize: 13, color: "var(--chrome-1)" }}>
                OrchestrationEngine
              </code>{" "}
              normalizes runtime events, queue&#8209;backed workers drain commands and checkpoints,
              and{" "}
              <code style={{ fontFamily: "var(--mono)", fontSize: 13, color: "var(--chrome-1)" }}>
                RuntimeReceiptBus
              </code>{" "}
              emits a typed receipt on quiescence. Click any row to inspect the receipt.
            </p>
          </div>

          <HarbourExecutionTimeline projection={projection} onToggle={onToggle} />
        </div>
      </div>
    </section>
  );
}

function HarbourExecutionTimeline({ projection, onToggle }) {
  const containerRef = React.useRef(null);
  const svgRef = React.useRef(null);
  const animPathRef = React.useRef(null);
  const gradRef = React.useRef(null);
  const [geom, setGeom] = React.useState({ d: "", width: 72, height: 200 });
  const [pathLen, setPathLen] = React.useState(0);
  const gradId = React.useId().replace(/:/g, "");

  React.useLayoutEffect(() => {
    const container = containerRef.current;
    if (!container) return;
    const measure = () => {
      const cRect = container.getBoundingClientRect();
      const markers = container.querySelectorAll('[data-harbour-timeline-marker="true"]');
      const points = Array.from(markers).map((m) => {
        const r = m.getBoundingClientRect();
        return {
          id: m.dataset.markerId,
          x: (parseFloat(m.dataset.markerX) || 0) + 4, // center of 8px dot
          y: r.top - cRect.top + r.height / 2,
        };
      });
      if (!points.length) return;
      const d = buildSnakePath(points);
      const lastPoint = points[points.length - 1];
      const height = Math.ceil((lastPoint && lastPoint.y) || 0) + 32;
      const width = Math.max(
        72,
        Math.ceil(points.reduce((m, p) => Math.max(m, p.x), ITEM_MARKER_X) + 40),
      );
      setGeom({ d, width, height });
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(container);
    container
      .querySelectorAll('[data-harbour-timeline-marker="true"]')
      .forEach((m) => ro.observe(m));
    window.addEventListener("resize", measure);
    return () => {
      ro.disconnect();
      window.removeEventListener("resize", measure);
    };
  }, [projection]);

  React.useLayoutEffect(() => {
    if (!geom.d || !animPathRef.current) {
      setPathLen(0);
      return;
    }
    try {
      setPathLen(animPathRef.current.getTotalLength());
    } catch {
      setPathLen(0);
    }
  }, [geom.d]);

  const dashArray = React.useMemo(() => {
    if (pathLen <= 0) return undefined;
    const tail = Math.max(pathLen - SEGMENT_LENGTH, SEGMENT_LENGTH + 1);
    return `${SEGMENT_LENGTH} ${tail}`;
  }, [pathLen]);

  React.useEffect(() => {
    if (pathLen <= 0) return;
    const path = animPathRef.current;
    const grad = gradRef.current;
    if (!path || !grad) return;
    const durationMs = Math.max((pathLen / PIXELS_PER_SECOND) * 1000, MIN_ANIMATION_DURATION_MS);
    const halfSeg = SEGMENT_LENGTH / 2;
    const travel = pathLen + SEGMENT_LENGTH;
    let raf = 0,
      start = 0;
    const step = (now) => {
      if (!start) start = now;
      const t = ((now - start) % durationMs) / durationMs;
      const cursor = t * travel - SEGMENT_LENGTH;
      path.style.strokeDashoffset = `${-cursor}`;
      const sampleAt = Math.min(Math.max(cursor + halfSeg, 0), pathLen);
      try {
        const pt = path.getPointAtLength(sampleAt);
        grad.setAttribute("gradientTransform", `translate(0 ${pt.y - halfSeg})`);
      } catch {}
      raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [pathLen]);

  return (
    <div className="ex-shell" ref={containerRef} data-testid="harbour-execution-timeline">
      <div className="ex-head">
        <div className="ex-head-l">
          <div className="ex-title">Execution timeline</div>
          <div className="ex-turn-chip">turn · 42</div>
        </div>
      </div>

      <div className="ex-body">
        {geom.d ? (
          <svg
            ref={svgRef}
            className="ex-svg"
            aria-hidden="true"
            style={{
              width: `${geom.width}px`,
              height: `${geom.height}px`,
              position: "absolute",
              left: 0,
              top: 0,
              pointerEvents: "none",
            }}
            viewBox={`0 0 ${geom.width} ${geom.height}`}
          >
            <defs>
              <linearGradient
                ref={gradRef}
                id={`ex-seg-${gradId}`}
                x1="0"
                y1="0"
                x2="0"
                y2={SEGMENT_LENGTH}
                gradientUnits="userSpaceOnUse"
                gradientTransform="translate(0 0)"
              >
                <stop offset="0" stopColor="rgba(253, 186, 116, 0)" />
                <stop offset="0.4" stopColor="rgba(253, 186, 116, 0.85)" />
                <stop offset="0.5" stopColor="rgba(255, 236, 208, 1)" />
                <stop offset="0.6" stopColor="rgba(253, 186, 116, 0.85)" />
                <stop offset="1" stopColor="rgba(253, 186, 116, 0)" />
              </linearGradient>
            </defs>
            <path
              d={geom.d}
              fill="none"
              stroke="rgba(255,255,255,0.08)"
              strokeWidth="1.25"
              strokeLinecap="round"
            />
            <path
              ref={animPathRef}
              d={geom.d}
              fill="none"
              stroke={`url(#ex-seg-${gradId})`}
              strokeWidth="2"
              className="seg"
              style={{
                strokeDasharray: dashArray,
                strokeDashoffset: SEGMENT_LENGTH,
                filter:
                  "drop-shadow(0 0 4px rgba(253,186,116,0.75)) drop-shadow(0 0 10px rgba(253,186,116,0.4))",
              }}
            />
          </svg>
        ) : null}

        <div className="ex-sections" style={{ position: "relative", zIndex: 3 }}>
          {projection.sections.map((section) => (
            <TimelineSection key={section.key} section={section} onToggle={onToggle} />
          ))}
        </div>
      </div>
    </div>
  );
}

function TimelineSection({ section, onToggle }) {
  return (
    <section className="ex-section">
      <div className="ex-section-head">
        <span
          aria-hidden="true"
          data-harbour-timeline-marker="true"
          data-marker-id={`section:${section.key}`}
          data-marker-x={SECTION_MARKER_X}
          className="ex-section-dot"
          style={{ left: `${SECTION_MARKER_X}px`, top: `${MARKER_TOP}px` }}
        />
        <div className="ex-section-head-pad">
          <div className="ex-section-label">{section.label}</div>
          <p className="ex-section-desc">{section.description}</p>
        </div>
      </div>
      <div className="ex-items">
        {section.items.length === 0 ? (
          <div className="ex-empty">{section.emptyMessage}</div>
        ) : (
          section.items.map((entry) => (
            <TimelineEntry key={entry.id} entry={entry} onToggle={onToggle} />
          ))
        )}
      </div>
    </section>
  );
}

function TimelineEntry({ entry, onToggle }) {
  const markerX = ITEM_MARKER_X + Math.min(entry.depth, MAX_NESTING_DEPTH) * ITEM_MARKER_INDENT;
  const contentLeft = markerX + 20;
  const interactive = true; // every row is clickable for expand/collapse
  const dotCls = `ex-dot${entry.selected ? " selected" : entry.kind === "summary" ? " summary" : ""}`;
  const cardCls = `ex-card${entry.selected ? " selected" : ""}`;
  const Component = "button";

  return (
    <div style={{ position: "relative" }}>
      <span
        aria-hidden="true"
        data-harbour-timeline-marker="true"
        data-marker-id={entry.id}
        data-marker-x={markerX}
        className={dotCls}
        style={{ left: `${markerX}px`, top: `${MARKER_TOP + 6}px` }}
      />
      <Component
        className={cardCls}
        style={{ "--ex-card-left": `${contentLeft}px` }}
        data-selected={entry.selected ? "true" : "false"}
        type="button"
        aria-expanded={entry.expanded ? "true" : "false"}
        onClick={() => onToggle && onToggle(entry.id)}
      >
        <div className="ex-row">
          <div style={{ minWidth: 0 }}>
            <div
              style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: "4px 10px" }}
            >
              <span className="ex-label">{entry.label}</span>
              {entry.badges && entry.badges.length ? (
                <span className="ex-badges">
                  {entry.badges.map((b) => (
                    <span key={b} className="ex-chip">
                      {b}
                    </span>
                  ))}
                </span>
              ) : null}
            </div>
            {entry.supportingText ? <div className="ex-support">{entry.supportingText}</div> : null}
            {entry.body ? <div className="ex-body-text">{entry.body}</div> : null}
          </div>
          <div className="ex-meta">
            {entry.status ? <StatusChip status={entry.status} /> : null}
            {entry.timestamp ? <Stamp iso={entry.timestamp} /> : null}
          </div>
        </div>
        {entry.expanded && entry.detailItems && entry.detailItems.length > 0 ? (
          <div className="ex-expand">
            {entry.detailItems.map((d) => (
              <div key={`${entry.id}-${d.label}`} style={{ display: "grid", gap: 4 }}>
                <div className="ex-expand-k">{d.label}</div>
                <div className="ex-expand-v">{d.value}</div>
              </div>
            ))}
          </div>
        ) : null}
      </Component>
    </div>
  );
}

function StatusChip({ status }) {
  const norm = status.toLowerCase();
  return (
    <span className={`ex-status-chip ${norm}`}>
      <span aria-hidden="true" className="d" />
      {status}
    </span>
  );
}

function Stamp({ iso }) {
  const { time, full } = fmtTime(iso);
  return (
    <span className="ex-stamp" title={full}>
      {time}
    </span>
  );
}

Object.assign(window, { OrchestrationSection });
