import { LLMMessage, LLMProviderConfig } from '../plugins/plugin_entry.js';
import type { ThinkLevel } from '../plugins/plugin_entry.js';
import { AnyAgentTool, ToolResultContent } from '../tools/common.js';
import { runAttempt, AttemptResult } from './attempt.js';
import { parseModelRef } from './model_selection.js';
import { resolveThinkingDefault } from './thinking.js';
import { TodoStore } from './todo_store.js';
import { todoToolSchema, executeTodo } from '../tools/todo_tool.js';
import { buildLLMMessages } from './prompt_builder.js';
import type { RuntimeProcessEvent } from './process_events.js';

export interface AgentConfig {
  name: string;
  systemPrompt: string;
  model: string;
  apiKey: string;
  baseUrl?: string;
  channelId?: number;
  channelName?: string;
  channelSupplier?: string;
  temperature?: number;
  maxTokens?: number;
  maxToolRounds?: number;
  skills?: string[];
  defaultThinkLevel?: ThinkLevel;
}

export interface AgentRunParams {
  agentConfig: AgentConfig;
  tools: AnyAgentTool[];
  toolNames?: string[];
  userMessage: string;
  userMessageMetadata?: Record<string, unknown>;
  history?: LLMMessage[];
  onToken?: (text: string) => void;
  onThinking?: (text: string) => void;
  onToolCall?: (name: string, args: Record<string, unknown>, toolCallId?: string) => void;
  onToolResult?: (name: string, result: string, toolCallId?: string, rawResult?: ToolResultContent | string) => void;
  onToolProcessEvent?: (input: {
    toolCallId?: string;
    operationId: string;
    toolName: string;
    args: Record<string, unknown>;
    event: RuntimeProcessEvent;
  }) => void;
  beforeToolCall?: (input: {
    toolCallId?: string;
    name: string;
    label?: string;
    args: Record<string, unknown>;
    tool?: AnyAgentTool;
    messages: LLMMessage[];
  }) => Promise<{ args?: Record<string, unknown>; block?: boolean; reason?: string } | void>;
  afterToolCall?: (input: {
    toolCallId?: string;
    name: string;
    label?: string;
    args: Record<string, unknown>;
    result: string;
    rawResult?: ToolResultContent | string;
    isError: boolean;
    tool?: AnyAgentTool;
    messages: LLMMessage[];
  }) => Promise<{ result?: string; rawResult?: ToolResultContent | string } | void>;
  sessionThinkLevel?: ThinkLevel;
  todoStore?: TodoStore;
  onTodoUpdate?: (data: { todos: any[]; summary: any }) => void;
  onThinkingDelta?: (text: string) => void;
  onClarifyRequest?: (question: string, choices?: string[]) => Promise<string>;
}

export async function runAgent(params: AgentRunParams): Promise<AttemptResult> {
  const {
    agentConfig,
    tools,
    userMessage,
    history = [],
    onToken,
    onThinking,
    onToolCall,
    onToolResult,
    onToolProcessEvent,
    beforeToolCall,
    afterToolCall,
  } = params;
  const onThinkingEvent = params.onThinkingDelta ?? onThinking;

  const modelRef = parseModelRef(agentConfig.model);
  const thinkLevel = params.sessionThinkLevel
    ?? params.agentConfig.defaultThinkLevel
    ?? resolveThinkingDefault(modelRef.model);
  const llmConfig: LLMProviderConfig = {
    provider: modelRef.provider,
    model: modelRef.model,
    apiKey: agentConfig.apiKey,
    baseUrl: agentConfig.baseUrl,
    channelId: agentConfig.channelId,
    channelName: agentConfig.channelName,
    channelSupplier: agentConfig.channelSupplier,
    temperature: agentConfig.temperature,
    maxTokens: agentConfig.maxTokens,
    thinkLevel,
  };

  const messages: LLMMessage[] = buildLLMMessages(
    agentConfig.systemPrompt,
    history,
    { content: userMessage, metadata: params.userMessageMetadata },
    params.toolNames || tools.map(tool => tool.name),
  );

  const todoStore = params.todoStore ?? new TodoStore();
  const enabledToolNames = new Set(params.toolNames || tools.map(tool => tool.name));
  const allTools = [...tools];
  if (enabledToolNames.has('todo')) {
    const todoTool: AnyAgentTool = {
      ...todoToolSchema,
      label: '任务规划',
      visibility: 'internal',
      execute: async (_id: string, args: any) => {
        const result = executeTodo(todoStore, args);
        params.onTodoUpdate?.(result);
        return JSON.stringify(result);
      },
    };
    allTools.push(todoTool);
  }

  return runAttempt({
    messages,
    tools: allTools,
    config: llmConfig,
    maxToolRounds: agentConfig.maxToolRounds,
    onToken,
    onThinking: onThinkingEvent,
    onToolCall,
    onToolResult,
    onToolProcessEvent,
    beforeToolCall,
    afterToolCall,
    onClarifyRequest: params.onClarifyRequest,
  });
}
