import { paddingToMatchBlockByBase64 } from "@midscene/shared/img";
import { getDebug } from "@midscene/shared/logger";
import { assert } from "@midscene/shared/utils";
import { buildYamlFlowFromPlans, fillBboxParam, findAllMidsceneLocatorField } from "../common.mjs";
import { systemPromptToTaskPlanning } from "./prompt/llm-planning.mjs";
import { extractXMLTag } from "./prompt/util.mjs";
import { AIResponseParseError, callAI, safeParseJson } from "./service-caller/index.mjs";
const debug = getDebug('planning');
const warnLog = getDebug('planning', {
    console: true
});
function parseXMLPlanningResponse(xmlString, modelFamily) {
    const thought = extractXMLTag(xmlString, 'thought');
    const memory = extractXMLTag(xmlString, 'memory');
    const log = extractXMLTag(xmlString, 'log') || '';
    const error = extractXMLTag(xmlString, 'error');
    const actionType = extractXMLTag(xmlString, 'action-type');
    const actionParamStr = extractXMLTag(xmlString, 'action-param-json');
    const completeGoalRegex = /<complete\s+success="(true|false)">([\s\S]*?)<\/complete>/i;
    const completeGoalMatch = xmlString.match(completeGoalRegex);
    let finalizeMessage;
    let finalizeSuccess;
    if (completeGoalMatch) {
        finalizeSuccess = 'true' === completeGoalMatch[1];
        finalizeMessage = completeGoalMatch[2]?.trim() || void 0;
    }
    let action = null;
    if (actionType && 'null' !== actionType.toLowerCase()) {
        const type = actionType.trim();
        let param;
        if (actionParamStr) try {
            param = safeParseJson(actionParamStr, modelFamily);
        } catch (e) {
            throw new Error(`Failed to parse action-param-json: ${e}`);
        }
        action = {
            type,
            ...void 0 !== param ? {
                param
            } : {}
        };
    }
    return {
        ...thought ? {
            thought
        } : {},
        ...memory ? {
            memory
        } : {},
        log,
        ...error ? {
            error
        } : {},
        action,
        ...void 0 !== finalizeMessage ? {
            finalizeMessage
        } : {},
        ...void 0 !== finalizeSuccess ? {
            finalizeSuccess
        } : {}
    };
}
async function plan(userInstruction, opts) {
    const { context, modelConfig, conversationHistory } = opts;
    const { size } = context;
    const screenshotBase64 = context.screenshot.base64;
    const { modelFamily } = modelConfig;
    const systemPrompt = await systemPromptToTaskPlanning({
        actionSpace: opts.actionSpace,
        modelFamily,
        includeBbox: opts.includeBbox,
        includeThought: true,
        deepThink: true === opts.deepThink
    });
    let imagePayload = screenshotBase64;
    let imageWidth = size.width;
    let imageHeight = size.height;
    if ('qwen2.5-vl' === modelFamily) {
        const paddedResult = await paddingToMatchBlockByBase64(imagePayload);
        imageWidth = paddedResult.width;
        imageHeight = paddedResult.height;
        imagePayload = paddedResult.imageBase64;
    }
    const actionContext = opts.actionContext ? `<high_priority_knowledge>${opts.actionContext}</high_priority_knowledge>\n` : '';
    const instruction = [
        {
            role: 'user',
            content: [
                {
                    type: 'text',
                    text: `${actionContext}<user_instruction>${userInstruction}</user_instruction>`
                }
            ]
        }
    ];
    let latestFeedbackMessage;
    const historicalLogsText = conversationHistory.historicalLogsToText();
    const historicalLogsSection = historicalLogsText ? `\n\n${historicalLogsText}` : '';
    const memoriesText = conversationHistory.memoriesToText();
    const memoriesSection = memoriesText ? `\n\n${memoriesText}` : '';
    if (conversationHistory.pendingFeedbackMessage) {
        latestFeedbackMessage = {
            role: 'user',
            content: [
                {
                    type: 'text',
                    text: `${conversationHistory.pendingFeedbackMessage}. The previous action has been executed, here is the latest screenshot. Please continue according to the instruction.${memoriesSection}${historicalLogsSection}`
                },
                {
                    type: 'image_url',
                    image_url: {
                        url: imagePayload,
                        detail: 'high'
                    }
                }
            ]
        };
        conversationHistory.resetPendingFeedbackMessageIfExists();
    } else latestFeedbackMessage = {
        role: 'user',
        content: [
            {
                type: 'text',
                text: `this is the latest screenshot${memoriesSection}${historicalLogsSection}`
            },
            {
                type: 'image_url',
                image_url: {
                    url: imagePayload,
                    detail: 'high'
                }
            }
        ]
    };
    conversationHistory.append(latestFeedbackMessage);
    conversationHistory.compressHistory(50, 20);
    const historyLog = conversationHistory.snapshot(opts.imagesIncludeCount);
    const msgs = [
        {
            role: 'system',
            content: systemPrompt
        },
        ...instruction,
        ...historyLog
    ];
    const { content: rawResponse, usage, reasoning_content } = await callAI(msgs, modelConfig, {
        deepThink: 'unset' === opts.deepThink ? void 0 : opts.deepThink
    });
    let planFromAI;
    try {
        planFromAI = parseXMLPlanningResponse(rawResponse, modelFamily);
        if (planFromAI.action && void 0 !== planFromAI.finalizeSuccess) {
            warnLog('Planning response included both an action and <complete>; ignoring <complete> output.');
            planFromAI.finalizeMessage = void 0;
            planFromAI.finalizeSuccess = void 0;
        }
        const actions = planFromAI.action ? [
            planFromAI.action
        ] : [];
        let shouldContinuePlanning = true;
        if (void 0 !== planFromAI.finalizeSuccess) {
            debug('task completed via <complete> tag, stop planning');
            shouldContinuePlanning = false;
        }
        const returnValue = {
            ...planFromAI,
            actions,
            rawResponse,
            usage,
            reasoning_content,
            yamlFlow: buildYamlFlowFromPlans(actions, opts.actionSpace),
            shouldContinuePlanning
        };
        assert(planFromAI, "can't get plans from AI");
        actions.forEach((action)=>{
            const type = action.type;
            const actionInActionSpace = opts.actionSpace.find((action)=>action.name === type);
            debug('actionInActionSpace matched', actionInActionSpace);
            const locateFields = actionInActionSpace ? findAllMidsceneLocatorField(actionInActionSpace.paramSchema) : [];
            debug('locateFields', locateFields);
            locateFields.forEach((field)=>{
                const locateResult = action.param[field];
                if (locateResult && void 0 !== modelFamily) action.param[field] = fillBboxParam(locateResult, imageWidth, imageHeight, modelFamily);
            });
        });
        if (planFromAI.log) conversationHistory.appendHistoricalLog(planFromAI.log);
        if (planFromAI.memory) conversationHistory.appendMemory(planFromAI.memory);
        conversationHistory.append({
            role: 'assistant',
            content: [
                {
                    type: 'text',
                    text: rawResponse
                }
            ]
        });
        return returnValue;
    } catch (parseError) {
        const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
        throw new AIResponseParseError(`XML parse error: ${errorMessage}`, rawResponse, usage);
    }
}
export { parseXMLPlanningResponse, plan };

//# sourceMappingURL=llm-planning.mjs.map