<script lang="ts" module>
  // The agent's working trace for ONE assistant turn, rendered inline as a
  // progressive Claude-Code-style stream instead of a collapsed blob (replaces
  // AgentTrace). Top to bottom: live plan checklist (running turn only), a
  // "Thinking…" line while the model reasons (before the first tool / between
  // steps), then each tool call as its own compact line grouped under its plan
  // step. The streamed text answer is rendered by ChatThread after this.
</script>

<script lang="ts">
  import type { ToolCallResult } from '$lib/types'
  import type { PlanStep } from '$lib/api'
  import ToolLine from './ToolLine.svelte'
  import Spinner from './Spinner.svelte'
  import CheckIcon from '@lucide/svelte/icons/check'
  import XIcon from '@lucide/svelte/icons/x'
  import PauseIcon from '@lucide/svelte/icons/pause'
  import SlashIcon from '@lucide/svelte/icons/slash'
  import ChevronRightIcon from '@lucide/svelte/icons/chevron-right'

  let {
    tools = [],
    status = 'idle',
    label = null,
    isLast = false,
    planSteps = [],
    taskStatus
  }: {
    tools?: ToolCallResult[]
    /** `idle` = this turn has no live state (a finished historical turn). */
    status?: 'running' | 'done' | 'error' | 'idle'
    /** Live indicator text while thinking (the running step / tool / "thinking…"). */
    label?: string | null
    isLast?: boolean
    planSteps?: PlanStep[]
    taskStatus?: string
  } = $props()

  const TERMINAL = new Set(['done', 'failed', 'abandoned'])

  // seq → step title (current-gen only) so tool groups can label themselves.
  const stepTitle = $derived(new Map<number, string>(planSteps.map((s) => [s.seq, s.title])))

  // Group consecutive tools by their plan step (when attributed). Plan-less /
  // meta tools (propose_plan, set_goal, …) have no stepSeq and form orphan
  // groups rendered without a header.
  interface Group {
    step: { seq: number; title: string } | null
    tools: ToolCallResult[]
  }
  const groups = $derived.by<Group[]>(() => {
    const out: Group[] = []
    let cur: Group | null = null
    for (const t of tools) {
      const seq = t.stepSeq
      if (!cur || (cur.step?.seq ?? null) !== (seq ?? null)) {
        cur = {
          step: seq != null && stepTitle.has(seq) ? { seq, title: stepTitle.get(seq)! } : null,
          tools: []
        }
        out.push(cur)
      }
      cur.tools.push(t)
    }
    return out
  })

  // Thinking line: visible while the turn is running and the model is reasoning
  // — before the first tool, or after a tool finishes but before the next one
  // starts. Hidden while a tool is mid-flight (its own spinner carries the
  // liveness) and on idle/finished turns.
  const lastToolRunning = $derived(
    tools.length > 0 && tools[tools.length - 1].type === 'tool_use'
  )
  const showThinking = $derived(status === 'running' && !lastToolRunning)

  // Plan checklist: only on the running/last turn, and only if a plan exists.
  const showPlan = $derived(isLast && planSteps.length > 0)
  const planTerminal = $derived(!!taskStatus && TERMINAL.has(taskStatus))
  let planExpanded = $state(false)
  const planDone = $derived(planSteps.filter((s) => s.status === 'done').length)
  const planFailedStep = $derived(planSteps.find((s) => s.status === 'failed'))

  // Elapsed time on the running step — ticks every second while a step is
  // running so the operator can see how long it's been going (and spot a
  // stuck step).
  let now = $state(Date.now())
  $effect(() => {
    const running = planSteps.some((s) => s.status === 'running')
    if (!running) return
    const id = setInterval(() => {
      now = Date.now()
    }, 1000)
    return () => clearInterval(id)
  })
  function stepElapsed(s: PlanStep): string {
    if (s.status !== 'running' || !s.started_at) return ''
    const sec = Math.max(0, Math.floor((now - new Date(s.started_at).getTime()) / 1000))
    if (sec < 60) return `${sec}s`
    if (sec < 3600) return `${Math.floor(sec / 60)}m`
    return `${Math.floor(sec / 3600)}h ${Math.floor((sec % 3600) / 60)}m`
  }
</script>

{#if showPlan}
  <div class="plan {planTerminal && !planExpanded ? 'plan-collapsed' : ''}">
    {#if planTerminal && !planExpanded}
      <button class="plan-summary" onclick={() => (planExpanded = true)}>
        {#if planFailedStep}
          <XIcon class="size-3 text-destructive" />
          <span class="plan-summary-text">Plan failed — step {planFailedStep.seq}</span>
        {:else}
          <CheckIcon class="size-3 text-primary" />
          <span class="plan-summary-text">Plan complete — {planDone}/{planSteps.length} steps</span>
        {/if}
        <ChevronRightIcon class="size-3 text-muted-foreground/60" />
      </button>
    {:else}
      <div class="plan-head">
        <span class="plan-head-label">Plan</span>
        <span class="plan-head-count">{planDone}/{planSteps.length}</span>
      </div>
      <ul class="plan-list">
        {#each planSteps as s (s.id)}
          <li class="plan-step {s.status === 'running' ? 'running' : ''}">
            <span class="plan-node {s.status}" aria-hidden="true">
              {#if s.status === 'running'}<Spinner class="size-3 text-primary" />
              {:else if s.status === 'done'}<CheckIcon class="size-2.5" strokeWidth={3.5} />
              {:else if s.status === 'failed'}<XIcon class="size-2.5" strokeWidth={3.5} />
              {:else if s.status === 'blocked'}<PauseIcon class="size-2" strokeWidth={3} />
              {:else if s.status === 'skipped' || s.status === 'replaced'}<SlashIcon class="size-2" strokeWidth={3} />
              {/if}
            </span>
            <span class="plan-title" title={s.title}>{s.title}</span>
            {#if s.status === 'running'}
              <span class="plan-elapsed">{stepElapsed(s)}</span>
            {/if}
          </li>
        {/each}
      </ul>
    {/if}
  </div>
{/if}

<!-- Thinking line: always rendered (fixed height) so appearing/disappearing
     doesn't shift the layout — it just fades in/out. Shows a STABLE label
     ("Working…") rather than the current operation, which would rewrite
     itself on every step/tool transition and read as text appearing and
     disappearing. The current operation is already visible in the plan
     checklist (running step) and the tool lines below. -->
<div class="thinking {showThinking ? '' : 'thinking-hidden'}" aria-hidden={!showThinking}>
  <Spinner class="size-3 shrink-0 text-primary" />
  <span class="thinking-text">Working…</span>
</div>

{#if groups.length > 0}
  <div class="tools">
    {#each groups as g, gi (gi)}
      {#if g.step}
        <div class="step-head">Step {g.step.seq} · {g.step.title}</div>
      {/if}
      {#each g.tools as tool (tool.id ?? `${gi}-${tool.name}`)}
        <ToolLine {tool} />
      {/each}
    {/each}
  </div>
{:else if status === 'idle' && tools.length === 0}
  <!-- finished turn with no tools: nothing to render -->
{:else if status === 'error'}
  <div class="thinking err">
    <XIcon class="size-3 shrink-0 text-destructive" />
    <span class="thinking-text">{label || 'Turn ended with an error'}</span>
  </div>
{/if}

<style>
  .plan {
    border: 1px solid var(--border);
    background: color-mix(in oklab, var(--primary) 3%, var(--card));
    padding: 0.35rem 0.55rem 0.4rem;
    margin-bottom: 0.35rem;
    animation: plan-in 0.2s ease-out;
  }
  @keyframes plan-in {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .plan {
      animation: none;
    }
  }
  .plan-summary {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    width: 100%;
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 0;
    text-align: left;
  }
  .plan-summary-text {
    font-size: 12px;
    color: var(--foreground);
    flex: 1;
  }
  .plan-head {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    margin-bottom: 0.25rem;
  }
  .plan-head-label {
    font-size: 9px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--muted-foreground);
  }
  .plan-head-count {
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--primary);
  }
  .plan-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
  }
  .plan-step {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.1rem 0;
  }
  .plan-step.running {
    background: color-mix(in oklab, var(--primary) 8%, transparent);
    margin: 0 -0.3rem;
    padding-left: 0.3rem;
    padding-right: 0.3rem;
  }
  .plan-node {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 0.875rem;
    height: 0.875rem;
    flex-shrink: 0;
    color: var(--muted-foreground);
  }
  .plan-node.done {
    color: var(--primary);
  }
  .plan-node.failed {
    color: var(--destructive);
  }
  .plan-node.running {
    color: var(--primary);
  }
  .plan-title {
    font-size: 12px;
    color: var(--muted-foreground);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .plan-step.running .plan-title {
    color: var(--foreground);
    font-weight: 500;
  }
  .plan-elapsed {
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--primary);
    margin-left: auto;
  }

  .thinking {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.2rem 0;
    height: 1.5rem;
    overflow: hidden;
    opacity: 1;
    transition: opacity 0.2s ease;
  }
  .thinking-hidden {
    opacity: 0;
    pointer-events: none;
  }
  .thinking.err {
    color: var(--destructive);
  }
  .thinking-text {
    font-size: 12px;
    color: var(--muted-foreground);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .tools {
    display: flex;
    flex-direction: column;
  }
  .step-head {
    font-size: 9px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--muted-foreground);
    padding: 0.35rem 0 0.1rem;
  }
</style>
