/**
 * ToolPart — one tool call, rendered COLLAPSED by default with a clear expand
 * affordance. This is the SHARED SHELL: the header (glyph + name + subtitle +
 * duration + line count + optional hint) and the expand/collapse mechanics —
 * what's INSIDE varies per tool and is dispatched through the tool renderer
 * registry (`view/tools/registry.tsx`, Epic 2.2):
 *
 *   ⚡ terminal  sleep 8  · 12s                   ← running (elapsed ticks live)
 *   ▶ terminal  ls -la src  · 0.3s  (12 lines)   ← collapsed (default)
 *   ▼ terminal  ls -la src  · 0.3s               ← expanded header
 *   │ <renderer body>                            ← labeled fields / output / …
 *   ✗ terminal  ✗ exit 1  · 0.1s  (3 lines)      ← failed (error-colored glyph)
 *
 * Lifecycle is legible from the HEAD GLYPH alone (Epic 2.5): `⚡` running (with
 * a live `· Ns` elapsed off the shared 1s tick in `elapsed.ts` — never a timer
 * per part), `▶`/`▼` settled-expandable, `✗` failed (theme error color).
 * Clicking an expandable header toggles it (wrapped in useScrollAnchor so
 * expanding never yanks the viewport); running parts have no expand
 * affordance. The header row is chrome (selectable=false) — a free-form drag
 * copies only the expanded body content. Fully themed (no hardcoded styles).
 */
import { type ToolPartState } from '../logic/store.ts'
import type { ThemeColors } from '../logic/theme.ts'
import { useDimensions } from './dimensions.tsx'
import { useDisplay } from './display.tsx'
import { createSignal, Show } from 'solid-js'

import { truncate } from '../logic/toolOutput.ts'
import { elapsedSeconds, useElapsedTick } from './elapsed.ts'
import { useScrollAnchor } from './scrollAnchor.tsx'
import { useSessionInfo } from './sessionInfo.tsx'
import { useTheme } from './theme.tsx'
import { resultLines } from './tools/defaultTool.tsx'
import { rendererFor } from './tools/registry.tsx'

const GUTTER = 2

function fmtDuration(s: number): string {
  if (s < 10) return `${s.toFixed(1)}s`
  if (s < 60) return `${Math.round(s)}s`
  const m = Math.floor(s / 60)
  const r = Math.round(s % 60)
  return r ? `${m}m ${r}s` : `${m}m`
}

/** Live elapsed format — whole seconds (the tick advances 1s at a time). */
function fmtElapsed(s: number): string {
  if (s < 60) return `${s}s`
  const m = Math.floor(s / 60)
  const r = s % 60
  return r ? `${m}m ${r}s` : `${m}m`
}

/**
 * Header tool-NAME style — the name is the PRIMARY cue for what a settled tool
 * IS, so it renders in the primary text color + BOLD (the transcript otherwise
 * reads as undifferentiated muted rows). The failed state's error coloring
 * wins (still bold — failures should be unmistakable alongside the ✗ glyph);
 * a running part keeps its current muted treatment (the ⚡ glyph + live
 * elapsed already carry the running signal). Exported so tests can pin the
 * selection logic (char frames carry no color/attribute info).
 */
export function toolNameStyle(
  state: { failed: boolean; running: boolean },
  color: ThemeColors
): { fg: string; bold: boolean } {
  if (state.failed) return { bold: true, fg: color.error }
  if (state.running) return { bold: false, fg: color.muted }
  return { bold: true, fg: color.text }
}

/**
 * Live `  · 12s` elapsed for a RUNNING part. Mounted only under the running
 * `<Show>`, so its useElapsedTick subscription starts/stops the SHARED 1s
 * interval with the part's lifecycle (the last cleanup clears it). Falls back
 * to the plain ` …` marker when startedAt is unknown (e.g. a tool.complete
 * that arrived without a local tool.start).
 */
function RunningElapsed(props: { startedAt: number | undefined }) {
  const theme = useTheme()
  const tick = useElapsedTick()
  const text = () => {
    tick() // re-read every shared tick — Date.now() alone is not reactive
    return props.startedAt === undefined ? ' …' : `  · ${fmtElapsed(elapsedSeconds(props.startedAt))}`
  }
  return <span style={{ fg: theme().color.muted }}>{text()}</span>
}

export function ToolPart(props: { part: ToolPartState }) {
  const theme = useTheme()
  const dims = useDimensions()
  const info = useSessionInfo() // session cwd for path-relativizing renderers
  const anchor = useScrollAnchor()
  const display = useDisplay()
  // /details expanded → settled bodies default-OPEN; a manual click overrides
  // either way (and a later global flip applies again to un-overridden parts).
  const [override, setOverride] = createSignal<boolean | undefined>(undefined)
  const expanded = () => override() ?? display().details === 'expanded'
  const toggle = () => anchor(() => setOverride(!expanded()))

  // Per-tool renderer (re-dispatches if the name settles on tool.complete).
  const renderer = () => rendererFor(props.part.name)
  const bodyWidth = () => Math.max(20, dims().width - GUTTER - 4)
  const lines = () => resultLines(props.part)
  const running = () => props.part.state === 'running'
  // Expandable when the renderer says there's a body to reveal beyond the header.
  const collapsible = () => !running() && renderer().expandable(props.part)
  // Header subtitle: errors win; otherwise the renderer's collapsed summary.
  const subtitle = () => (props.part.error ? `✗ ${props.part.error}` : renderer().subtitle(props.part, info().cwd))
  const hint = () => renderer().hint?.(props.part)
  // Optional `+N −M` change summary (file tools) — themed, settled parts only.
  const stats = () => (running() || props.part.error ? undefined : renderer().stats?.(props.part))

  // Failed parts are legible from the glyph alone: `✗` in the head position
  // (error-colored), regardless of expandability — `(N lines)` still marks an
  // expandable body. `error` only lands on tool.complete, so running stays ⚡.
  const failed = () => !running() && Boolean(props.part.error)
  const headGlyph = () => (failed() ? '✗' : collapsible() ? (expanded() ? '▼' : '▶') : '⚡')
  // accent glyph MARKS the tool (draws the eye); the NAME is primary (bold text
  // via toolNameStyle) so WHAT the tool is reads at a glance; subtitle/metadata
  // stay muted — the secondary tier below the bright assistant answer.
  const headColor = () => (failed() ? theme().color.error : theme().color.accent)
  const subWidth = () => Math.max(1, bodyWidth() - props.part.name.length - 2)

  return (
    // Spacing between parts is owned by the parts column (gap), not per-part
    // margins — so a tool appearing mid-stream doesn't shift the layout.
    <box style={{ flexDirection: 'column', flexShrink: 0 }}>
      {/* header — clickable to toggle when there's an expandable body */}
      <box style={{ flexDirection: 'row', flexShrink: 0 }} onMouseDown={() => collapsible() && toggle()}>
        <box style={{ flexShrink: 0, width: GUTTER }}>
          <text selectable={false}>
            <span style={{ fg: headColor() }}>{headGlyph()}</span>
          </text>
        </box>
        <box style={{ flexDirection: 'row', flexGrow: 1, minWidth: 0 }}>
          {/* the whole header row is a collapsed SUMMARY (tool name + subtitle +
              duration + "(N lines)") — chrome, not the copyable body — so a
              free-form drag over a tool yields only the expanded body content,
              never the header label. */}
          <text selectable={false}>
            {/* the NAME is the primary cue (text + bold; error when failed;
                muted while running) — see toolNameStyle. Subtitle stays muted. */}
            <span style={toolNameStyle({ failed: failed(), running: running() }, theme().color)}>
              {props.part.name}
            </span>
            {/* subtitle shows while running too (the gateway argsPreview — e.g.
                the command being executed) so a running tool reads `⚡ terminal
                sleep 8 · 12s`, Ink parity. */}
            <Show when={subtitle()}>
              <span style={{ fg: props.part.error ? theme().color.error : theme().color.muted }}>
                {`  ${truncate(subtitle(), subWidth())}`}
              </span>
            </Show>
            <Show when={stats()}>
              {/* `+N −M` change summary (file tools) — added in the ok/added color,
                  removed in the error/removed color (themed, never hardcoded). */}
              {s => (
                <>
                  <span style={{ fg: theme().color.ok }}>{`  +${s().added}`}</span>
                  <span style={{ fg: theme().color.error }}>{` −${s().removed}`}</span>
                </>
              )}
            </Show>
            <Show when={hint()}>
              {/* per-tool muted hint (e.g. delegate_task's "(/agents to monitor)") —
                  shown while running too, Ink parity. */}
              <span style={{ fg: theme().color.muted }}>{`  ${hint() ?? ''}`}</span>
            </Show>
            <Show when={!running() && props.part.duration !== undefined}>
              <span style={{ fg: theme().color.muted }}>{`  · ${fmtDuration(props.part.duration ?? 0)}`}</span>
            </Show>
            {/* live elapsed (running only) — the <Show> scopes the shared-tick
                subscription to the running lifecycle (see RunningElapsed). */}
            <Show when={running()}>
              <RunningElapsed startedAt={props.part.startedAt} />
            </Show>
            <Show when={collapsible() && !expanded() && lines().length > 1}>
              <span style={{ fg: theme().color.muted }}>{`  (${lines().length} lines)`}</span>
            </Show>
          </text>
        </box>
      </box>

      {/* expanded body — the per-tool renderer's Body, inside a single
          left-bordered column (a `│` rule, not a bg fill — opencode's BlockTool
          style; also renders faithfully and reads cleaner). */}
      <Show when={collapsible() && expanded()}>
        <box
          style={{ flexDirection: 'column', flexGrow: 1, minWidth: 0, marginLeft: GUTTER, paddingLeft: 1 }}
          border={['left']}
          borderColor={props.part.error ? theme().color.error : theme().color.border}
        >
          {(() => {
            const Body = renderer().Body
            return <Body part={props.part} width={bodyWidth() - 2} cwd={info().cwd} />
          })()}
        </box>
      </Show>
    </box>
  )
}
