/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved.
 */
package magic.agent_executor.react

import magic.core.agent.*
import magic.core.rag.RetrieverMode
import magic.log.LogUtils
import magic.config.Config
import magic.parser.WithTag

public class ReactExecutor <: AgentExecutor {
    public ReactExecutor(
        private let loop!: Int64 = Config.maxReactNumber
    ) { }

    override public func run(agent: Agent, request: AgentRequest): AgentResponse {
        LogUtils.info("React executor runs ${agent.name}")
        let task = ReactTask(agent, request)
        for (_ in 0..this.loop) {
            if (let Some(resp) <- task.runOnce()) {
                return resp
            }
        }
        LogUtils.info("Exceed the max react loop")
        return task.summarize()
    }

    override public func asyncRun(agent: Agent, request: AgentRequest): AsyncAgentResponse {
        LogUtils.info("React executor async runs ${agent.name}")
        let task = ReactTask(agent, request)
        // Create a thread to execute the react task
        let fut: Future<Iterator<String>> = spawn {
            try {
                for (_ in 0..this.loop) {
                    if (let Some(asyncAnswer) <- task.asyncRunOnce()) {
                        return asyncAnswer
                    }
                }
                LogUtils.info("Exceed the max react loop")
                return task.asyncSummarize()
            } catch (e: Exception) {
                if (request.verbose) {
                    task.execInfo.verboseChannel.close()
                }
                throw e
            }
        }
        return AsyncAgentResponse(IteratorWrapper(agent, fut), execInfo: task.execInfo)
    }
}

protected class IteratorWrapper <: Iterator<String> {
    protected IteratorWrapper(
        private let agent: Agent,
        private let workerFut: Future<Iterator<String>>
    ) { }

    /**
     * Concatenate all response content and log it finally
     */
    private let buffer = StringBuilder()

    override public func next(): Option<String> {
        let asyncAnswer = workerFut.get()
        let data = asyncAnswer.next()
        match (data) {
            case Some(v) => buffer.append(v)
            case None =>
                LogUtils.info(this.agent.name, buffer.toString().withTag(Tag.ANSWER))
        }
        return data
    }
}