'use client';

/**
 * SchedulesView — Forge web Schedules tab.
 *
 * Mirrors the JobsView shape (list + expand + action bar) but radically
 * simpler: Schedule = pipeline + input + trigger, no concurrency/dedup
 * surface. The 4-state model (idle / running / last_failed / paused)
 * drives all visible state.
 *
 * Design reference:
 *   forge-browser-extension/docs/design/design_handoff_forge_schedules/
 */

import { useCallback, useEffect, useState } from 'react';
import ScheduleCreateModal from './ScheduleCreateModal';
import ScheduleQuickCreate from './ScheduleQuickCreate';

interface Schedule {
  id: string;
  name: string;
  enabled: boolean;
  body_kind: 'pipeline' | 'prompt';
  body_ref: string;
  input: Record<string, unknown>;
  skills: string[];
  action_kind: 'none' | 'chat' | 'email' | 'telegram';
  action_config: Record<string, unknown>;
  action_skip_on_empty: boolean;
  schedule_kind: 'period' | 'once' | 'cron' | 'manual';
  schedule_interval_minutes: number;
  schedule_at: string | null;
  schedule_cron: string | null;
  next_run_at: string | null;
  last_run_at: string | null;
  created_at: string;
  updated_at: string;
  inflight_count: number;
  last_status: 'done' | 'failed' | 'cancelled' | null;
  active_state: 'idle' | 'running' | 'last_failed' | 'paused';
}

interface ScheduleRun {
  id: string;
  schedule_id: string;
  target_id: string;
  trigger: 'schedule' | 'manual';
  status: 'started' | 'done' | 'failed' | 'cancelled';
  body_output: string | null;
  action_status: 'pending' | 'done' | 'failed' | 'skipped' | null;
  action_error: string | null;
  started_at: string;
  finished_at: string | null;
  error: string | null;
}

type Filter = 'all' | 'running' | 'last_failed' | 'idle' | 'paused';

interface Props {
  onViewPipeline?: (pipelineId: string) => void;
}

export default function SchedulesView({ onViewPipeline }: Props) {
  const [schedules, setSchedules] = useState<Schedule[] | null>(null);
  const [err, setErr] = useState('');
  const [expandedId, setExpandedId] = useState<string>('');
  const [runsBySchedule, setRunsBySchedule] = useState<Record<string, ScheduleRun[]>>({});
  const [filter, setFilter] = useState<Filter>('all');
  const [showCreate, setShowCreate] = useState(false);
  const [showQuickCreate, setShowQuickCreate] = useState(false);
  const [editingSchedule, setEditingSchedule] = useState<Schedule | null>(null);

  const refresh = useCallback(async () => {
    try {
      const r = await fetch('/api/schedules');
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const j = await r.json();
      setSchedules(j.schedules || []);
      setErr('');
    } catch (e) {
      setErr(e instanceof Error ? e.message : String(e));
      setSchedules([]);
    }
  }, []);

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

  // Poll while one is expanded so state stays live.
  useEffect(() => {
    if (!expandedId) return;
    const id = expandedId;
    const t = setInterval(() => {
      void refresh();
      void loadRuns(id);
    }, 3000);
    return () => clearInterval(t);
  }, [expandedId, refresh]);

  async function loadRuns(id: string) {
    try {
      const r = await fetch(`/api/schedules/${encodeURIComponent(id)}/runs?limit=20`);
      const j = await r.json();
      setRunsBySchedule((prev) => ({ ...prev, [id]: j.runs || [] }));
    } catch {}
  }

  function expand(id: string) {
    if (expandedId === id) { setExpandedId(''); return; }
    setExpandedId(id);
    if (!runsBySchedule[id]) void loadRuns(id);
  }

  async function fireNow(id: string, opts?: { cancelInflight?: boolean }) {
    try {
      const qs = opts?.cancelInflight ? '?cancel_inflight=1' : '';
      const r = await fetch(`/api/schedules/${encodeURIComponent(id)}/run${qs}`, { method: 'POST' });
      if (!r.ok) {
        const j = await r.json().catch(() => ({}));
        setErr(j.error || `Fire failed (${r.status})`);
        return;
      }
      setTimeout(() => { void refresh(); void loadRuns(id); }, 300);
    } catch (e) {
      setErr(`Fire failed: ${e instanceof Error ? e.message : String(e)}`);
    }
  }

  async function cancelInflight(id: string) {
    if (!confirm('Cancel all in-flight pipelines for this schedule?')) return;
    try {
      await fetch(`/api/schedules/${encodeURIComponent(id)}/cancel`, { method: 'POST' });
      void refresh();
      void loadRuns(id);
    } catch (e) { setErr(`Cancel failed: ${e instanceof Error ? e.message : String(e)}`); }
  }

  async function stopSchedule(id: string) {
    if (!confirm('Pause AND cancel all running pipelines?')) return;
    try {
      await fetch(`/api/schedules/${encodeURIComponent(id)}/stop`, { method: 'POST' });
      void refresh();
      void loadRuns(id);
    } catch (e) { setErr(`Stop failed: ${e instanceof Error ? e.message : String(e)}`); }
  }

  async function togglePaused(s: Schedule) {
    try {
      await fetch(`/api/schedules/${encodeURIComponent(s.id)}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ enabled: !s.enabled }),
      });
      void refresh();
    } catch (e) { setErr(`Toggle failed: ${e instanceof Error ? e.message : String(e)}`); }
  }

  async function deleteSchedule(s: Schedule) {
    const cancelInflightPipes = s.inflight_count > 0
      && confirm(`This schedule has ${s.inflight_count} running pipeline${s.inflight_count === 1 ? '' : 's'}. Cancel them too?`);
    if (!confirm(`Delete schedule "${s.name}"?`)) return;
    try {
      const qs = cancelInflightPipes ? '?cancel_inflight=1' : '';
      await fetch(`/api/schedules/${encodeURIComponent(s.id)}${qs}`, { method: 'DELETE' });
      setExpandedId('');
      void refresh();
    } catch (e) { setErr(`Delete failed: ${e instanceof Error ? e.message : String(e)}`); }
  }

  // ─── Render ─────────────────────────────────────────────

  const counts = {
    all: schedules?.length ?? 0,
    running: (schedules ?? []).filter((s) => s.active_state === 'running').length,
    last_failed: (schedules ?? []).filter((s) => s.active_state === 'last_failed').length,
    idle: (schedules ?? []).filter((s) => s.active_state === 'idle').length,
    paused: (schedules ?? []).filter((s) => s.active_state === 'paused').length,
  };

  const visible = (schedules ?? []).filter((s) => filter === 'all' || s.active_state === filter);

  return (
    <div className="flex-1 flex flex-col min-h-0 overflow-auto p-4">
      <div className="flex items-center justify-between mb-3">
        <div>
          <div className="flex items-baseline gap-2">
            <h2 className="text-[14px] font-semibold text-[var(--text-primary)]">Schedules</h2>
            <span className="text-[10px] text-[var(--text-secondary)]">tick every 60s</span>
          </div>
          <p className="text-[10px] text-[var(--text-secondary)] mt-0.5 max-w-2xl">
            Schedule a pipeline with input + trigger. Pipeline runs everything; Schedule just decides when to fire.
            Jobs (old) still work in parallel; new automations should use Schedules.
          </p>
        </div>
        <div className="flex items-center gap-2">
          <button
            onClick={() => setShowQuickCreate(true)}
            className="text-[10px] px-3 py-1 border border-[var(--accent)] text-[var(--accent)] rounded hover:bg-[var(--accent)]/10"
            title="Describe in natural language; Forge parses + you confirm"
          >✨ Quick create</button>
          <button
            onClick={() => setShowCreate(true)}
            className="text-[10px] px-3 py-1 bg-[var(--accent)] text-[var(--bg-primary)] rounded font-semibold hover:opacity-90"
          >+ New schedule</button>
        </div>
      </div>

      {/* Filter tabs */}
      <div className="flex items-baseline border-b border-[var(--border)] mb-3">
        {(['all', 'running', 'last_failed', 'idle', 'paused'] as const).map((f) => (
          <button
            key={f}
            onClick={() => setFilter(f)}
            className={`text-[11px] px-2.5 py-1.5 -mb-px border-b-2 ${
              filter === f
                ? 'border-[var(--text-primary)] text-[var(--text-primary)] font-semibold'
                : 'border-transparent text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
            }`}
          >
            {f.replace('_', ' ')}
            <span className={`ml-1.5 text-[9px] font-mono px-1 rounded ${
              filter === f ? 'bg-[var(--bg-secondary)] text-[var(--text-secondary)]' : 'text-[var(--text-secondary)]/60'
            }`}>{counts[f]}</span>
          </button>
        ))}
      </div>

      {err && <div className="text-[11px] text-[var(--red)] bg-[var(--red)]/10 rounded p-2 mb-3">{err}</div>}

      {schedules == null ? (
        <div className="text-[11px] text-[var(--text-secondary)] text-center py-8">Loading…</div>
      ) : visible.length === 0 ? (
        <div className="text-[11px] text-[var(--text-secondary)] text-center py-8">
          {filter === 'all' ? '(no schedules — click "+ New schedule" to create one)' : `(no ${filter.replace('_', ' ')} schedules)`}
        </div>
      ) : (
        <div className="space-y-2">
          {visible.map((s) => (
            <ScheduleRow
              key={s.id}
              schedule={s}
              expanded={expandedId === s.id}
              runs={runsBySchedule[s.id]}
              onClick={() => expand(s.id)}
              onFire={(cancel) => fireNow(s.id, { cancelInflight: cancel })}
              onCancel={() => cancelInflight(s.id)}
              onStop={() => stopSchedule(s.id)}
              onTogglePaused={() => togglePaused(s)}
              onEdit={() => setEditingSchedule(s)}
              onDelete={() => deleteSchedule(s)}
              onViewPipeline={onViewPipeline}
            />
          ))}
        </div>
      )}

      {showCreate && (
        <ScheduleCreateModal
          onClose={() => setShowCreate(false)}
          onCreated={() => { setShowCreate(false); void refresh(); }}
        />
      )}
      {showQuickCreate && (
        <ScheduleQuickCreate
          onClose={() => setShowQuickCreate(false)}
          onCreated={() => { setShowQuickCreate(false); void refresh(); }}
        />
      )}
      {editingSchedule && (
        <ScheduleCreateModal
          existing={editingSchedule}
          onClose={() => setEditingSchedule(null)}
          onCreated={() => { setEditingSchedule(null); void refresh(); }}
        />
      )}
    </div>
  );
}

// ─── Row ──────────────────────────────────────────────────

const STATE_META: Record<Schedule['active_state'], { label: string; color: string; bg: string }> = {
  running:     { label: 'running',     color: 'var(--yellow)', bg: 'rgba(212, 160, 23, 0.15)' },
  last_failed: { label: 'last failed', color: 'var(--red)',    bg: 'rgba(168, 51, 31, 0.15)' },
  idle:        { label: 'idle',        color: 'var(--green)',  bg: 'rgba(46, 107, 77, 0.15)' },
  paused:      { label: 'paused',      color: 'var(--text-secondary)', bg: 'rgba(128, 126, 118, 0.15)' },
};

function ScheduleRow({
  schedule: s,
  expanded,
  runs,
  onClick,
  onFire,
  onCancel,
  onStop,
  onTogglePaused,
  onEdit,
  onDelete,
  onViewPipeline,
}: {
  schedule: Schedule;
  expanded: boolean;
  runs?: ScheduleRun[];
  onClick: () => void;
  onFire: (cancelInflight: boolean) => void;
  onCancel: () => void;
  onStop: () => void;
  onTogglePaused: () => void;
  onEdit: () => void;
  onDelete: () => void;
  onViewPipeline?: (pipelineId: string) => void;
}) {
  const meta = STATE_META[s.active_state];
  const busy = s.active_state === 'running';
  const inflight = s.inflight_count;

  return (
    <div className={`rounded border bg-[var(--bg-secondary)] ${expanded ? 'border-[var(--text-secondary)]' : 'border-[var(--border)]'}`}>
      <div onClick={onClick} className="px-3 py-2 cursor-pointer">
        <div className="flex items-baseline gap-2">
          <span className="text-[10px] text-[var(--text-secondary)] w-3">{expanded ? '▾' : '▸'}</span>
          <span
            className="text-[9px] px-1.5 py-0.5 rounded uppercase font-semibold tracking-wide"
            style={{ background: meta.bg, color: meta.color }}
          >
            {meta.label}
          </span>
          <span className={`text-[12px] font-semibold text-[var(--text-primary)] truncate flex-1 ${s.active_state === 'paused' && inflight === 0 ? 'line-through opacity-60' : ''}`}>
            {s.name}
          </span>
          {s.active_state === 'paused' && inflight > 0 && (
            <span className="text-[9px] px-1.5 py-0.5 rounded bg-[var(--yellow)]/20 text-[var(--yellow)]">
              {inflight} pipeline{inflight === 1 ? '' : 's'} still running
            </span>
          )}
        </div>
        <div className="text-[10px] text-[var(--text-secondary)] font-mono mt-1 truncate">
          ⚡ {s.body_ref} · {describeSchedule(s)}
          <span className="ml-2 text-[var(--text-secondary)]/60">{s.id}</span>
        </div>
        <div className="text-[10px] text-[var(--text-secondary)] mt-0.5">
          {s.next_run_at ? `next ${formatTime(s.next_run_at)}` : (s.schedule_kind === 'manual' ? 'manual only' : 'paused')}
          {' · '}
          {s.last_run_at ? `last ${formatTime(s.last_run_at)}` : 'never run'}
          {s.last_status && (
            <span className={`ml-1.5 text-[9px] px-1 py-0 rounded ${
              s.last_status === 'done' ? 'bg-[var(--green)]/15 text-[var(--green)]' :
              s.last_status === 'failed' ? 'bg-[var(--red)]/15 text-[var(--red)]' :
              'bg-[var(--text-secondary)]/15 text-[var(--text-secondary)]'
            }`}>{s.last_status}</span>
          )}
        </div>
      </div>

      {expanded && (
        <div className="border-t border-[var(--border)] px-3 py-2 bg-[var(--bg-tertiary)]">
          {/* Action bar — conditional per state spec */}
          <div className="flex items-center gap-2 mb-3 flex-wrap">
            {/* Run now: always shown; disabled if running (unless cancel_inflight) */}
            <button
              onClick={(e) => { e.stopPropagation(); onFire(busy); }}
              disabled={busy && inflight > 0 && !confirm}
              className={`text-[10px] px-2 py-0.5 border rounded ${
                s.active_state === 'last_failed' || s.active_state === 'idle'
                  ? 'border-[var(--text-primary)] bg-[var(--text-primary)] text-[var(--bg-primary)]'
                  : 'border-[var(--border)] hover:bg-[var(--bg-secondary)]'
              }`}
              title={busy ? `Cancel current pipeline${inflight === 1 ? '' : 's'} and run anyway?` : 'Trigger this schedule now'}
            >
              {busy ? `Run now (cancel ${inflight})` : 'Run now'}
            </button>

            {/* Cancel (only if has inflight) */}
            {inflight > 0 && (
              <button
                onClick={(e) => { e.stopPropagation(); onCancel(); }}
                className="text-[10px] px-2 py-0.5 border border-[var(--yellow)]/50 text-[var(--yellow)] rounded hover:bg-[var(--yellow)]/10"
                title="Kill the in-flight pipelines but keep schedule enabled"
              >Cancel ({inflight})</button>
            )}

            {/* Pause: hidden if already paused */}
            {s.enabled && (
              <button
                onClick={(e) => { e.stopPropagation(); onTogglePaused(); }}
                className="text-[10px] px-2 py-0.5 border border-[var(--border)] rounded hover:bg-[var(--bg-secondary)]"
                title="Stop auto-triggering. Running pipelines keep going."
              >Pause</button>
            )}

            {/* Resume: only when paused and no inflight (avoid confusion) */}
            {!s.enabled && inflight === 0 && (
              <button
                onClick={(e) => { e.stopPropagation(); onTogglePaused(); }}
                className="text-[10px] px-2 py-0.5 border border-[var(--text-primary)] bg-[var(--text-primary)] text-[var(--bg-primary)] rounded"
                title="Re-enable scheduled triggers"
              >Resume</button>
            )}

            {/* Stop: only when there's inflight (running OR paused-with-inflight) */}
            {inflight > 0 && (
              <button
                onClick={(e) => { e.stopPropagation(); onStop(); }}
                className="text-[10px] px-2 py-0.5 border border-[var(--red)]/50 text-[var(--red)] rounded hover:bg-[var(--red)]/10"
                title="Pause AND cancel all running pipelines"
              >Stop</button>
            )}

            <button
              onClick={(e) => { e.stopPropagation(); onEdit(); }}
              className="text-[10px] px-2 py-0.5 border border-[var(--border)] rounded hover:bg-[var(--bg-secondary)]"
              title="Edit input / trigger / action (body type & ref are immutable)"
            >Edit</button>

            <span className="flex-1" />

            <button
              onClick={(e) => { e.stopPropagation(); onDelete(); }}
              className="text-[10px] px-2 py-0.5 border border-[var(--red)]/50 text-[var(--red)] rounded hover:bg-[var(--red)]/10"
            >Delete</button>
          </div>

          {/* Configuration */}
          <div className="mb-3">
            <div className="text-[10px] font-semibold text-[var(--text-secondary)] uppercase mb-1">Configuration</div>
            <div className="text-[10px] grid grid-cols-[80px_1fr] gap-x-3 gap-y-1 font-mono">
              <span className="text-[var(--text-secondary)]">Body</span>
              <span>{s.body_kind} · {s.body_ref}</span>
              <span className="text-[var(--text-secondary)]">Action</span>
              <span>{describeAction(s)}</span>
              <span className="text-[var(--text-secondary)]">Trigger</span>
              <span>{describeSchedule(s)}</span>
              {Object.entries(s.input).map(([k, v]) => (
                <>
                  <span key={`k-${k}`} className="text-[var(--text-secondary)]">{k}</span>
                  <span key={`v-${k}`} className="whitespace-pre-wrap break-all">{typeof v === 'string' ? v : JSON.stringify(v)}</span>
                </>
              ))}
            </div>
          </div>

          {/* Tracked pipelines */}
          <div className="mb-1">
            <div className="text-[10px] font-semibold text-[var(--text-secondary)] uppercase mb-1 flex items-baseline gap-2 flex-wrap">
              <span>Tracked pipelines</span>
              {runs && runs.length > 0 && (
                <span className="text-[10px] font-normal normal-case text-[var(--text-primary)]">
                  {runs.length} total
                  {runs.filter(r => r.status === 'started').length   ? <> · <span className="text-[var(--accent)]">{runs.filter(r => r.status === 'started').length} running</span></>   : null}
                  {runs.filter(r => r.status === 'done').length      ? <> · <span className="text-[var(--green)]">{runs.filter(r => r.status === 'done').length} done</span></>          : null}
                  {runs.filter(r => r.status === 'failed').length    ? <> · <span className="text-[var(--red)]">{runs.filter(r => r.status === 'failed').length} failed</span></>        : null}
                  {runs.filter(r => r.status === 'cancelled').length ? <> · <span className="text-[var(--text-secondary)]">{runs.filter(r => r.status === 'cancelled').length} cancelled</span></> : null}
                </span>
              )}
            </div>
            {runs == null ? (
              <div className="text-[10px] text-[var(--text-secondary)]">Loading…</div>
            ) : runs.length === 0 ? (
              <div className="text-[10px] text-[var(--text-secondary)]">(no runs yet)</div>
            ) : (
              <div className="space-y-0.5">
                {/* Show running + last terminal */}
                {(() => {
                  const active = runs.filter((r) => r.status === 'started');
                  const lastTerminal = runs.find((r) => r.status !== 'started');
                  return lastTerminal ? [...active, lastTerminal] : active;
                })().map((r) => (
                  <RunRow
                    key={r.id}
                    run={r}
                    isPipelineBody={s.body_kind === 'pipeline'}
                    onViewPipeline={onViewPipeline}
                  />
                ))}
              </div>
            )}
          </div>
        </div>
      )}
    </div>
  );
}

function describeSchedule(s: Schedule): string {
  switch (s.schedule_kind) {
    case 'period': return `every ${s.schedule_interval_minutes} min`;
    case 'cron':   return `cron "${s.schedule_cron}"`;
    case 'once':   return `once at ${s.schedule_at}`;
    case 'manual': return 'manual only';
  }
}

function describeAction(s: Schedule): string {
  const cfg = s.action_config || {};
  switch (s.action_kind) {
    case 'none':     return 'none';
    case 'chat': {
      const id = typeof cfg.session_id === 'string' ? cfg.session_id : '';
      return id ? `chat · ${id.slice(0, 8)}…` : 'chat';
    }
    case 'email': {
      const to = cfg.to;
      const list = Array.isArray(to) ? to.join(', ') : (typeof to === 'string' ? to : '');
      return list ? `email · ${list}` : 'email';
    }
    case 'telegram': {
      const id = typeof cfg.chat_id === 'string' && cfg.chat_id ? cfg.chat_id : '(default chat)';
      return `telegram · ${id}`;
    }
    default: return s.action_kind;
  }
}

function RunRow({ run: r, isPipelineBody, onViewPipeline }: {
  run: ScheduleRun;
  isPipelineBody: boolean;
  onViewPipeline?: (pipelineId: string) => void;
}) {
  // body_output/error captured for prompt runs aren't navigable to a
  // pipeline detail page; the only place to surface them is right here,
  // inline. Failed runs default to expanded so the user doesn't have to hunt.
  // Surface ALL three stages — trigger / body / action — instead of
  // just body status. Skipped or failed action was previously
  // invisible (e.g. "body status was started" race where action got
  // permanently skipped while body settled later — user had no way
  // to see WHY chat/email/telegram never fired).
  const actionFailedOrSkipped = r.action_status === 'failed' || r.action_status === 'skipped';
  const [expanded, setExpanded] = useState(r.status === 'failed' || actionFailedOrSkipped);
  const canOpenPipeline = isPipelineBody && !!onViewPipeline;
  const hasDetails = !!(r.body_output || r.error || r.action_error);

  return (
    <div className="text-[10px] font-mono">
      <div
        className="flex items-baseline gap-2 py-0.5 px-1 rounded hover:bg-[var(--bg-secondary)]/50 cursor-pointer"
        onClick={(e) => {
          e.stopPropagation();
          if (canOpenPipeline) onViewPipeline!(r.target_id);
          else if (hasDetails) setExpanded((x) => !x);
        }}
        title={canOpenPipeline ? 'Open pipeline run · click status badges to expand' : hasDetails ? 'Click to expand details' : ''}
      >
        <span className={
          r.status === 'started'   ? 'text-[var(--accent)]' :
          r.status === 'done'      ? 'text-[var(--green)]' :
          r.status === 'failed'    ? 'text-[var(--red)]' :
          r.status === 'cancelled' ? 'text-[var(--text-secondary)]' : ''
        }>
          {r.status === 'started'   ? '🔄' :
           r.status === 'done'      ? '✅' :
           r.status === 'failed'    ? '❌' :
           r.status === 'cancelled' ? '⏹' : '·'}
        </span>
        <span className="text-[var(--text-secondary)] w-20 truncate">{r.target_id.slice(0, 8)}</span>
        <span className="flex-1 truncate">
          {formatTime(r.started_at)}{r.finished_at && ' → ' + formatTime(r.finished_at)}
        </span>
        <span className={`px-1 py-0 text-[9px] rounded ${
          r.trigger === 'manual' ? 'bg-[var(--accent)]/15 text-[var(--accent)]' : 'bg-[var(--text-secondary)]/15 text-[var(--text-secondary)]'
        }`}>{r.trigger}</span>
        {/* Body status badge */}
        <span className={`px-1 py-0 text-[9px] rounded ${
          r.status === 'done'      ? 'bg-[var(--green)]/15 text-[var(--green)]' :
          r.status === 'failed'    ? 'bg-[var(--red)]/15 text-[var(--red)]' :
          r.status === 'cancelled' ? 'bg-[var(--text-secondary)]/15 text-[var(--text-secondary)]' :
          'bg-[var(--accent)]/15 text-[var(--accent)]'
        }`} title="body status">body:{r.status}</span>
        {/* Action status badge — visible whenever action_status is set
            (i.e. action was attempted). null = action_kind=none, hide. */}
        {r.action_status !== null && (
          <span className={`px-1 py-0 text-[9px] rounded ${
            r.action_status === 'done'    ? 'bg-[var(--green)]/15 text-[var(--green)]' :
            r.action_status === 'failed'  ? 'bg-[var(--red)]/15 text-[var(--red)]' :
            r.action_status === 'skipped' ? 'bg-[var(--yellow)]/15 text-[var(--yellow)]' :
            'bg-[var(--text-secondary)]/15 text-[var(--text-secondary)]'
          }`} title={`action status${r.action_error ? ' — ' + r.action_error : ''}`}>
            action:{r.action_status}
          </span>
        )}
      </div>
      {expanded && hasDetails && (
        <div className="ml-7 my-1 px-2 py-1.5 bg-[var(--bg-secondary)] rounded text-[10px] whitespace-pre-wrap break-words border border-[var(--border)]">
          {r.error && (
            <div className="text-[var(--red)] mb-1">
              <span className="font-semibold">body error:</span> {r.error}
            </div>
          )}
          {r.action_error && (
            <div className={`mb-1 ${r.action_status === 'failed' ? 'text-[var(--red)]' : 'text-[var(--yellow)]'}`}>
              <span className="font-semibold">action {r.action_status || 'error'}:</span> {r.action_error}
            </div>
          )}
          {r.body_output && (
            <div className="text-[var(--text-secondary)]">
              <div className="font-semibold mb-0.5">body_output (fed into action):</div>
              <div className="max-h-60 overflow-auto">{r.body_output.slice(0, 4000)}{r.body_output.length > 4000 ? '…' : ''}</div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

function formatTime(iso: string): string {
  if (!iso) return '';
  const d = new Date(iso);
  const now = Date.now();
  const diff = d.getTime() - now;
  const absMin = Math.abs(diff) / 60_000;
  if (absMin < 1) return diff > 0 ? '<1m' : 'just now';
  if (absMin < 60) {
    const m = Math.round(absMin);
    return diff > 0 ? `in ${m}m` : `${m}m ago`;
  }
  return d.toLocaleString();
}
