/**************************************************************************************** 
 Copyright © 2003-2012 hbasesoft Corporation. All rights reserved. Reproduction or       <br>
 transmission in whole or in part, in any form or by any means, electronic, mechanical <br>
 or otherwise, is prohibited without the prior written consent of the copyright owner. <br>
 ****************************************************************************************/
package com.hbasesoft.framework.ai.agent.planning.executor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.collections4.MapUtils;

import com.hbasesoft.framework.ai.agent.agent.AgentState;
import com.hbasesoft.framework.ai.agent.agent.BaseAgent;
import com.hbasesoft.framework.ai.agent.config.IManusProperties;
import com.hbasesoft.framework.ai.agent.dynamic.agent.DynamicAgent;
import com.hbasesoft.framework.ai.agent.dynamic.agent.service.AgentService;
import com.hbasesoft.framework.ai.agent.llm.ILlmService;
import com.hbasesoft.framework.ai.agent.planning.model.vo.ExecutionContext;
import com.hbasesoft.framework.ai.agent.planning.model.vo.ExecutionStep;
import com.hbasesoft.framework.ai.agent.recorder.PlanExecutionRecorder;
import com.hbasesoft.framework.common.utils.logger.LoggerUtil;

/**
 * <Description> <br>
 * 
 * @author 王伟<br>
 * @version 1.0<br>
 * @taskId <br>
 * @CreateDate 2025年8月20日 <br>
 * @since V1.0<br>
 * @see com.hbasesoft.framework.ai.agent.planning.executor <br>
 */

public abstract class AbstractPlanExecutor implements PlanExecutorInterface {

    protected final PlanExecutionRecorder recorder;

    // Pattern to match square brackets at the beginning of a string, supports
    // Chinese and
    // other characters
    protected final Pattern pattern = Pattern.compile("^\\s*\\[([^\\]]+)\\]");

    protected final List<DynamicAgent> agents;

    protected final AgentService agentService;

    protected ILlmService llmService;

    protected final IManusProperties manusProperties;

    // Define static final strings for the keys used in executorParams
    public static final String PLAN_STATUS_KEY = "planStatus";

    public static final String CURRENT_STEP_INDEX_KEY = "currentStepIndex";

    public static final String STEP_TEXT_KEY = "stepText";

    public static final String EXTRA_PARAMS_KEY = "extraParams";

    public static final String EXECUTION_ENV_STRING_KEY = "current_step_env_data";

    public AbstractPlanExecutor(List<DynamicAgent> agents, PlanExecutionRecorder recorder, AgentService agentService,
        ILlmService llmService, IManusProperties manusProperties) {
        this.agents = agents;
        this.recorder = recorder;
        this.agentService = agentService;
        this.llmService = llmService;
        this.manusProperties = manusProperties;
    }

    /**
     * General logic for executing a single step.
     * 
     * @param step The execution step
     * @param context The execution context
     * @return The step executor
     */
    protected BaseAgent executeStep(ExecutionStep step, ExecutionContext context) {
        try {
            String stepType = getStepFromStepReq(step.getStepRequirement());
            int stepIndex = step.getStepIndex();
            String expectedReturnInfo = step.getTerminateColumns();

            String planStatus = context.getPlan().getPlanExecutionStateStringFormat(true);
            String stepText = step.getStepRequirement();

            Map<String, Object> initSettings = new HashMap<>();
            initSettings.put(PLAN_STATUS_KEY, planStatus);
            initSettings.put(CURRENT_STEP_INDEX_KEY, String.valueOf(stepIndex));
            initSettings.put(STEP_TEXT_KEY, stepText);
            initSettings.put(EXTRA_PARAMS_KEY, context.getPlan().getExecutionParams());
            if (MapUtils.isNotEmpty(context.getToolsContext())) {
                context.getToolsContext().forEach((k, v) -> {
                    initSettings.put(k, v);
                });
            }

            // 通知监听器步骤开始
            context.notifyStepStart(step);
            context.notifyStatusChange("开始执行步骤: " + stepText);

            BaseAgent executor = getExecutorForStep(stepType, context, initSettings, expectedReturnInfo);
            if (executor == null) {
                LoggerUtil.error("No executor found for step type: {0}", stepType);
                String errorMsg = "No executor found for step type: " + stepType;
                step.setResult(errorMsg);
                context.notifyStepProgress(step, "执行器未找到: " + stepType);
                context.notifyStepComplete(step, errorMsg);
                return null;
            }

            step.setAgent(executor);
            executor.setState(AgentState.IN_PROGRESS);

            // 通知监听器步骤进度
            context.notifyStepProgress(step, "执行器已准备就绪: " + stepType);

            recorder.recordStepStart(step, context);
            
            // 通知监听器步骤进度
            context.notifyStepProgress(step, "开始执行代理: " + executor.getClass().getSimpleName());
            
            String stepResultStr = executor.run();
            step.setResult(stepResultStr);

            // 通知监听器步骤完成
            context.notifyStepComplete(step, stepResultStr);
            context.notifyStatusChange("步骤执行完成: " + stepText);

            return executor;
        }
        catch (Exception e) {
            LoggerUtil.error("Error executing step: {0}", step.getStepRequirement(), e);
            String errorMsg = "Execution failed: " + e.getMessage();
            step.setResult(errorMsg);
            
            // 通知监听器错误
            context.notifyStepProgress(step, "执行失败: " + e.getMessage());
            context.notifyError(e);
        }
        finally {
            recorder.recordStepEnd(step, context);
        }
        return null;
    }

    /**
     * Extract the step type from the step requirement string.
     */
    protected String getStepFromStepReq(String stepRequirement) {
        Matcher matcher = pattern.matcher(stepRequirement);
        if (matcher.find()) {
            return matcher.group(1).trim().toLowerCase();
        }
        return "DEFAULT_AGENT";
    }

    /**
     * Get the executor for the step.
     */
    protected BaseAgent getExecutorForStep(String stepType, ExecutionContext context, Map<String, Object> initSettings,
        String expectedReturnInfo) {
        for (DynamicAgent agent : agents) {
            if (agent.getName().equalsIgnoreCase(stepType)) {
                BaseAgent executor = agentService.createDynamicBaseAgent(agent.getName(),
                    context.getPlan().getCurrentPlanId(), context.getPlan().getRootPlanId(), initSettings,
                    expectedReturnInfo);
                // Set thinkActRecordId from context for sub-plan executions
                if (context.getThinkActRecordId() != null) {
                    executor.setThinkActRecordId(context.getThinkActRecordId());
                }
                return executor;
            }
        }
        throw new IllegalArgumentException(
            "No Agent Executor found for step type, check your agents list : " + stepType);
    }

    protected PlanExecutionRecorder getRecorder() {
        return recorder;
    }

    /**
     * Parse columns string by splitting with comma or Chinese comma.
     * 
     * @param columnsInString the columns string to parse
     * @return list of column names
     */
    protected List<String> parseColumns(String columnsInString) {
        List<String> columns = new ArrayList<>();
        if (columnsInString == null || columnsInString.trim().isEmpty()) {
            return columns;
        }

        // Split by comma (,) or Chinese comma (，)
        String[] parts = columnsInString.split("[,，]");
        for (String part : parts) {
            String trimmed = part.trim();
            if (!trimmed.isEmpty()) {
                columns.add(trimmed);
            }
        }

        return columns;
    }

    /**
     * Cleanup work after execution is completed.
     */
    protected void performCleanup(ExecutionContext context, BaseAgent lastExecutor) {
        String planId = context.getCurrentPlanId();
        llmService.clearAgentMemory(planId);
        if (lastExecutor != null) {
            lastExecutor.clearUp(planId);
        }
    }

}
