import type { ContentBlock, ToolResultBlockParam, ToolUseBlockParam } from '@anthropic-ai/sdk/resources/index.mjs';
type BetaContentBlock = ContentBlock | ToolResultBlockParam;
import * as React from 'react';
import { ConfigurableShortcutHint } from 'src/components/ConfigurableShortcutHint.js';
import { CtrlOToExpand, SubAgentProvider } from 'src/components/CtrlOToExpand.js';
import { Byline, KeyboardShortcutHint } from '@anthropic/ink';
import type { z } from 'zod/v4';
import { AgentProgressLine } from 'src/components/AgentProgressLine.js';
import { FallbackToolUseErrorMessage } from 'src/components/FallbackToolUseErrorMessage.js';
import { FallbackToolUseRejectedMessage } from 'src/components/FallbackToolUseRejectedMessage.js';
import { Markdown } from 'src/components/Markdown.js';
import { Message as MessageComponent } from 'src/components/Message.js';
import { MessageResponse } from 'src/components/MessageResponse.js';
import { ToolUseLoader } from 'src/components/ToolUseLoader.js';
import { Box, Text } from '@anthropic/ink';
import { getDumpPromptsPath } from 'src/services/api/dumpPrompts.js';
import { findToolByName, type Tools } from 'src/Tool.js';
import type { Message, ProgressMessage } from 'src/types/message.js';
import type { AgentToolProgress } from 'src/types/tools.js';
import { count } from 'src/utils/array.js';
import { getSearchOrReadFromContent, getSearchReadSummaryText } from 'src/utils/collapseReadSearch.js';
import { getDisplayPath } from 'src/utils/file.js';
import { formatDuration, formatNumber } from 'src/utils/format.js';
import { buildSubagentLookups, createAssistantMessage, EMPTY_LOOKUPS } from 'src/utils/messages.js';
import type { ModelAlias } from 'src/utils/model/aliases.js';
import { getMainLoopModel, parseUserSpecifiedModel, renderModelName } from 'src/utils/model/model.js';
import type { Theme, ThemeName } from 'src/utils/theme.js';
import type { outputSchema, Progress, RemoteLaunchedOutput } from './AgentTool.js';
import { inputSchema } from './AgentTool.js';
import { getAgentColor } from './agentColorManager.js';
import { GENERAL_PURPOSE_AGENT } from './built-in/generalPurposeAgent.js';
import { BetaUsage } from '@anthropic-ai/sdk/resources/beta.mjs';

const MAX_PROGRESS_MESSAGES_TO_SHOW = 3;

/**
 * Guard: checks if progress data has a `message` field (agent_progress or
 * skill_progress).  Other progress types (e.g. bash_progress forwarded from
 * sub-agents) lack this field and must be skipped by UI helpers.
 */
function hasProgressMessage(data: Progress): data is AgentToolProgress {
  if (!('message' in data)) {
    return false;
  }
  const msg = (data as AgentToolProgress).message;
  return msg != null && typeof msg === 'object' && 'type' in msg;
}

/**
 * Check if a progress message is a search/read/REPL operation (tool use or result).
 * Returns { isSearch, isRead, isREPL } if it's a collapsible operation, null otherwise.
 *
 * For tool_result messages, uses the provided `toolUseByID` map to find the
 * corresponding tool_use block instead of relying on `normalizedMessages`.
 */
function getSearchOrReadInfo(
  progressMessage: ProgressMessage<Progress>,
  tools: Tools,
  toolUseByID: Map<string, ToolUseBlockParam>,
): { isSearch: boolean; isRead: boolean; isREPL: boolean } | null {
  if (!hasProgressMessage(progressMessage.data)) {
    return null;
  }
  const message = progressMessage.data.message;

  // Check tool_use (assistant message)
  if (message.type === 'assistant') {
    return getSearchOrReadFromContent(message.message.content[0], tools);
  }

  // Check tool_result (user message) - find corresponding tool use from the map
  if (message.type === 'user') {
    const content = message.message.content[0];
    if (content?.type === 'tool_result') {
      const toolUse = toolUseByID.get(content.tool_use_id);
      if (toolUse) {
        return getSearchOrReadFromContent(toolUse, tools);
      }
    }
  }

  return null;
}

type SummaryMessage = {
  type: 'summary';
  searchCount: number;
  readCount: number;
  replCount: number;
  uuid: string;
  isActive: boolean; // true if still in progress (last message was tool_use, not tool_result)
};

type ProcessedMessage = { type: 'original'; message: ProgressMessage<AgentToolProgress> } | SummaryMessage;

/**
 * Process progress messages to group consecutive search/read operations into summaries.
 * For ants only - returns original messages for non-ants.
 * @param isAgentRunning - If true, the last group is always marked as active (in progress)
 */
function processProgressMessages(
  messages: ProgressMessage<Progress>[],
  tools: Tools,
  isAgentRunning: boolean,
): ProcessedMessage[] {
  // Only process for ants
  if (process.env.USER_TYPE !== 'ant') {
    return messages
      .filter(
        (m): m is ProgressMessage<AgentToolProgress> => hasProgressMessage(m.data) && m.data.message.type !== 'user',
      )
      .map(m => ({ type: 'original', message: m }));
  }

  const result: ProcessedMessage[] = [];
  let currentGroup: {
    searchCount: number;
    readCount: number;
    replCount: number;
    startUuid: string;
  } | null = null;

  function flushGroup(isActive: boolean): void {
    if (currentGroup && (currentGroup.searchCount > 0 || currentGroup.readCount > 0 || currentGroup.replCount > 0)) {
      result.push({
        type: 'summary',
        searchCount: currentGroup.searchCount,
        readCount: currentGroup.readCount,
        replCount: currentGroup.replCount,
        uuid: `summary-${currentGroup.startUuid}`,
        isActive,
      });
    }
    currentGroup = null;
  }

  const agentMessages = messages.filter((m): m is ProgressMessage<AgentToolProgress> => hasProgressMessage(m.data));

  // Build tool_use lookup incrementally as we iterate
  const toolUseByID = new Map<string, ToolUseBlockParam>();
  for (const msg of agentMessages) {
    // Track tool_use blocks as we see them
    if (msg.data.message.type === 'assistant') {
      for (const c of msg.data.message.message.content) {
        if (c.type === 'tool_use') {
          toolUseByID.set(c.id, c as ToolUseBlockParam);
        }
      }
    }
    const info = getSearchOrReadInfo(msg, tools, toolUseByID);

    if (info && (info.isSearch || info.isRead || info.isREPL)) {
      // This is a search/read/REPL operation - add to current group
      if (!currentGroup) {
        currentGroup = {
          searchCount: 0,
          readCount: 0,
          replCount: 0,
          startUuid: msg.uuid,
        };
      }
      // Only count tool_result messages (not tool_use) to avoid double counting
      if (msg.data.message.type === 'user') {
        if (info.isSearch) {
          currentGroup.searchCount++;
        } else if (info.isREPL) {
          currentGroup.replCount++;
        } else if (info.isRead) {
          currentGroup.readCount++;
        }
      }
    } else {
      // Non-search/read/REPL message - flush current group (completed) and add this message
      flushGroup(false);
      // Skip user tool_result messages — subagent progress messages lack
      // toolUseResult, so UserToolSuccessMessage returns null and the
      // height=1 Box in renderToolUseProgressMessage shows as a blank line.
      if (msg.data.message.type !== 'user') {
        result.push({ type: 'original', message: msg });
      }
    }
  }

  // Flush any remaining group - it's active if the agent is still running
  flushGroup(isAgentRunning);

  return result;
}

const ESTIMATED_LINES_PER_TOOL = 9;
const TERMINAL_BUFFER_LINES = 7;

type Output = z.input<ReturnType<typeof outputSchema>>;

export function AgentPromptDisplay({
  prompt,
  dim: _dim = false,
}: {
  prompt: string;
  theme?: ThemeName; // deprecated, kept for compatibility - Markdown uses useTheme internally
  dim?: boolean; // deprecated, kept for compatibility - dimColor cannot be applied to Box (Markdown returns Box)
}): React.ReactNode {
  return (
    <Box flexDirection="column">
      <Text color="success" bold>
        Prompt:
      </Text>
      <Box paddingLeft={2}>
        <Markdown>{prompt}</Markdown>
      </Box>
    </Box>
  );
}

export function AgentResponseDisplay({
  content,
}: {
  content: { type: string; text: string }[];
  theme?: ThemeName; // deprecated, kept for compatibility - Markdown uses useTheme internally
}): React.ReactNode {
  return (
    <Box flexDirection="column">
      <Text color="success" bold>
        Response:
      </Text>
      {content.map((block: { type: string; text: string }, index: number) => (
        <Box key={index} paddingLeft={2} marginTop={index === 0 ? 0 : 1}>
          <Markdown>{block.text}</Markdown>
        </Box>
      ))}
    </Box>
  );
}

type VerboseAgentTranscriptProps = {
  progressMessages: ProgressMessage<Progress>[];
  tools: Tools;
  verbose: boolean;
};

function VerboseAgentTranscript({ progressMessages, tools, verbose }: VerboseAgentTranscriptProps): React.ReactNode {
  const { lookups: agentLookups, inProgressToolUseIDs } = buildSubagentLookups(
    progressMessages
      .filter((pm): pm is ProgressMessage<AgentToolProgress> => hasProgressMessage(pm.data))
      .map(pm => pm.data),
  );

  // Filter out user tool_result messages that lack toolUseResult.
  // Subagent progress messages don't carry the parsed tool output,
  // so UserToolSuccessMessage returns null and MessageResponse renders
  // a bare ⎿ with no content.
  const filteredMessages = progressMessages.filter((pm): pm is ProgressMessage<AgentToolProgress> => {
    if (!hasProgressMessage(pm.data)) {
      return false;
    }
    const msg = pm.data.message;
    if (msg.type === 'user' && msg.toolUseResult === undefined) {
      return false;
    }
    return true;
  });

  return (
    <>
      {filteredMessages.map(progressMessage => (
        <MessageResponse key={progressMessage.uuid} height={1}>
          <MessageComponent
            message={progressMessage.data.message}
            lookups={agentLookups}
            addMargin={false}
            tools={tools}
            commands={[]}
            verbose={verbose}
            inProgressToolUseIDs={inProgressToolUseIDs}
            progressMessagesForMessage={[]}
            shouldAnimate={false}
            shouldShowDot={false}
            isTranscriptMode={false}
            isStatic={true}
          />
        </MessageResponse>
      ))}
    </>
  );
}

export function renderToolResultMessage(
  data: Output,
  progressMessagesForMessage: ProgressMessage<Progress>[],
  {
    tools,
    verbose,
    theme,
    isTranscriptMode = false,
  }: {
    tools: Tools;
    verbose: boolean;
    theme: ThemeName;
    isTranscriptMode?: boolean;
  },
): React.ReactNode {
  // Remote-launched agents (ant-only) use a private output type not in the
  // public schema. Narrow via the internal discriminant.
  const internal = data as Output | RemoteLaunchedOutput;
  if (internal.status === 'remote_launched') {
    return (
      <Box flexDirection="column">
        <MessageResponse height={1}>
          <Text>
            Remote agent launched{' '}
            <Text dimColor>
              · {internal.taskId} · {internal.sessionUrl}
            </Text>
          </Text>
        </MessageResponse>
      </Box>
    );
  }
  if (data.status === 'async_launched') {
    const { prompt } = data;
    return (
      <Box flexDirection="column">
        <MessageResponse height={1}>
          <Text>
            Backgrounded agent
            {!isTranscriptMode && (
              <Text dimColor>
                {' ('}
                <Byline>
                  <KeyboardShortcutHint shortcut="↓" action="manage" />
                  {prompt && (
                    <ConfigurableShortcutHint
                      action="app:toggleTranscript"
                      context="Global"
                      fallback="ctrl+o"
                      description="expand"
                    />
                  )}
                </Byline>
                {')'}
              </Text>
            )}
          </Text>
        </MessageResponse>
        {isTranscriptMode && prompt && (
          <MessageResponse>
            <AgentPromptDisplay prompt={prompt} theme={theme} />
          </MessageResponse>
        )}
      </Box>
    );
  }

  if (data.status !== 'completed') {
    return null;
  }

  const { agentId, totalDurationMs, totalToolUseCount, totalTokens, usage, content, prompt } = data;
  const result = [
    totalToolUseCount === 1 ? '1 tool use' : `${totalToolUseCount} tool uses`,
    formatNumber(totalTokens) + ' tokens',
    formatDuration(totalDurationMs),
  ];

  const completionMessage = `Done (${result.join(' · ')})`;

  const finalAssistantMessage = createAssistantMessage({
    content: completionMessage,
    usage: { ...usage, inference_geo: null, iterations: null, speed: null } as unknown as BetaUsage,
  });

  return (
    <Box flexDirection="column">
      {process.env.USER_TYPE === 'ant' && (
        <MessageResponse>
          <Text color="warning">[ANT-ONLY] API calls: {getDisplayPath(getDumpPromptsPath(agentId))}</Text>
        </MessageResponse>
      )}
      {isTranscriptMode && prompt && (
        <MessageResponse>
          <AgentPromptDisplay prompt={prompt} theme={theme} />
        </MessageResponse>
      )}
      {isTranscriptMode ? (
        <SubAgentProvider>
          <VerboseAgentTranscript progressMessages={progressMessagesForMessage} tools={tools} verbose={verbose} />
        </SubAgentProvider>
      ) : null}
      {isTranscriptMode && content && content.length > 0 && (
        <MessageResponse>
          <AgentResponseDisplay content={content} theme={theme} />
        </MessageResponse>
      )}
      <MessageResponse height={1}>
        <MessageComponent
          message={finalAssistantMessage}
          lookups={EMPTY_LOOKUPS}
          addMargin={false}
          tools={tools}
          commands={[]}
          verbose={verbose}
          inProgressToolUseIDs={new Set()}
          progressMessagesForMessage={[]}
          shouldAnimate={false}
          shouldShowDot={false}
          isTranscriptMode={false}
          isStatic={true}
        />
      </MessageResponse>
      {!isTranscriptMode && (
        <Text dimColor>
          {'  '}
          <CtrlOToExpand />
        </Text>
      )}
    </Box>
  );
}

export function renderToolUseMessage({
  description,
  prompt,
}: Partial<{
  description: string;
  prompt: string;
}>): React.ReactNode {
  if (!description || !prompt) {
    return null;
  }
  return description;
}

export function renderToolUseTag(
  input: Partial<{
    description: string;
    prompt: string;
    subagent_type: string;
    model?: ModelAlias;
  }>,
): React.ReactNode {
  const tags: React.ReactNode[] = [];

  if (input.model) {
    const mainModel = getMainLoopModel();
    const agentModel = parseUserSpecifiedModel(input.model);
    if (agentModel !== mainModel) {
      tags.push(
        <Box key="model" flexWrap="nowrap" marginLeft={1}>
          <Text dimColor>{renderModelName(agentModel)}</Text>
        </Box>,
      );
    }
  }

  if (tags.length === 0) {
    return null;
  }

  return <>{tags}</>;
}

const INITIALIZING_TEXT = 'Initializing…';

export function renderToolUseProgressMessage(
  progressMessages: ProgressMessage<Progress>[],
  {
    tools,
    verbose,
    terminalSize,
    inProgressToolCallCount,
    isTranscriptMode = false,
  }: {
    tools: Tools;
    verbose: boolean;
    terminalSize?: { columns: number; rows: number };
    inProgressToolCallCount?: number;
    isTranscriptMode?: boolean;
  },
): React.ReactNode {
  if (!progressMessages.length) {
    return (
      <MessageResponse height={1}>
        <Text dimColor>{INITIALIZING_TEXT}</Text>
      </MessageResponse>
    );
  }

  // Checks to see if we should show a super condensed progress message summary.
  // This prevents flickers when the terminal size is too small to render all the dynamic content
  const toolToolRenderLinesEstimate = (inProgressToolCallCount ?? 1) * ESTIMATED_LINES_PER_TOOL + TERMINAL_BUFFER_LINES;
  const shouldUseCondensedMode =
    !isTranscriptMode && terminalSize && terminalSize.rows && terminalSize.rows < toolToolRenderLinesEstimate;

  const getProgressStats = () => {
    const toolUseCount = count(progressMessages, msg => {
      if (!hasProgressMessage(msg.data)) {
        return false;
      }
      const message = msg.data.message;
      return message.message.content.some((content: BetaContentBlock) => content.type === 'tool_use');
    });

    const latestAssistant = progressMessages.findLast(
      (msg): msg is ProgressMessage<AgentToolProgress> =>
        hasProgressMessage(msg.data) && msg.data.message.type === 'assistant',
    );

    let tokens = null;
    if (latestAssistant?.data.message.type === 'assistant') {
      const usage = latestAssistant.data.message.message.usage;
      tokens =
        (usage.cache_creation_input_tokens ?? 0) +
        (usage.cache_read_input_tokens ?? 0) +
        usage.input_tokens +
        usage.output_tokens;
    }

    return { toolUseCount, tokens };
  };

  if (shouldUseCondensedMode) {
    const { toolUseCount, tokens } = getProgressStats();

    return (
      <MessageResponse height={1}>
        <Text dimColor>
          In progress… · <Text bold>{toolUseCount}</Text> tool {toolUseCount === 1 ? 'use' : 'uses'}
          {tokens && ` · ${formatNumber(tokens)} tokens`} ·{' '}
          <ConfigurableShortcutHint
            action="app:toggleTranscript"
            context="Global"
            fallback="ctrl+o"
            description="expand"
            parens
          />
        </Text>
      </MessageResponse>
    );
  }

  // Process messages to group consecutive search/read operations into summaries (ants only)
  // isAgentRunning=true since this is the progress view while the agent is still running
  const processedMessages = processProgressMessages(progressMessages, tools, true);

  // For display, take the last few processed messages
  const displayedMessages = isTranscriptMode
    ? processedMessages
    : processedMessages.slice(-MAX_PROGRESS_MESSAGES_TO_SHOW);

  // Count hidden tool uses specifically (not all messages) to match the
  // final "Done (N tool uses)" count. Each tool use generates multiple
  // progress messages (tool_use + tool_result + text), so counting all
  // hidden messages inflates the number shown to the user.
  const hiddenMessages = isTranscriptMode
    ? []
    : processedMessages.slice(0, Math.max(0, processedMessages.length - MAX_PROGRESS_MESSAGES_TO_SHOW));
  const hiddenToolUseCount = count(hiddenMessages, m => {
    if (m.type === 'summary') {
      return m.searchCount + m.readCount + m.replCount > 0;
    }
    const data = m.message.data;
    if (!hasProgressMessage(data)) {
      return false;
    }
    return data.message.message.content.some((content: BetaContentBlock) => content.type === 'tool_use');
  });

  const firstData = progressMessages[0]?.data;
  const prompt = firstData && hasProgressMessage(firstData) ? firstData.prompt : undefined;

  // After grouping, displayedMessages can be empty when the only progress so
  // far is an assistant tool_use for a search/read op (grouped but not yet
  // counted, since counts increment on tool_result). Fall back to the
  // initializing text so MessageResponse doesn't render a bare ⎿.
  if (displayedMessages.length === 0 && !(isTranscriptMode && prompt)) {
    return (
      <MessageResponse height={1}>
        <Text dimColor>{INITIALIZING_TEXT}</Text>
      </MessageResponse>
    );
  }

  const { lookups: subagentLookups, inProgressToolUseIDs: collapsedInProgressIDs } = buildSubagentLookups(
    progressMessages
      .filter((pm): pm is ProgressMessage<AgentToolProgress> => hasProgressMessage(pm.data))
      .map(pm => pm.data),
  );

  return (
    <MessageResponse>
      <Box flexDirection="column">
        <SubAgentProvider>
          {isTranscriptMode && prompt && (
            <Box marginBottom={1}>
              <AgentPromptDisplay prompt={prompt} />
            </Box>
          )}
          {displayedMessages.map(processed => {
            if (processed.type === 'summary') {
              // Render summary for grouped search/read/REPL operations using shared formatting
              const summaryText = getSearchReadSummaryText(
                processed.searchCount,
                processed.readCount,
                processed.isActive,
                processed.replCount,
              );
              return (
                <Box key={processed.uuid} height={1} overflow="hidden">
                  <Text dimColor>{summaryText}</Text>
                </Box>
              );
            }
            // Render original message without height=1 wrapper so null
            // content (tool not found, renderToolUseMessage returns null)
            // doesn't leave a blank line. Tool call headers are single-line
            // anyway so truncation isn't needed.
            return (
              <MessageComponent
                key={processed.message.uuid}
                message={processed.message.data.message}
                lookups={subagentLookups}
                addMargin={false}
                tools={tools}
                commands={[]}
                verbose={verbose}
                inProgressToolUseIDs={collapsedInProgressIDs}
                progressMessagesForMessage={[]}
                shouldAnimate={false}
                shouldShowDot={false}
                style="condensed"
                isTranscriptMode={false}
                isStatic={true}
              />
            );
          })}
        </SubAgentProvider>
        {hiddenToolUseCount > 0 && (
          <Text dimColor>
            +{hiddenToolUseCount} more tool {hiddenToolUseCount === 1 ? 'use' : 'uses'} <CtrlOToExpand />
          </Text>
        )}
      </Box>
    </MessageResponse>
  );
}

export function renderToolUseRejectedMessage(
  _input: { description: string; prompt: string; subagent_type: string },
  {
    progressMessagesForMessage,
    tools,
    verbose,
    isTranscriptMode,
  }: {
    columns: number;
    messages: Message[];
    style?: 'condensed';
    theme: ThemeName;
    progressMessagesForMessage: ProgressMessage<Progress>[];
    tools: Tools;
    verbose: boolean;
    isTranscriptMode?: boolean;
  },
): React.ReactNode {
  // Get agentId from progress messages if available (agent was running before rejection)
  const firstData = progressMessagesForMessage[0]?.data;
  const agentId = firstData && hasProgressMessage(firstData) ? firstData.agentId : undefined;

  return (
    <>
      {process.env.USER_TYPE === 'ant' && agentId && (
        <MessageResponse>
          <Text color="warning">[ANT-ONLY] API calls: {getDisplayPath(getDumpPromptsPath(agentId))}</Text>
        </MessageResponse>
      )}
      {renderToolUseProgressMessage(progressMessagesForMessage, {
        tools,
        verbose,
        isTranscriptMode,
      })}
      <FallbackToolUseRejectedMessage />
    </>
  );
}

export function renderToolUseErrorMessage(
  result: ToolResultBlockParam['content'],
  {
    progressMessagesForMessage,
    tools,
    verbose,
    isTranscriptMode,
  }: {
    progressMessagesForMessage: ProgressMessage<Progress>[];
    tools: Tools;
    verbose: boolean;
    isTranscriptMode?: boolean;
  },
): React.ReactNode {
  return (
    <>
      {renderToolUseProgressMessage(progressMessagesForMessage, {
        tools,
        verbose,
        isTranscriptMode,
      })}
      <FallbackToolUseErrorMessage result={result} verbose={verbose} />
    </>
  );
}

function calculateAgentStats(progressMessages: ProgressMessage<Progress>[]): {
  toolUseCount: number;
  tokens: number | null;
} {
  const toolUseCount = count(progressMessages, msg => {
    if (!hasProgressMessage(msg.data)) {
      return false;
    }
    const message = msg.data.message;
    return (
      message.type === 'user' &&
      message.message.content.some((content: BetaContentBlock) => content.type === 'tool_result')
    );
  });

  const latestAssistant = progressMessages.findLast(
    (msg): msg is ProgressMessage<AgentToolProgress> =>
      hasProgressMessage(msg.data) && msg.data.message.type === 'assistant',
  );

  let tokens = null;
  if (latestAssistant?.data.message.type === 'assistant') {
    const usage = latestAssistant.data.message.message.usage;
    tokens =
      (usage.cache_creation_input_tokens ?? 0) +
      (usage.cache_read_input_tokens ?? 0) +
      usage.input_tokens +
      usage.output_tokens;
  }

  return { toolUseCount, tokens };
}

export function renderGroupedAgentToolUse(
  toolUses: Array<{
    param: ToolUseBlockParam;
    isResolved: boolean;
    isError: boolean;
    isInProgress: boolean;
    progressMessages: ProgressMessage<Progress>[];
    result?: {
      param: ToolResultBlockParam;
      output: Output;
    };
  }>,
  options: {
    shouldAnimate: boolean;
    tools: Tools;
  },
): React.ReactNode | null {
  const { shouldAnimate, tools } = options;

  // Calculate stats for each agent
  const agentStats = toolUses.map(({ param, isResolved, isError, progressMessages, result }) => {
    const stats = calculateAgentStats(progressMessages);
    const lastToolInfo = extractLastToolInfo(progressMessages, tools);
    const parsedInput = inputSchema().safeParse(param.input);

    // teammate_spawned is not part of the exported Output type (cast through unknown
    // for dead code elimination), so check via string comparison on the raw value
    const isTeammateSpawn = (result?.output?.status as string) === 'teammate_spawned';

    // For teammate spawns, show @name with type in parens and description as status
    let agentType: string;
    let description: string | undefined;
    let color: keyof Theme | undefined;
    let descriptionColor: keyof Theme | undefined;
    let taskDescription: string | undefined;
    if (isTeammateSpawn && parsedInput.success && parsedInput.data.name) {
      agentType = `@${parsedInput.data.name}`;
      const subagentType = parsedInput.data.subagent_type;
      description = isCustomSubagentType(subagentType) ? subagentType : undefined;
      taskDescription = parsedInput.data.description;
      // Use the custom agent definition's color on the type, not the name
      descriptionColor = isCustomSubagentType(subagentType) ? getAgentColor(subagentType) : undefined;
    } else {
      agentType = parsedInput.success ? userFacingName(parsedInput.data) : 'Agent';
      description = parsedInput.success ? parsedInput.data.description : undefined;
      color = parsedInput.success ? userFacingNameBackgroundColor(parsedInput.data) : undefined;
      taskDescription = undefined;
    }

    // Check if this was launched as a background agent OR backgrounded mid-execution
    const launchedAsAsync =
      parsedInput.success && 'run_in_background' in parsedInput.data && parsedInput.data.run_in_background === true;
    const outputStatus = (result?.output as { status?: string } | undefined)?.status;
    const backgroundedMidExecution = outputStatus === 'async_launched' || outputStatus === 'remote_launched';
    const isAsync = launchedAsAsync || backgroundedMidExecution || isTeammateSpawn;

    const name = parsedInput.success ? parsedInput.data.name : undefined;

    return {
      id: param.id,
      agentType,
      description,
      toolUseCount: stats.toolUseCount,
      tokens: stats.tokens,
      isResolved,
      isError,
      isAsync,
      color,
      descriptionColor,
      lastToolInfo,
      taskDescription,
      name,
    };
  });

  const anyUnresolved = toolUses.some(t => !t.isResolved);
  const anyError = toolUses.some(t => t.isError);
  const allComplete = !anyUnresolved;

  // Check if all agents are the same type
  const allSameType = agentStats.length > 0 && agentStats.every(stat => stat.agentType === agentStats[0]?.agentType);
  const commonType = allSameType && agentStats[0]?.agentType !== 'Agent' ? agentStats[0]?.agentType : null;

  // Check if all resolved agents are async (background)
  const allAsync = agentStats.every(stat => stat.isAsync);

  return (
    <Box flexDirection="column" marginTop={1}>
      <Box flexDirection="row">
        <ToolUseLoader shouldAnimate={shouldAnimate && anyUnresolved} isUnresolved={anyUnresolved} isError={anyError} />
        <Text>
          {allComplete ? (
            allAsync ? (
              <>
                <Text bold>{toolUses.length}</Text> background agents launched{' '}
                <Text dimColor>
                  <KeyboardShortcutHint shortcut="↓" action="manage" parens />
                </Text>
              </>
            ) : (
              <>
                <Text bold>{toolUses.length}</Text> {commonType ? `${commonType} agents` : 'agents'} finished
              </>
            )
          ) : (
            <>
              Running <Text bold>{toolUses.length}</Text> {commonType ? `${commonType} agents` : 'agents'}…
            </>
          )}{' '}
        </Text>
        {!allAsync && <CtrlOToExpand />}
      </Box>
      {agentStats.map((stat, index) => (
        <AgentProgressLine
          key={stat.id}
          agentType={stat.agentType}
          description={stat.description}
          descriptionColor={stat.descriptionColor}
          taskDescription={stat.taskDescription}
          toolUseCount={stat.toolUseCount}
          tokens={stat.tokens}
          color={stat.color}
          isLast={index === agentStats.length - 1}
          isResolved={stat.isResolved}
          isError={stat.isError}
          isAsync={stat.isAsync}
          shouldAnimate={shouldAnimate}
          lastToolInfo={stat.lastToolInfo}
          hideType={allSameType}
          name={stat.name}
        />
      ))}
    </Box>
  );
}

export function userFacingName(
  input:
    | Partial<{
        description: string;
        prompt: string;
        subagent_type: string;
        name: string;
        team_name: string;
      }>
    | undefined,
): string {
  if (input?.subagent_type && input.subagent_type !== GENERAL_PURPOSE_AGENT.agentType) {
    // Display "worker" agents as "Agent" for cleaner UI
    if (input.subagent_type === 'worker') {
      return 'Agent';
    }
    return input.subagent_type;
  }
  return 'Agent';
}

export function userFacingNameBackgroundColor(
  input: Partial<{ description: string; prompt: string; subagent_type: string }> | undefined,
): keyof Theme | undefined {
  if (!input?.subagent_type) {
    return undefined;
  }

  // Get the color for this agent
  return getAgentColor(input.subagent_type);
}

export function extractLastToolInfo(progressMessages: ProgressMessage<Progress>[], tools: Tools): string | null {
  // Build tool_use lookup from all progress messages (needed for reverse iteration)
  const toolUseByID = new Map<string, ToolUseBlockParam>();
  for (const pm of progressMessages) {
    if (!hasProgressMessage(pm.data)) {
      continue;
    }
    if (pm.data.message.type === 'assistant') {
      for (const c of pm.data.message.message.content) {
        if (c.type === 'tool_use') {
          toolUseByID.set(c.id, c as ToolUseBlockParam);
        }
      }
    }
  }

  // Count trailing consecutive search/read operations from the end
  let searchCount = 0;
  let readCount = 0;
  for (let i = progressMessages.length - 1; i >= 0; i--) {
    const msg = progressMessages[i]!;
    if (!hasProgressMessage(msg.data)) {
      continue;
    }
    const info = getSearchOrReadInfo(msg, tools, toolUseByID);
    if (info && (info.isSearch || info.isRead)) {
      // Only count tool_result messages to avoid double counting
      if (msg.data.message.type === 'user') {
        if (info.isSearch) {
          searchCount++;
        } else if (info.isRead) {
          readCount++;
        }
      }
    } else {
      break;
    }
  }

  if (searchCount + readCount >= 2) {
    return getSearchReadSummaryText(searchCount, readCount, true);
  }

  // Find the last tool_result message
  const lastToolResult = progressMessages.findLast((msg): msg is ProgressMessage<AgentToolProgress> => {
    if (!hasProgressMessage(msg.data)) {
      return false;
    }
    const message = msg.data.message;
    return message.type === 'user' && message.message.content.some((c: BetaContentBlock) => c.type === 'tool_result');
  });

  if (lastToolResult?.data.message.type === 'user') {
    const toolResultBlock = lastToolResult.data.message.message.content.find(
      (c: BetaContentBlock) => c.type === 'tool_result',
    );

    if (toolResultBlock?.type === 'tool_result') {
      // Look up the corresponding tool_use — already indexed above
      const toolUseBlock = toolUseByID.get(toolResultBlock.tool_use_id);

      if (toolUseBlock) {
        const tool = findToolByName(tools, toolUseBlock.name);
        if (!tool) {
          return toolUseBlock.name; // Fallback to raw name
        }

        const input = toolUseBlock.input as Record<string, unknown>;
        const parsedInput = tool.inputSchema.safeParse(input);

        // Get user-facing tool name
        const userFacingToolName = tool.userFacingName(parsedInput.success ? parsedInput.data : undefined);

        // Try to get summary from the tool itself
        if (tool.getToolUseSummary) {
          const summary = tool.getToolUseSummary(parsedInput.success ? parsedInput.data : undefined);
          if (summary) {
            return `${userFacingToolName}: ${summary}`;
          }
        }

        // Default: just show user-facing tool name
        return userFacingToolName;
      }
    }
  }

  return null;
}

function isCustomSubagentType(subagentType: string | undefined): subagentType is string {
  return !!subagentType && subagentType !== GENERAL_PURPOSE_AGENT.agentType && subagentType !== 'worker';
}
