import type { Message, ToolCall, ToolDef } from '../../llm/openai-compat.js';
import { logger } from '../../logger.js';
import { type ToolContext } from '../tools/index.js';
import { ToolResultCache } from '../context/tool-result-cache.js';
import type { AgentLoopCallbacks } from './types.js';
import { isAllowedRegularTool, executeRegularToolCallCached, type ToolCallResult } from './tool-execution.js';
import { parseInteractiveBrowseWaitingHuman } from './interactive-browse-result.js';
import { recordToolResult } from './tool-result-recorder.js';

const PARALLEL_SAFE_TOOL_NAMES = new Set([
  'Read',
  'Glob',
  'Grep',
  'WebSearch',
  'WebFetch',
  'ReadImage',
]);

export interface DispatchedImage { dataUrl: string; label?: string }

export type DispatchOutcome =
  | {
      status: 'completed';
      pendingImages: DispatchedImage[];
      regularToolsUsedDelta: number;
    }
  | {
      status: 'waiting_human';
      output: string;
      waitReason: string;
      sessionId: string;
    };

// Read は拡張子で office/pdf/msg へ内部ディスパッチする。.msg は添付保存の副作用が
// あるので並列化しない（旧 ReadMsg が並列安全でなかったのを保全）。それ以外の Read
// (テキスト/Excel/PDF 抽出等) は副作用なしで並列実行できる。
export const canExecuteInParallel = (call: ToolCall, regularTools: ToolDef[]): boolean => {
  const toolName = call.function.name;
  if (!isAllowedRegularTool(toolName, regularTools) || !PARALLEL_SAFE_TOOL_NAMES.has(toolName)) {
    return false;
  }
  if (toolName === 'Read') {
    try {
      const args = JSON.parse(call.function.arguments) as Record<string, unknown>;
      const fp = typeof args.file_path === 'string' ? args.file_path : '';
      if (fp.toLowerCase().endsWith('.msg')) return false;
    } catch {
      // 引数が壊れている場合は名前ベースの判定（safe）に委ねる
    }
  }
  return true;
};

export async function dispatchRegularToolCalls(
  regularCalls: ToolCall[],
  regularTools: ToolDef[],
  toolCtx: ToolContext,
  messages: Message[],
  callbacks: AgentLoopCallbacks | undefined,
  initialRegularToolsUsed: number,
  toolResultCache: ToolResultCache | undefined,
  movementName: string,
): Promise<DispatchOutcome> {
  const pendingImages: DispatchedImage[] = [];
  let regularToolsUsed = initialRegularToolsUsed;

  const runOne = (call: ToolCall): Promise<ToolCallResult> =>
    executeRegularToolCallCached(call, regularTools, toolCtx, toolResultCache, movementName);

  const recordResult = (toolName: string, result: ToolCallResult): void => {
    regularToolsUsed = recordToolResult(toolName, result, messages, callbacks, pendingImages, regularToolsUsed);
  };

  for (let index = 0; index < regularCalls.length;) {
    const tc = regularCalls[index]!;
    const toolName = tc.function.name;

    if (canExecuteInParallel(tc, regularTools)) {
      const batch: ToolCall[] = [];
      while (index < regularCalls.length) {
        const candidate = regularCalls[index]!;
        if (!canExecuteInParallel(candidate, regularTools)) break;
        batch.push(candidate);
        index++;
      }
      logger.info(`[agent-loop] executing ${batch.length} parallel tool call(s): ${batch.map((c) => c.function.name).join(',')}`);
      const batchResults = await Promise.all(batch.map(runOne));
      for (let bi = 0; bi < batchResults.length; bi++) {
        recordResult(batch[bi]!.function.name, batchResults[bi]!);
      }
      continue;
    }

    const sequentialResult = await runOne(tc);
    recordResult(toolName, sequentialResult);

    const waitingHuman = parseInteractiveBrowseWaitingHuman(toolName, sequentialResult.result);
    if (waitingHuman) {
      return {
        status: 'waiting_human',
        output: sequentialResult.result,
        waitReason: waitingHuman.waitReason,
        sessionId: waitingHuman.sessionId,
      };
    }
    index++;
  }

  return {
    status: 'completed',
    pendingImages,
    regularToolsUsedDelta: regularToolsUsed - initialRegularToolsUsed,
  };
}
