/** @vitest-environment jsdom */
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { LayerProvider } from '@astryxdesign/core'
import { cleanup, fireEvent, render, screen, within } from '@testing-library/react'
import '@testing-library/jest-dom/vitest'
import React from 'react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { fetchCollectorState, fetchFactoryRun } from '../../lib/collector-client'
import { postCollectorAction } from '../../lib/action-client'
import type { StateResponse } from '../../lib/collector-types'
import type { FactoryAgentAttemptView, FactoryDecisionView, FactoryPanelData, FactoryRunView } from '../../lib/factory-types'
import { FactoryContent } from './FactoryContent'

vi.mock('../../lib/collector-client', async (importOriginal) => {
  const actual = await importOriginal<typeof import('../../lib/collector-client')>()
  return {
    ...actual,
    fetchCollectorState: vi.fn(),
    fetchFactoryRun: vi.fn(),
  }
})

vi.mock('../../lib/action-client', () => ({
  postCollectorAction: vi.fn(),
}))

const REPO_A = '/home/user/Projects/overdeck'
const REPO_B = '/home/user/Projects/multideal'

function factoryPanel(data: FactoryPanelData, ts = '2026-08-05T12:00:00.000Z'): StateResponse['panels'][number] {
  return { id: 'factory-runs', ts, data }
}

function factoryState(data: FactoryPanelData, adapterOverrides: Partial<StateResponse['adapters'][number]> = {}): StateResponse {
  return {
    panels: [factoryPanel(data)],
    adapters: [{
      id: 'factory',
      interval: 5_000,
      lastAttempt: Date.parse('2026-08-05T12:00:00.000Z'),
      lastSuccess: Date.parse('2026-08-05T12:00:00.000Z'),
      lastPollDurationMs: 12,
      consecutiveErrors: 0,
      stale: false,
      ...adapterOverrides,
    }],
  }
}

function completedRun(overrides: Partial<FactoryRunView> = {}): FactoryRunView {
  return {
    adwId: 'adw-complete-a',
    repo: REPO_A,
    repoName: 'overdeck',
    adwName: 'adw_test',
    runSlug: 'ship-feature-a',
    request: 'ship feature A',
    status: 'success',
    engineer: 'engineer',
    startedAt: '2026-08-05T10:00:00Z',
    endedAt: '2026-08-05T10:30:00Z',
    totalTokens: 12_000,
    totalCost: 1.25,
    phases: [
      {
        phaseId: 'phase-a-1',
        seq: 1,
        name: 'plan',
        kind: 'engineer',
        owner: 'coder',
        description: null,
        status: 'success',
        attempt: 0,
        retries: 0,
        error: null,
        startedAt: '2026-08-05T10:00:00Z',
        endedAt: '2026-08-05T10:10:00Z',
        tokens: 0,
        spend: 0,
      },
      {
        phaseId: 'phase-a-2',
        seq: 2,
        name: 'build',
        kind: 'agent',
        owner: 'coder',
        description: null,
        status: 'success',
        attempt: 0,
        retries: 1,
        error: null,
        startedAt: '2026-08-05T10:10:00Z',
        endedAt: '2026-08-05T10:30:00Z',
        tokens: 6_100,
        spend: 0.75,
      },
    ],
    events: [],
    decisions: [],
    ...overrides,
  }
}

function pendingDecision(overrides: Partial<FactoryDecisionView> = {}): FactoryDecisionView {
  return {
    decisionId: 'dec-1',
    phase: 'plan',
    question: 'Which storage backend?',
    options: [
      { value: 'sqlite', label: 'SQLite', recommended: true },
      { value: 'postgres', label: 'Postgres' },
    ],
    freeText: true,
    context: 'Both are viable; sqlite is zero-ops.',
    status: 'pending',
    answerValue: null,
    answerText: null,
    answeredBy: null,
    createdAt: '2026-08-05T11:10:00Z',
    answeredAt: null,
    ...overrides,
  }
}

function runningRun(overrides: Partial<FactoryRunView> = {}): FactoryRunView {
  return {
    adwId: 'adw-running-b',
    repo: REPO_B,
    repoName: 'multideal',
    adwName: 'adw_live',
    runSlug: 'investigate-failure',
    request: 'investigate failure',
    status: 'running',
    engineer: 'engineer',
    startedAt: '2026-08-05T11:00:00Z',
    endedAt: null,
    totalTokens: 900,
    totalCost: 0.12,
    phases: [
      {
        phaseId: 'phase-b-1',
        seq: 1,
        name: 'triage',
        kind: 'engineer',
        owner: 'coder',
        description: null,
        status: 'success',
        attempt: 0,
        retries: 0,
        error: null,
        startedAt: '2026-08-05T11:00:00Z',
        endedAt: '2026-08-05T11:05:00Z',
        tokens: 0,
        spend: 0,
      },
      {
        phaseId: 'phase-b-2',
        seq: 2,
        name: 'fix',
        kind: 'agent',
        owner: 'coder',
        description: null,
        status: 'running',
        attempt: 0,
        retries: 0,
        error: null,
        startedAt: '2026-08-05T11:05:00Z',
        endedAt: null,
        tokens: 400,
        spend: 0.05,
      },
    ],
    events: [],
    decisions: [],
    ...overrides,
  }
}

type ToolCallFixture = {
  toolCallId: string
  seq: number
  toolName: string | null
  args: Record<string, unknown> | null
  startedAt: string | null
  endedAt: string | null
  durationMs: number | null
  ok: boolean | null
  resultExcerpt: string | null
}

type AttemptFixture = FactoryAgentAttemptView & {
  timeoutKind: 'wall' | 'idle' | null
  toolCalls: ToolCallFixture[]
}

function agentAttempt(overrides: Partial<AttemptFixture> = {}): AttemptFixture {
  return {
    attemptId: 'attempt-a',
    phaseId: 'phase-a-2',
    agent: 'coder',
    sessionId: 'session-a',
    command: 'codex exec',
    systemPrompt: null,
    userPrompt: 'Build the requested feature.',
    returncode: 0,
    signal: null,
    timedOut: false,
    timeoutKind: null,
    tokens: 6_100,
    error: null,
    stderrPath: null,
    host: 'local',
    account: 'primary',
    model: 'gpt-test',
    startedAt: '2026-08-05T10:10:00Z',
    lastOutputAt: '2026-08-05T10:29:00Z',
    endedAt: '2026-08-05T10:30:00Z',
    durationMs: 1_200_000,
    idleMs: null,
    usage: null,
    providerFailure: null,
    toolCalls: [],
    ...overrides,
  }
}

function renderFactoryContent() {
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
  return {
    ...render(
      <QueryClientProvider client={queryClient}>
        <LayerProvider>
          <FactoryContent />
        </LayerProvider>
      </QueryClientProvider>,
    ),
    queryClient,
  }
}

afterEach(() => {
  cleanup()
  vi.clearAllMocks()
})

describe('FactoryContent', () => {
  it('renders a completed run with per-phase estimated spend', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [completedRun()],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    expect(within(card).getByRole('heading', { level: 3, name: 'ship-feature-a' })).toBeInTheDocument()
    expect(within(card).getByText('ship feature A · adw_test')).toBeInTheDocument()
    const summaryToggle = within(card).getByTestId('factory-run-toggle')
    expect(summaryToggle).not.toHaveTextContent('adw-complete-a')
    expect(within(card).getByText('adw-complete-a')).toBeInTheDocument()
    expect(within(card).getByText('overdeck')).toBeInTheDocument()
    expect(within(card).getByText('12,000')).toBeInTheDocument()
    expect(within(card).getByText('est. $1.2500')).toBeInTheDocument()

    expect(card).toHaveAttribute('data-expanded', 'false')
    fireEvent.click(within(card).getByTestId('factory-run-toggle'))

    const phaseTable = within(card).getByTestId('factory-phase-table')
    expect(within(phaseTable).getByText('build')).toBeInTheDocument()
    expect(within(phaseTable).getByText('est. $0.7500')).toBeInTheDocument()
    expect(within(phaseTable).getByText('6,100')).toBeInTheDocument()
  })

  it('loads trace detail only after a summarized run expands', async () => {
    const detail = completedRun()
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [completedRun({ detailAvailable: true, phases: [], events: [], attempts: [], gates: [], diffs: [], processes: [] })],
    }))
    vi.mocked(fetchFactoryRun).mockResolvedValue(detail)

    const { queryClient } = renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    expect(fetchFactoryRun).not.toHaveBeenCalled()
    const toggle = within(card).getByTestId('factory-run-toggle')
    fireEvent.click(toggle)

    expect(await within(card).findByTestId('factory-phase-table')).toBeInTheDocument()
    expect(fetchFactoryRun).toHaveBeenCalledOnce()
    expect(fetchFactoryRun).toHaveBeenCalledWith('adw-complete-a', expect.any(AbortSignal))

    fireEvent.click(toggle)
    await vi.waitFor(() => {
      expect(queryClient.getQueryData(['collector-factory-run', 'adw-complete-a'])).toBeUndefined()
    })
  })

  it('keeps forensic messages collapsed while showing task coverage in the attempt drawer', async () => {
    const systemPrompt = [
      'First line establishes the operating context for this agent and continues beyond the inline preview.',
      'Second line contains additional constraints that must remain readable.',
      'Third line adds enough material to make this a genuinely multiline prompt.',
      'Late prompt sentinel: preserve this exact final instruction in full.',
    ].join('\n')
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [completedRun({
        attempts: [agentAttempt({
          attemptId: 'attempt-long-prompt',
          systemPrompt,
        })],
      })],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    fireEvent.click(within(card).getByTestId('factory-run-toggle'))
    fireEvent.click(within(card).getByRole('button', { name: 'coder' }))

    const attemptDrawer = screen.getByRole('dialog', { name: 'gpt-test' })
    expect(within(attemptDrawer).getByText('Task instructions')).toBeInTheDocument()
    expect(within(attemptDrawer).getByText('Attachment accounting was not recorded for this attempt.')).toBeInTheDocument()
    expect(within(attemptDrawer).getByRole('button', { name: 'System message' })).toBeInTheDocument()
    expect(within(attemptDrawer).getByRole('button', { name: 'User message' })).toBeInTheDocument()
  })

  it('renders tool calls in sequence with durations and opens the complete result excerpt', async () => {
    const longResult = `${'Result detail '.repeat(30)}\nFinal tool result sentinel.`
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [completedRun({
        attempts: [agentAttempt({
          toolCalls: [
            {
              toolCallId: 'call-2',
              seq: 2,
              toolName: 'write_file',
              args: { path: 'src/result.ts', content: 'complete' },
              startedAt: '2026-08-05T10:12:00Z',
              endedAt: '2026-08-05T10:12:03Z',
              durationMs: 3_000,
              ok: true,
              resultExcerpt: longResult,
            },
            {
              toolCallId: 'call-0',
              seq: 0,
              toolName: 'read_file',
              args: { path: 'src/input.ts' },
              startedAt: '2026-08-05T10:10:10Z',
              endedAt: '2026-08-05T10:10:11Z',
              durationMs: 1_000,
              ok: true,
              resultExcerpt: 'input contents',
            },
            {
              toolCallId: 'call-1',
              seq: 1,
              toolName: 'run_command',
              args: { command: 'test' },
              startedAt: '2026-08-05T10:11:00Z',
              endedAt: '2026-08-05T10:11:02Z',
              durationMs: 2_000,
              ok: false,
              resultExcerpt: 'test failed',
            },
          ],
        })],
      })],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    fireEvent.click(within(card).getByTestId('factory-run-toggle'))
    fireEvent.click(within(card).getByRole('button', { name: 'coder' }))

    const attemptDrawer = screen.getByRole('dialog', { name: 'gpt-test' })
    expect(attemptDrawer).toHaveStyle({ '--x-maxWidth': 'min(72rem, 100%)' })
    const toolCallTable = within(attemptDrawer).getByRole('table', { name: 'Tool calls for this agent attempt' })
    const rows = within(toolCallTable).getAllByRole('row').slice(1)
    expect(rows).toHaveLength(3)
    expect(rows[0]).toHaveTextContent('0')
    expect(rows[0]).toHaveTextContent('read_file')
    expect(rows[0]).toHaveTextContent('1s')
    expect(rows[1]).toHaveTextContent('1')
    expect(rows[1]).toHaveTextContent('run_command')
    expect(rows[1]).toHaveTextContent('2s')
    expect(within(rows[1]!).getByTestId('status-chip')).toHaveAttribute('data-status-category', 'failed')
    expect(rows[2]).toHaveTextContent('2')
    expect(rows[2]).toHaveTextContent('write_file')
    expect(rows[2]).toHaveTextContent('3s')

    const resultLabel = within(rows[2]!).getByText('Result')
    fireEvent.click(within(resultLabel.parentElement!).getByRole('button', { name: 'Read more' }))
    const resultDrawer = screen.getByRole('dialog', { name: 'Result' })
    expect(within(resultDrawer).getByText((_, element) => (
      element?.tagName === 'PRE'
      && element.textContent?.includes('Final tool result sentinel.') === true
    ))).toBeInTheDocument()
  })

  it('renders a killed tool call without inventing a zero duration', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [completedRun({
        attempts: [agentAttempt({
          returncode: null,
          signal: 15,
          toolCalls: [{
            toolCallId: 'call-killed',
            seq: 0,
            toolName: 'run_command',
            args: { command: 'long-running-task' },
            startedAt: '2026-08-05T10:29:00Z',
            endedAt: null,
            durationMs: null,
            ok: null,
            resultExcerpt: null,
          }],
        })],
      })],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    fireEvent.click(within(card).getByTestId('factory-run-toggle'))
    fireEvent.click(within(card).getByRole('button', { name: 'coder' }))
    const attemptDrawer = screen.getByRole('dialog', { name: 'gpt-test' })
    const toolCallTable = within(attemptDrawer).getByRole('table', { name: 'Tool calls for this agent attempt' })
    const row = within(toolCallTable).getAllByRole('row')[1]!
    expect(row).toHaveTextContent('killed')
    expect(row).not.toHaveTextContent('0s')
  })

  it('renders an explicit empty state when an attempt has no tool calls', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [completedRun({ attempts: [agentAttempt({ toolCalls: [] })] })],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    fireEvent.click(within(card).getByTestId('factory-run-toggle'))
    fireEvent.click(within(card).getByRole('button', { name: 'coder' }))
    const attemptDrawer = screen.getByRole('dialog', { name: 'gpt-test' })
    expect(within(attemptDrawer).getByText('No tool calls recorded for this attempt.')).toBeInTheDocument()
    expect(within(attemptDrawer).queryByRole('table', { name: 'Tool calls for this agent attempt' })).not.toBeInTheDocument()
  })

  it('identifies an idle timeout as the no-output limit in the table and metadata', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [completedRun({ attempts: [agentAttempt({ timedOut: true, timeoutKind: 'idle', returncode: null })] })],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    fireEvent.click(within(card).getByTestId('factory-run-toggle'))
    expect(within(card).getByText('timed out (no output)')).toBeInTheDocument()
    fireEvent.click(within(card).getByRole('button', { name: 'coder' }))
    const attemptDrawer = screen.getByRole('dialog', { name: 'gpt-test' })
    expect(within(attemptDrawer).getByText('timed out (no output)')).toBeInTheDocument()
  })

  it('keeps the existing timeout wording when timeoutKind is null', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [completedRun({ attempts: [agentAttempt({ timedOut: true, timeoutKind: null, returncode: null })] })],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    fireEvent.click(within(card).getByTestId('factory-run-toggle'))
    expect(within(card).getByText('timed out')).toBeInTheDocument()
    expect(within(card).queryByText(/timed out \(/)).not.toBeInTheDocument()
    fireEvent.click(within(card).getByRole('button', { name: 'coder' }))
    const attemptDrawer = screen.getByRole('dialog', { name: 'gpt-test' })
    expect(within(attemptDrawer).getByText('timed out')).toBeInTheDocument()
    expect(within(attemptDrawer).getByText('yes')).toBeInTheDocument()
  })

  it('renders a partial running run with elapsed timing and stop command', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [runningRun()],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    expect(within(card).getByRole('heading', { level: 3, name: 'investigate-failure' })).toBeInTheDocument()
    expect(within(card).getByText('investigate failure · adw_live')).toBeInTheDocument()
    const summaryToggle = within(card).getByTestId('factory-run-toggle')
    expect(summaryToggle).not.toHaveTextContent('adw-running-b')
    expect(within(card).getAllByText('adw-running-b').length).toBeGreaterThan(0)
    const header = within(card).getByTestId('factory-run-header')
    expect(within(header).getByRole('button', { name: 'Stop run' })).toBeInTheDocument()
    const adwIdRow = within(card).getByText('adw id').parentElement!
    expect(within(adwIdRow).getByText('adw-running-b')).toBeInTheDocument()
    expect(within(card).getByText('multideal')).toBeInTheDocument()
    expect(within(card).getAllByText(/elapsed/).length).toBeGreaterThan(0)
    expect(within(card).getByText('factory stop adw-running-b')).toBeInTheDocument()
    expect(within(card).getByTestId('factory-phase-table')).toBeInTheDocument()
  })

  it('shows events when a run has events but no phases', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [{
        adwId: 'adw-events-only',
        repo: REPO_A,
        repoName: null,
        adwName: null,
        runSlug: 'bootstrap-only',
        request: 'bootstrap only',
        status: 'running',
        engineer: null,
        startedAt: '2026-08-05T12:00:00Z',
        endedAt: null,
        totalTokens: null,
        totalCost: null,
        phases: [],
        events: [{
          rowid: 1,
          eventId: 'evt-1',
          phaseId: null,
          parentId: null,
          type: 'phase_start',
          name: 'boot',
          payloadJson: null,
          tokens: null,
          startedAt: '2026-08-05T12:00:00Z',
          endedAt: null,
        }],
        decisions: [],
      }],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    expect(within(card).getByTestId('factory-event-groups')).toBeInTheDocument()
    expect(within(card).getByText('boot')).toBeInTheDocument()
    expect(within(card).queryByText('No phases or events recorded for this run.')).not.toBeInTheDocument()
  })

  it('shows canonical repo name for legacy worktree paths without repoName', async () => {
    const worktreeRepo = `${REPO_A}/.worktrees/cluster-machines-hero`
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [runningRun({
        repo: worktreeRepo,
        repoName: null,
        runSlug: 'cluster-machines-hero',
      })],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    expect(within(card).getByRole('heading', { level: 3, name: 'cluster-machines-hero' })).toBeInTheDocument()
    expect(within(card).getByText('overdeck')).toBeInTheDocument()
    expect(within(card).queryByText('cluster-machines-hero', { selector: 'span.truncate' })).not.toBeInTheDocument()
  })

  it('lists live runs before completed runs and shows multiple repos', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [completedRun(), runningRun()],
    }))

    renderFactoryContent()

    const cards = await screen.findAllByTestId('factory-run-card')
    expect(cards).toHaveLength(2)
    expect(cards[0]).toHaveAttribute('data-adw-id', 'adw-running-b')
    expect(cards[1]).toHaveAttribute('data-adw-id', 'adw-complete-a')
    expect(screen.getByText('multideal')).toBeInTheDocument()
    expect(screen.getByText('overdeck')).toBeInTheDocument()
  })

  it('handles malformed and unknown fields without crashing', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [{
        adwId: 'adw-unknown',
        repo: '   ',
        repoName: null,
        adwName: '   ',
        runSlug: '   ',
        request: '   ',
        status: '   ',
        engineer: null,
        startedAt: 'not-a-date',
        endedAt: null,
        totalTokens: Number.NaN,
        totalCost: Number.NaN,
        phases: [{
          phaseId: 'phase-unknown',
          seq: null,
          name: null,
          kind: null,
          owner: null,
          description: null,
          status: null,
          attempt: null,
          retries: null,
          error: null,
          startedAt: null,
          endedAt: null,
          tokens: 0,
          spend: 0,
        }],
        events: [],
        decisions: [],
      }],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    expect(within(card).getByRole('heading', { name: 'adw-unknown' })).toBeInTheDocument()
    expect(within(card).getAllByText('unknown').length).toBeGreaterThan(0)
    expect(within(card).getByText('unassigned')).toBeInTheDocument()
  })

  it('distinguishes an empty database from collector failure', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [],
    }))

    const { unmount: unmountEmpty } = renderFactoryContent()
    expect(await screen.findByText(/No factory runs recorded yet/)).toBeInTheDocument()
    unmountEmpty()
    cleanup()
    vi.clearAllMocks()

    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState(
      { dbPath: '/tmp/missing.db', dbPresent: false, runs: [] },
    ))
    const { unmount: unmountMissing } = renderFactoryContent()
    expect(await screen.findByText(/Factory trace database is not available/)).toBeInTheDocument()
    unmountMissing()
    cleanup()
    vi.clearAllMocks()

    vi.mocked(fetchCollectorState).mockResolvedValue({
      panels: [factoryPanel({ dbPath: '/tmp/sssf.db', dbPresent: true, runs: [] })],
      adapters: [{
        id: 'factory',
        interval: 5_000,
        consecutiveErrors: 2,
        stale: false,
        lastError: 'sqlite open failed',
      }],
    })
    renderFactoryContent()
    expect(await screen.findByText(/Factory collector is failing: sqlite open failed/)).toBeInTheDocument()
  })

  it('expands a run with a pending decision by default and answers it in place', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [runningRun({ decisions: [pendingDecision()] })],
    }))
    vi.mocked(postCollectorAction).mockResolvedValue({ ok: true })

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    expect(card).toHaveAttribute('data-expanded', 'true')
    expect(within(card).getByText('1 decision waiting')).toBeInTheDocument()
    expect(within(card).getByText('Which storage backend?')).toBeInTheDocument()
    expect(within(card).getByText('Both are viable; sqlite is zero-ops.')).toBeInTheDocument()

    fireEvent.click(within(card).getByRole('button', { name: /SQLite/ }))

    expect(postCollectorAction).toHaveBeenCalledWith('factory.decision.answer', {
      args: { decisionId: 'dec-1', choice: 'sqlite' },
      requestedBy: 'deck-web',
    })
    expect(await within(card).findByText(/Answered: SQLite/)).toBeInTheDocument()
  })

  it('shows answered decision history with the chosen answer, rendered as relative time', async () => {
    vi.useFakeTimers({ toFake: ['Date'] })
    vi.setSystemTime(new Date('2026-08-05T12:00:00.000Z'))
    try {
      vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
        dbPath: '/tmp/sssf.db',
        dbPresent: true,
        runs: [completedRun({
          decisions: [pendingDecision({
            status: 'answered',
            answerValue: 'postgres',
            answeredBy: 'web',
            answeredAt: '2026-08-05T11:20:00Z',
          })],
        })],
      }))

      renderFactoryContent()

      const card = await screen.findByTestId('factory-run-card')
      fireEvent.click(within(card).getByTestId('factory-run-toggle'))
      const settled = within(card).getByTestId('factory-decision-settled')
      expect(within(settled).getByText('Which storage backend?')).toBeInTheDocument()
      expect(within(settled).getByText(/Postgres/)).toBeInTheDocument()
      expect(within(settled).queryByText('2026-08-05T11:20:00Z')).not.toBeInTheDocument()
      const time = within(settled).getByText('40m ago')
      expect(time).toHaveAttribute('title', '2026-08-05T11:20:00Z')
    } finally {
      vi.useRealTimers()
    }
  })

  it('falls back to the raw string when answeredAt is unparseable', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [completedRun({
        decisions: [pendingDecision({
          status: 'answered',
          answerValue: 'postgres',
          answeredBy: 'web',
          answeredAt: 'not-a-date',
        })],
      })],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    fireEvent.click(within(card).getByTestId('factory-run-toggle'))
    const settled = within(card).getByTestId('factory-decision-settled')
    expect(within(settled).getByText((text) => text.includes('not-a-date'))).toBeInTheDocument()
  })

  it('offers a rerun command on finished runs as the change-your-mind path', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [completedRun()],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    fireEvent.click(within(card).getByTestId('factory-run-toggle'))
    const hint = within(card).getByTestId('factory-rerun-hint')
    expect(hint).toHaveTextContent(`factory --repo '${REPO_A}' test 'ship feature A'`)
  })

  it('keeps run summary text outside the expand toggle so identifiers stay selectable', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [completedRun()],
    }))

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    const toggle = within(card).getByTestId('factory-run-toggle')
    expect(toggle).toHaveAttribute('type', 'button')
    expect(toggle).toHaveAttribute('aria-expanded', 'false')
    expect(toggle).not.toHaveTextContent('ship-feature-a')
    expect(toggle).not.toHaveTextContent('ship feature A')
    expect(within(card).getByRole('heading', { level: 3, name: 'ship-feature-a' })).toBeInTheDocument()
    expect(within(card).getByText('ship feature A · adw_test')).toBeInTheDocument()

    fireEvent.click(toggle)
    expect(toggle).toHaveAttribute('aria-expanded', 'true')
    fireEvent.click(toggle)
    expect(toggle).toHaveAttribute('aria-expanded', 'false')
  })

  it('stops a running run through the two-step confirm button', async () => {
    vi.mocked(fetchCollectorState).mockResolvedValue(factoryState({
      dbPath: '/tmp/sssf.db',
      dbPresent: true,
      runs: [runningRun()],
    }))
    vi.mocked(postCollectorAction).mockResolvedValue({ ok: true, result: 'already stopped (status=success)' })

    renderFactoryContent()

    const card = await screen.findByTestId('factory-run-card')
    fireEvent.click(within(card).getByRole('button', { name: 'Stop run' }))
    expect(postCollectorAction).not.toHaveBeenCalled()

    fireEvent.click(within(card).getByRole('button', { name: 'Yes, stop it' }))
    expect(postCollectorAction).toHaveBeenCalledWith('factory.run.stop', {
      args: { adwId: 'adw-running-b' },
      requestedBy: 'deck-web',
    })
    expect(await within(card).findByTestId('factory-stop-done')).toHaveTextContent(
      'already stopped (status=success)',
    )
  })
})
