<?php

namespace XZD\AI\V5\Agents;

use XZD\AI\V5\AI\Types;
use XZD\AI\V5\Core\Logger;

/**
 * AgentLoop — ReAct 对话循环 (Thought → Tool → Observe → Response)
 *
 * V3.2: 支持 guestToken 访客令牌，所有 SessionManager 调用携带 userId+guestToken。
 */
class AgentLoop
{
    private BaseAgent $agent;
    private ActionDispatcher $dispatcher;
    private SessionManager $sessionManager;
    private int $maxTurns;

    public function __construct(BaseAgent $agent, ActionDispatcher $dispatcher, int $maxTurns = 5)
    {
        $this->agent = $agent;
        $this->dispatcher = $dispatcher;
        $this->sessionManager = new SessionManager();
        $this->maxTurns = $maxTurns;
    }

    /**
     * 执行完整的 ReAct 循环
     *
     * @return array ['content'=>'...','tool_calls'=>[...],'turns'=>N,'messages'=>[...]]
     */
    public function run(string $sessionKey, string $userMessage, string $role, int $userId, string $guestToken = ''): array
    {
        $systemPrompt = $this->agent->getSystemPrompt($role, $userId);
        $userPrefix   = $this->agent->getConfigContextPrefix($userId) . $this->agent->getUserContextPrefix($role, $userId);
        $noMemory = \XZD\AI\V5\Core\Config::instance()->get('chat.no_memory', true);
        $history = $noMemory ? [] : $this->sessionManager->getRecentMessages($sessionKey, 20, $userId, $guestToken);

        $messages = array_merge(
            [Types::system($systemPrompt)],
            $history,
            [Types::user($userPrefix . $userMessage)]
        );

        $finalContent = '';
        $allToolCalls = [];
        $turn = 0;

        for ($turn = 0; $turn < $this->maxTurns; $turn++) {
            $response = $this->agent->chat($role, $messages);
            $content = $response['content'] ?? '';
            $toolCalls = $response['tool_calls'] ?? null;

            if (empty($toolCalls)) {
                $finalContent = $content;
                $this->sessionManager->addMessage($sessionKey, 'assistant', $finalContent, [
                    'model'        => $response['model'] ?? '',
                    'input_tokens'  => $response['input_tokens'] ?? 0,
                    'output_tokens' => $response['output_tokens'] ?? 0,
                ], $userId, $guestToken);

                $this->sessionManager->touchSession($sessionKey, [
                    'title'  => mb_substr($userMessage, 0, 50),
                    'model'  => $response['model'] ?? '',
                    'tokens' => ($response['input_tokens'] ?? 0) + ($response['output_tokens'] ?? 0),
                ], $userId, $guestToken);

                break;
            }

            $allToolCalls[] = ['turn' => $turn, 'calls' => $toolCalls];

            $messages[] = [
                'role'       => 'assistant',
                'content'    => $content,
                'tool_calls' => $toolCalls,
            ];

            $toolResults = [];
            $pendingConfirms = [];
            $executedToolCalls = [];
            foreach ($toolCalls as $i => $tc) {
                $funcName = $tc['function']['name'] ?? '';
                $funcArgs = json_decode($tc['function']['arguments'] ?? '{}', true) ?: [];
                $tcId = $tc['id'] ?? "call_{$turn}_{$i}";

                Logger::info('agent_loop_tool', "Turn {$turn}: {$funcName}", [
                    'session' => $sessionKey,
                    'args'    => $funcArgs,
                ]);

                $result = $this->dispatcher->dispatch($funcName, $funcArgs, $userId, $sessionKey);

                $tool = \XZD\AI\V5\Tools\ToolRegistry::instance()->get($funcName);
                Logger::operation([
                    'tool_id'     => $funcName,
                    'tool_name'   => $tool ? $tool->name : $funcName,
                    'category'    => $tool ? $tool->category : 'unknown',
                    'params'      => $funcArgs,
                    'result'      => $result,
                    'status'      => $result['success'] ? 'success' : 'failed',
                    'error_message' => $result['success'] ? null : ($result['message'] ?? ''),
                    'user_id'     => $userId,
                    'session_key' => $sessionKey,
                ]);

                if (!empty($result['confirm_required'])) {
                    $pendingConfirms[] = [
                        'tool_call'      => $tc,
                        'confirm_key'    => $result['confirm_key'] ?? '',
                        'confirm_message'=> $result['confirm_message'] ?? '请确认操作',
                        'confirm_detail' => $result['confirm_detail'] ?? '',
                    ];
                    continue;
                }

                $executedToolCalls[] = $tc;

                if ($result['success']) {
                    $payload = ['success' => true];
                    if (isset($result['data'])) $payload['data'] = $result['data'];
                    if (isset($result['message'])) $payload['message'] = $result['message'];
                    $resultContent = json_encode($payload, JSON_UNESCAPED_UNICODE);
                } else {
                    $resultContent = json_encode([
                        'success' => false,
                        'error'   => $result['message'] ?? '未知错误',
                        'code'    => $result['code'] ?? 'unknown',
                        'confirm_required' => $result['confirm_required'] ?? false,
                    ], JSON_UNESCAPED_UNICODE);
                }

                $messages[] = Types::tool($tcId, $resultContent);
                $toolResults[] = [
                    'tool_call_id' => $tcId,
                    'result'       => $result,
                ];

                $this->sessionManager->addMessage($sessionKey, 'tool', $resultContent, [
                    'tool_call_id' => $tcId,
                ], $userId, $guestToken);
            }

            $this->sessionManager->addMessage($sessionKey, 'assistant', $content, [
                'tool_calls'    => !empty($executedToolCalls) ? $executedToolCalls : null,
                'model'         => $response['model'] ?? '',
                'input_tokens'  => $response['input_tokens'] ?? 0,
                'output_tokens' => $response['output_tokens'] ?? 0,
            ], $userId, $guestToken);

            if (!empty($pendingConfirms)) {
                $confirmKeys = array_column($pendingConfirms, 'confirm_key');
                $confirmMessages = array_column($pendingConfirms, 'confirm_message');
                $confirmDetails = array_column($pendingConfirms, 'confirm_detail');
                $pendingCalls = array_column($pendingConfirms, 'tool_call');

                return [
                    'content'          => implode("\n", $confirmMessages),
                    'confirm_required' => true,
                    'confirm_keys'     => $confirmKeys,
                    'confirm_key'      => $confirmKeys[0],
                    'confirm_message'  => implode("\n\n", $confirmMessages),
                    'confirm_detail'   => implode("\n---\n", $confirmDetails),
                    'pending_tool_calls' => $pendingCalls,
                    'tool_calls'       => $allToolCalls,
                    'turns'            => $turn + 1,
                ];
            }

            if ($turn === $this->maxTurns - 1) {
                $finalContent = $content ?: '处理完成，但达到了最大执行轮次。';
            }
        }

        if ($turn > 0) {
            $this->sessionManager->touchSession($sessionKey, [
                'title'  => mb_substr($userMessage, 0, 50),
            ], $userId, $guestToken);
        }

        return [
            'content'    => $finalContent ?: '处理完成',
            'tool_calls' => $allToolCalls,
            'turns'      => $turn,
            'messages'   => $messages,
        ];
    }

    /**
     * 仅执行工具调用（用于确认后恢复执行）
     */
    public function executeToolsOnly(array $confirmedToolCalls, int $userId, string $sessionKey): array
    {
        $toolResults = [];
        foreach ($confirmedToolCalls as $i => $tc) {
            $funcName = $tc['function']['name'] ?? '';
            $funcArgs = json_decode($tc['function']['arguments'] ?? '{}', true) ?: [];
            $tcId = $tc['id'] ?? "confirm_call_{$i}";

            $result = $this->dispatcher->dispatch($funcName, $funcArgs, $userId, $sessionKey);
            $toolResults[] = [
                'tool_call_id' => $tcId,
                'result'       => $result,
            ];
        }
        return $toolResults;
    }

    /**
     * 流式执行 ReAct 循环
     */
    public function runStream(string $sessionKey, string $userMessage, string $role, int $userId, string $guestToken = ''): \Generator
    {
        $systemPrompt = $this->agent->getSystemPrompt($role, $userId);
        $userPrefix   = $this->agent->getConfigContextPrefix($userId) . $this->agent->getUserContextPrefix($role, $userId);
        $noMemory = \XZD\AI\V5\Core\Config::instance()->get('chat.no_memory', true);
        $history = $noMemory ? [] : $this->sessionManager->getRecentMessages($sessionKey, 20, $userId, $guestToken);

        $messages = array_merge(
            [Types::system($systemPrompt)],
            $history,
            [Types::user($userPrefix . $userMessage)]
        );

        $this->sessionManager->addMessage($sessionKey, 'user', $userMessage, [], $userId, $guestToken);

        $allToolCalls = [];

        for ($turn = 0; $turn < $this->maxTurns; $turn++) {
            $fullContent = '';
            $currentToolCalls = [];

            foreach ($this->agent->chatStream($role, $messages) as $chunk) {
                if (isset($chunk['error'])) {
                    yield ['delta' => '', 'type' => 'error', 'done' => true, 'error' => $chunk['error']];
                    return;
                }

                $delta = $chunk['delta'] ?? '';
                $fullContent .= $delta;

                if (!empty($delta)) {
                    yield ['delta' => $delta, 'type' => 'text', 'done' => false, 'turn' => $turn];
                }

                if (!empty($chunk['tool_call'])) {
                    $currentToolCalls = $chunk['tool_call'];
                }

                if ($chunk['done'] ?? false) {
                    break;
                }
            }

            $currentToolCalls = array_values(array_filter($currentToolCalls, function ($tc) {
                return !empty($tc['function']['name'] ?? '');
            }));

            if (empty($currentToolCalls)) {
                $finalContent = $fullContent ?: '处理完成';
                $this->sessionManager->addMessage($sessionKey, 'assistant', $finalContent, [], $userId, $guestToken);
                yield ['delta' => $finalContent, 'type' => 'text', 'done' => false, 'turn' => $turn];
                yield ['delta' => '', 'type' => 'done', 'done' => true, 'turn' => $turn];
                return;
            }

            $allToolCalls[] = ['turn' => $turn, 'calls' => $currentToolCalls];

            foreach ($currentToolCalls as $tc) {
                $rawName = $tc['function']['name'] ?? '';
                $tool = \XZD\AI\V5\Tools\ToolRegistry::instance()->get($rawName);
                $displayName = $tool ? $tool->name : $rawName;
                $toolAction = $tool ? $tool->description : '';
                yield [
                    'delta'         => '',
                    'type'          => 'tool_exec',
                    'done'          => false,
                    'turn'          => $turn,
                    'tool_name'     => $displayName,
                    'tool_id'       => $rawName,
                    'tool_action'   => mb_substr($toolAction, 0, 80),
                    'tool_call_id'  => $tc['id'] ?? '',
                ];
            }

            $messages[] = [
                'role'       => 'assistant',
                'content'    => $fullContent,
                'tool_calls' => $currentToolCalls,
            ];

            $streamPendingConfirms = [];
            $streamExecutedCalls = [];
            foreach ($currentToolCalls as $tc) {
                $funcName = $tc['function']['name'] ?? '';
                $funcArgs = json_decode($tc['function']['arguments'] ?? '{}', true) ?: [];
                $tcId = $tc['id'] ?? '';

                $toolObj = \XZD\AI\V5\Tools\ToolRegistry::instance()->get($funcName);
                yield [
                    'delta'         => '',
                    'type'          => 'tool_running',
                    'done'          => false,
                    'turn'          => $turn,
                    'tool_name'     => $toolObj ? $toolObj->name : $funcName,
                    'tool_id'       => $funcName,
                    'tool_call_id'  => $tcId,
                    'message'       => '正在执行 ' . ($toolObj ? $toolObj->name : $funcName) . '...',
                ];

                $result = $this->dispatcher->dispatch($funcName, $funcArgs, $userId, $sessionKey);

                if (!empty($result['confirm_required'])) {
                    $streamPendingConfirms[] = [
                        'tool_call'       => $tc,
                        'confirm_key'     => $result['confirm_key'] ?? '',
                        'confirm_message' => $result['confirm_message'] ?? '请确认操作',
                        'confirm_detail'  => $result['confirm_detail'] ?? '',
                    ];
                    yield [
                        'delta'         => json_encode($result, JSON_UNESCAPED_UNICODE),
                        'type'          => 'tool_result',
                        'done'          => false,
                        'turn'          => $turn,
                        'tool_name'     => $funcName,
                        'tool_call_id'  => $tcId,
                        'tool_success'  => false,
                        'confirm_required' => true,
                    ];
                    continue;
                }

                $streamExecutedCalls[] = $tc;

                $resultContent = $result['success']
                    ? json_encode($result['data'] ?? $result['message'] ?? 'ok', JSON_UNESCAPED_UNICODE)
                    : "错误: " . ($result['message'] ?? '未知错误');

                $messages[] = Types::tool($tcId, $resultContent);
                $this->sessionManager->addMessage($sessionKey, 'tool', $resultContent, [
                    'tool_call_id' => $tcId,
                ], $userId, $guestToken);

                $tool = \XZD\AI\V5\Tools\ToolRegistry::instance()->get($funcName);
                Logger::operation([
                    'tool_id'     => $funcName,
                    'tool_name'   => $tool ? $tool->name : $funcName,
                    'category'    => $tool ? $tool->category : 'unknown',
                    'params'      => $funcArgs,
                    'result'      => $result,
                    'status'      => $result['success'] ? 'success' : 'failed',
                    'error_message' => $result['success'] ? null : ($result['message'] ?? ''),
                    'user_id'     => $userId,
                    'session_key' => $sessionKey,
                ]);

                $imageUrl = null;
                if (!empty($result['success']) && !empty($result['data'])) {
                    $d = $result['data'];
                    if (!empty($d['url']) && filter_var($d['url'], FILTER_VALIDATE_URL)) {
                        $imageUrl = $d['url'];
                    } elseif (!empty($d['image_url']) && filter_var($d['image_url'], FILTER_VALIDATE_URL)) {
                        $imageUrl = $d['image_url'];
                    } elseif (is_string($d) && preg_match('/^https?:\/\/.*\.(png|jpg|jpeg|gif|webp)/i', $d)) {
                        $imageUrl = $d;
                    }
                }

                yield [
                    'delta'         => json_encode($result, JSON_UNESCAPED_UNICODE),
                    'type'          => 'tool_result',
                    'done'          => false,
                    'turn'          => $turn,
                    'tool_name'     => $funcName,
                    'tool_call_id'  => $tcId,
                    'tool_success'  => $result['success'],
                    'image_url'     => $imageUrl,
                ];
            }

            $this->sessionManager->addMessage($sessionKey, 'assistant', $fullContent, [
                'tool_calls' => !empty($streamExecutedCalls) ? $streamExecutedCalls : null,
            ], $userId, $guestToken);

            if (!empty($streamPendingConfirms)) {
                $confirmKeys = array_column($streamPendingConfirms, 'confirm_key');
                $confirmMessages = array_column($streamPendingConfirms, 'confirm_message');
                $confirmDetails = array_column($streamPendingConfirms, 'confirm_detail');
                yield [
                    'delta'              => implode("\n\n", $confirmMessages),
                    'type'               => 'confirm_required',
                    'done'               => true,
                    'confirm_keys'       => $confirmKeys,
                    'confirm_key'        => $confirmKeys[0],
                    'confirm_message'    => implode("\n\n", $confirmMessages),
                    'confirm_detail'     => implode("\n---\n", $confirmDetails),
                ];
                return;
            }
        }

        yield ['delta' => '', 'type' => 'done', 'done' => true];
    }
}
