import { defaultModelFamilyRequiredForLocateMessage } from "../ai-model/errors.mjs";
import { AiExtractElementInfo, AiLocateElement, AiLocateSection, buildSearchAreaConfig } from "../ai-model/inspect.mjs";
import { elementDescriberInstruction } from "../ai-model/prompt/describe.mjs";
import { AIResponseParseError, callAIWithObjectResponse } from "../ai-model/service-caller/index.mjs";
import { expandSearchArea } from "../common.mjs";
import { ServiceError } from "../types.mjs";
import { compositeElementInfoImg, cropByRect } from "@midscene/shared/img";
import { getDebug } from "@midscene/shared/logger";
import { assert } from "@midscene/shared/utils";
import { createServiceDump } from "./utils.mjs";
function _define_property(obj, key, value) {
    if (key in obj) Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
    });
    else obj[key] = value;
    return obj;
}
const debug = getDebug('ai:service');
class Service {
    async locate(query, opt, modelRuntime, abortSignal) {
        const { config: modelConfig } = modelRuntime;
        const queryPrompt = 'string' == typeof query ? query : query.prompt;
        assert(queryPrompt, 'query is required for locate');
        assert('object' == typeof query, 'query should be an object for locate');
        if (!modelConfig.modelFamily) throw new Error(defaultModelFamilyRequiredForLocateMessage);
        const context = opt?.context || await this.contextRetrieverFn();
        const searchArea = await this.resolveLocateSearchArea({
            query,
            queryPrompt,
            opt,
            context,
            modelRuntime,
            abortSignal
        });
        const startTime = Date.now();
        const { parseResult, rect, rawResponse, rawChoiceMessage, usage, reasoning_content } = await AiLocateElement({
            context,
            targetElementDescription: queryPrompt,
            searchConfig: searchArea.config,
            modelRuntime,
            abortSignal
        });
        const timeCost = Date.now() - startTime;
        const taskInfo = {
            ...this.taskInfo ? this.taskInfo : {},
            durationMs: timeCost,
            rawResponse: JSON.stringify(rawResponse),
            rawChoiceMessage,
            formatResponse: JSON.stringify(parseResult),
            usage,
            searchArea: searchArea.trace.sourceRect,
            searchAreaRawResponse: searchArea.trace.rawResponse,
            searchAreaRawChoiceMessage: searchArea.trace.rawChoiceMessage,
            searchAreaUsage: searchArea.trace.usage,
            reasoning_content
        };
        let errorLog;
        if (parseResult.errors?.length) errorLog = `failed to locate element: \n${parseResult.errors.join('\n')}`;
        const dumpData = {
            type: 'locate',
            userQuery: {
                element: queryPrompt
            },
            matchedRect: rect,
            data: null,
            taskInfo,
            deepLocate: !!searchArea.trace.sourceRect,
            error: errorLog
        };
        const element = parseResult.element;
        const dump = createServiceDump({
            ...dumpData,
            matchedElement: element ? [
                element
            ] : []
        });
        if (errorLog) throw new ServiceError(errorLog, dump);
        if (element) return {
            element: {
                center: element.center,
                rect: element.rect,
                description: element.description
            },
            rect,
            dump
        };
        return {
            element: null,
            rect,
            dump
        };
    }
    async resolveLocateSearchArea(options) {
        const { query, queryPrompt, opt, context, modelRuntime, abortSignal } = options;
        const { adapter } = modelRuntime;
        const hasPlanLocatedElement = !!opt?.planLocatedElement?.rect;
        if (!query.deepLocate) return {
            trace: {}
        };
        if (hasPlanLocatedElement) {
            const config = await buildSearchAreaConfig({
                context,
                baseRect: opt.planLocatedElement.rect
            });
            return {
                config,
                trace: {
                    sourceRect: config.sourceRect,
                    rawResponse: JSON.stringify({
                        source: 'plan-located-element',
                        rect: opt.planLocatedElement.rect
                    })
                }
            };
        }
        if (adapter.locate.supportsSearchArea) {
            const searchAreaResponse = await AiLocateSection({
                context,
                sectionDescription: queryPrompt,
                modelRuntime,
                abortSignal
            });
            const { searchAreaConfig } = searchAreaResponse;
            assert(searchAreaConfig, `cannot find search area for "${queryPrompt}"${searchAreaResponse.error ? `: ${searchAreaResponse.error}` : ''}`);
            return {
                config: searchAreaConfig,
                trace: {
                    sourceRect: searchAreaConfig.sourceRect,
                    rawResponse: searchAreaResponse.rawResponse,
                    rawChoiceMessage: searchAreaResponse.rawChoiceMessage,
                    usage: searchAreaResponse.usage
                }
            };
        }
        const firstPassLocateResult = await AiLocateElement({
            context,
            targetElementDescription: queryPrompt,
            modelRuntime,
            abortSignal
        });
        assert(firstPassLocateResult.rect, `cannot find search area for "${queryPrompt}"${firstPassLocateResult.parseResult.errors?.length ? `: ${firstPassLocateResult.parseResult.errors.join('\n')}` : ''}`);
        const config = await buildSearchAreaConfig({
            context,
            baseRect: firstPassLocateResult.rect
        });
        return {
            config,
            trace: {
                sourceRect: config.sourceRect,
                rawResponse: JSON.stringify({
                    source: 'deep-locate-first-pass',
                    rect: firstPassLocateResult.rect,
                    rawResponse: firstPassLocateResult.rawResponse
                }),
                rawChoiceMessage: firstPassLocateResult.rawChoiceMessage,
                usage: firstPassLocateResult.usage
            }
        };
    }
    async extract(dataDemand, modelRuntime, opt, pageDescription, multimodalPrompt, context) {
        assert(context, 'context is required for extract');
        assert('object' == typeof dataDemand || 'string' == typeof dataDemand, `dataDemand should be object or string, but get ${typeof dataDemand}`);
        const startTime = Date.now();
        let parseResult;
        let rawResponse;
        let rawChoiceMessage;
        let usage;
        let reasoning_content;
        try {
            const result = await AiExtractElementInfo({
                context,
                dataQuery: dataDemand,
                multimodalPrompt,
                extractOption: opt,
                modelRuntime,
                pageDescription
            });
            parseResult = result.parseResult;
            rawResponse = result.rawResponse;
            rawChoiceMessage = result.rawChoiceMessage;
            usage = result.usage;
            reasoning_content = result.reasoning_content;
        } catch (error) {
            if (error instanceof AIResponseParseError) {
                const timeCost = Date.now() - startTime;
                const taskInfo = {
                    ...this.taskInfo ? this.taskInfo : {},
                    durationMs: timeCost,
                    rawResponse: error.rawResponse,
                    rawChoiceMessage: error.rawChoiceMessage,
                    usage: error.usage
                };
                const dump = createServiceDump({
                    type: 'extract',
                    userQuery: {
                        dataDemand
                    },
                    data: null,
                    taskInfo,
                    error: error.message
                });
                throw new ServiceError(error.message, dump);
            }
            throw error;
        }
        const timeCost = Date.now() - startTime;
        const taskInfo = {
            ...this.taskInfo ? this.taskInfo : {},
            durationMs: timeCost,
            rawResponse,
            rawChoiceMessage,
            formatResponse: JSON.stringify(parseResult),
            usage,
            reasoning_content
        };
        let errorLog;
        if (parseResult.errors?.length) errorLog = `AI response error: \n${parseResult.errors.join('\n')}`;
        const dumpData = {
            type: 'extract',
            userQuery: {
                dataDemand
            },
            data: null,
            taskInfo,
            error: errorLog
        };
        const { data, thought } = parseResult || {};
        const dump = createServiceDump({
            ...dumpData,
            data
        });
        if (errorLog && !data) throw new ServiceError(errorLog, dump);
        return {
            data,
            thought,
            usage,
            reasoning_content,
            dump
        };
    }
    async describe(target, modelRuntime, opt) {
        assert(target, 'target is required for service.describe');
        const context = await this.contextRetrieverFn();
        const { shotSize } = context;
        const screenshotBase64 = context.screenshot.base64;
        assert(screenshotBase64, 'screenshot is required for service.describe');
        const systemPrompt = elementDescriberInstruction();
        const defaultRectSize = 30;
        const targetRect = Array.isArray(target) ? {
            left: Math.floor(target[0] - defaultRectSize / 2),
            top: Math.floor(target[1] - defaultRectSize / 2),
            width: defaultRectSize,
            height: defaultRectSize
        } : target;
        let imagePayload = await compositeElementInfoImg({
            inputImgBase64: screenshotBase64,
            size: shotSize,
            elementsPositionInfo: [
                {
                    rect: targetRect
                }
            ],
            borderThickness: 3
        });
        if (opt?.deepLocate) {
            const searchArea = expandSearchArea(targetRect, shotSize);
            debug('describe: cropping to searchArea', searchArea);
            const croppedResult = await cropByRect(imagePayload, searchArea);
            imagePayload = croppedResult.imageBase64;
        }
        const msgs = [
            {
                role: 'system',
                content: systemPrompt
            },
            {
                role: 'user',
                content: [
                    {
                        type: 'image_url',
                        image_url: {
                            url: imagePayload,
                            detail: 'high'
                        }
                    }
                ]
            }
        ];
        const res = await callAIWithObjectResponse(msgs, modelRuntime);
        const { content } = res;
        assert(!content.error, `describe failed: ${content.error}`);
        assert(content.description, 'failed to describe the element');
        return content;
    }
    constructor(context, opt){
        _define_property(this, "contextRetrieverFn", void 0);
        _define_property(this, "taskInfo", void 0);
        assert(context, 'context is required for Service');
        if ('function' == typeof context) this.contextRetrieverFn = context;
        else this.contextRetrieverFn = ()=>Promise.resolve(context);
        if (void 0 !== opt?.taskInfo) this.taskInfo = opt.taskInfo;
    }
}
export { Service as default };

//# sourceMappingURL=index.mjs.map