"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandsService = void 0;
const contracts_1 = require("@teachagenthub/contracts");
const common_1 = require("@nestjs/common");
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const gateway_state_service_1 = require("../../state/service/gateway-state.service");
const access_context_1 = require("./access-context");
const STATUS_TOOLS = new Set([
    'get_robot_mode',
    'get_program_state',
    'get_safety_mode',
    'get_robot_model',
    'get_software_version',
    'get_serial_number'
]);
let CommandsService = class CommandsService {
    state;
    constructor(state) {
        this.state = state;
        this.recommendationCorpus = this.loadRecommendationCorpus();
    }
    tenantId = contracts_1.APP_CONFIG.AGENT_TENANT_ID ?? 'default';
    recommendationCorpus;
    async dispatch(req, headers, options) {
        const session = this.state.getSession(req.sessionId);
        if (!session) {
            throw new common_1.NotFoundException('session not found');
        }
        const access = (0, access_context_1.resolveGatewayAccessContext)({
            authorization: headers?.authorization,
            tenantId: headers?.tenantId ?? this.tenantId,
            userId: headers?.userId,
            userRole: headers?.userRole
        });
        if (options?.persistInputRecord !== false && req.text?.trim()) {
            this.state.appendChatRecord(req.sessionId, req.text, true, 'user_input');
        }
        if (req.highRiskConfirmed) {
            this.state.appendChatRecord(req.sessionId, req.highRiskConfirmNote?.trim() || '高风险动作已二次确认', true, 'risk_confirmation');
        }
        const command = await this.parseCommand(session, req, access);
        command.params = {
            ...command.params,
            highRiskConfirmed: Boolean(req.highRiskConfirmed),
            ...(req.highRiskConfirmNote ? { highRiskConfirmNote: req.highRiskConfirmNote } : {})
        };
        const task = await this.createRuntimeTask(command, access);
        const execution = this.resolveExecution(task, req);
        const recommendationQuery = req.text?.trim() || req.hintText?.trim() || '';
        const queryRecommendations = this.recommendSimilarQuestions(recommendationQuery, 3);
        if (execution.accepted) {
            const toolName = typeof execution.data?.toolName === 'string' ? execution.data.toolName : '';
            const toolRecommendations = toolName ? this.recommendByToolContext(toolName, recommendationQuery, 3) : [];
            const mergedRecommendations = this.mergeRecommendations(queryRecommendations, toolRecommendations, 3);
            if (mergedRecommendations.length > 0) {
                execution.recommendedQuestions = mergedRecommendations;
            }
        }
        return { command, task, execution };
    }
    loadRecommendationCorpus() {
        const configuredPath = (contracts_1.APP_CONFIG.AGENT_RECOMMEND_EXAMPLES_FILE ?? '').trim();
        const sourcePath = this.resolveRecommendationSourcePath(configuredPath);
        if (!sourcePath) {
            return [];
        }
        try {
            const raw = (0, node_fs_1.readFileSync)(sourcePath, 'utf8');
            const parsed = JSON.parse(raw);
            if (!Array.isArray(parsed)) {
                return [];
            }
            const dedupe = new Set();
            const output = [];
            for (const item of parsed) {
                if (item.status === 'deprecated' || item.negative === true) {
                    continue;
                }
                const question = typeof item.query_text === 'string' ? item.query_text.trim() : '';
                if (!question) {
                    continue;
                }
                const normalized = this.normalizeRecommendationText(question);
                if (!normalized || dedupe.has(normalized)) {
                    continue;
                }
                const tokens = this.tokenizeRecommendationText(question);
                if (tokens.length === 0) {
                    continue;
                }
                dedupe.add(normalized);
                output.push({
                    question,
                    toolName: typeof item.tool_name === 'string' ? item.tool_name : '',
                    normalized,
                    tokens
                });
            }
            return output;
        }
        catch {
            return [];
        }
    }
    resolveRecommendationSourcePath(configuredPath) {
        const relativeDefaults = ['knowledge/aubo/examples/default.examples.json', './knowledge/aubo/examples/default.examples.json'];
        const rawCandidates = [configuredPath, ...relativeDefaults].filter((item) => item.length > 0);
        const roots = [process.cwd()];
        let current = __dirname;
        for (let i = 0; i < 8; i += 1) {
            roots.push(current);
            const parent = (0, node_path_1.resolve)(current, '..');
            if (parent === current) {
                break;
            }
            current = parent;
        }
        const resolvedCandidates = new Set();
        for (const rawPath of rawCandidates) {
            if ((0, node_path_1.isAbsolute)(rawPath)) {
                resolvedCandidates.add(rawPath);
                continue;
            }
            for (const root of roots) {
                resolvedCandidates.add((0, node_path_1.resolve)(root, rawPath));
            }
        }
        for (const filePath of resolvedCandidates) {
            if ((0, node_fs_1.existsSync)(filePath)) {
                return filePath;
            }
        }
        return undefined;
    }
    recommendSimilarQuestions(query, topK = 3) {
        if (!query.trim() || this.recommendationCorpus.length === 0 || topK <= 0) {
            return [];
        }
        const normalizedQuery = this.normalizeRecommendationText(query);
        const queryTokens = this.tokenizeRecommendationText(query);
        if (!normalizedQuery || queryTokens.length === 0) {
            return [];
        }
        const queryTokenSet = new Set(queryTokens);
        const scored = this.recommendationCorpus
            .filter((entry) => entry.normalized !== normalizedQuery)
            .map((entry) => {
            const overlap = entry.tokens.reduce((count, token) => count + (queryTokenSet.has(token) ? 1 : 0), 0);
            if (overlap === 0) {
                return undefined;
            }
            const recall = overlap / queryTokenSet.size;
            const precision = overlap / entry.tokens.length;
            const score = Math.min(1, recall * 0.72 + precision * 0.28);
            return {
                question: entry.question,
                similarity: Number(score.toFixed(4))
            };
        })
            .filter((item) => Boolean(item))
            .sort((a, b) => b.similarity - a.similarity || a.question.localeCompare(b.question, 'zh-Hans-CN'));
        return scored.slice(0, topK);
    }
    recommendByToolContext(toolName, query, topK = 3) {
        if (!toolName || this.recommendationCorpus.length === 0 || topK <= 0) {
            return [];
        }
        const normalizedQuery = this.normalizeRecommendationText(query);
        const queryTokens = this.tokenizeRecommendationText(query);
        const queryTokenSet = new Set(queryTokens);
        const isStatusContext = STATUS_TOOLS.has(toolName);
        const scored = this.recommendationCorpus
            .filter((entry) => {
            if (entry.normalized === normalizedQuery) {
                return false;
            }
            if (entry.toolName === toolName) {
                return true;
            }
            return isStatusContext && STATUS_TOOLS.has(entry.toolName);
        })
            .map((entry) => {
            const overlap = queryTokens.length > 0
                ? entry.tokens.reduce((count, token) => count + (queryTokenSet.has(token) ? 1 : 0), 0)
                : 0;
            let similarity = entry.toolName === toolName ? 0.68 : 0.52;
            if (overlap > 0 && queryTokenSet.size > 0) {
                const recall = overlap / queryTokenSet.size;
                const precision = overlap / entry.tokens.length;
                similarity = Math.max(similarity, Math.min(1, recall * 0.72 + precision * 0.28));
            }
            return {
                question: entry.question,
                similarity: Number(similarity.toFixed(4))
            };
        })
            .sort((a, b) => b.similarity - a.similarity || a.question.localeCompare(b.question, 'zh-Hans-CN'));
        return scored.slice(0, topK);
    }
    mergeRecommendations(primary, fallback, topK) {
        if (topK <= 0) {
            return [];
        }
        const merged = new Map();
        for (const candidate of [...primary, ...fallback]) {
            const existing = merged.get(candidate.question);
            if (!existing || candidate.similarity > existing.similarity) {
                merged.set(candidate.question, candidate);
            }
        }
        return Array.from(merged.values())
            .sort((a, b) => b.similarity - a.similarity || a.question.localeCompare(b.question, 'zh-Hans-CN'))
            .slice(0, topK);
    }
    normalizeRecommendationText(text) {
        return text
            .trim()
            .toLowerCase()
            .replace(/[^\p{Script=Han}a-z0-9]+/gu, ' ')
            .replace(/\s+/g, ' ');
    }
    tokenizeRecommendationText(text) {
        const normalized = this.normalizeRecommendationText(text);
        if (!normalized) {
            return [];
        }
        const words = normalized.split(' ').filter((item) => item.length > 0);
        const cjkText = normalized.replace(/[^\p{Script=Han}]+/gu, '');
        const bigrams = [];
        for (let i = 0; i < cjkText.length - 1; i += 1) {
            bigrams.push(cjkText.slice(i, i + 2));
        }
        return Array.from(new Set([...words, ...bigrams]));
    }
    async parseCommand(session, req, access) {
        const response = await fetch('http://localhost:3001/internal/v1/model/multimodal/understand', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'x-tenant-id': access.tenantId,
                'x-user-id': access.userId,
                'x-user-role': access.userRole,
                ...(access.authorization ? { Authorization: access.authorization } : {})
            },
            body: JSON.stringify({
                sessionId: session.sessionId,
                connectionId: session.connectionId,
                ipcIp: session.ipcIp,
                targetType: session.targetType,
                modality: req.modality,
                text: req.text,
                audioBase64: req.audioBase64,
                videoBase64: req.videoBase64,
                hintText: req.hintText
            })
        });
        const payload = (await response.json());
        if (!response.ok) {
            throw new Error(payload.message ? `model understand failed: ${payload.message}` : `model understand failed: ${response.status}`);
        }
        if (!payload.command) {
            throw new Error('model understand returned empty command');
        }
        return payload.command;
    }
    async createRuntimeTask(command, access) {
        const response = await fetch('http://localhost:3001/internal/v1/runtime/tasks', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'x-tenant-id': access.tenantId,
                'x-user-id': access.userId,
                'x-user-role': access.userRole,
                ...(access.authorization ? { Authorization: access.authorization } : {})
            },
            body: JSON.stringify(command)
        });
        const payload = (await response.json());
        if (!response.ok) {
            throw new Error(payload.message ? `runtime create task failed: ${payload.message}` : `runtime create task failed: ${response.status}`);
        }
        return payload;
    }
    resolveExecution(task, req) {
        const traces = task.executionReport?.traces ?? [];
        const executorTrace = traces.find((trace) => trace.module === 'executor');
        if (task.status === 'completed' && executorTrace?.status === 'success') {
            const output = executorTrace.output;
            const rawMessage = typeof output?.message === 'string' ? output.message : 'executed by runtime executor';
            const message = this.normalizeExecutionMessage(rawMessage, output);
            return {
                accepted: true,
                message,
                ...(output ? { data: output } : {}),
                state: 'accepted'
            };
        }
        const failedMessage = task.error ?? executorTrace?.error ?? 'runtime execution failed';
        if (failedMessage === 'POLICY_CONFIRM_REQUIRED') {
            const confirmText = req.text?.trim() || req.hintText?.trim() || undefined;
            return {
                accepted: false,
                message: '检测到高风险动作，等待人工确认后执行',
                state: 'pending_confirmation',
                code: failedMessage,
                confirmation: {
                    ...(confirmText ? { confirmText } : {}),
                    note: 'click_confirm_to_continue'
                }
            };
        }
        return { accepted: false, message: failedMessage, state: 'rejected', code: failedMessage };
    }
    normalizeExecutionMessage(message, output) {
        const trimmed = this.stripQuotes(message.trim());
        const toolName = typeof output?.toolName === 'string' ? output.toolName : '';
        if (toolName === 'get_robot_mode') {
            const mode = this.extractRobotMode(trimmed);
            if (mode) {
                return `当前机器人模式为 ${mode}`;
            }
            return `当前机器人模式为 ${trimmed}`;
        }
        if (toolName === 'get_safety_mode') {
            const mode = this.extractValueByLabels(trimmed, ['Safetymode', 'Safety Mode']);
            if (mode) {
                return `当前安全模式为 ${mode}`;
            }
            return `当前安全模式为 ${trimmed}`;
        }
        if (toolName === 'get_program_state') {
            const state = this.extractValueByLabels(trimmed, ['Programstate', 'Program State']);
            if (state) {
                return `当前程序状态为 ${state}`;
            }
            return `当前程序状态为 ${trimmed}`;
        }
        if (toolName === 'get_serial_number') {
            const sn = this.extractValueByLabels(trimmed, ['SerialNumber', 'Serial Number', 'SN']);
            if (sn) {
                return `当前机器人序列号为 ${sn}`;
            }
            return `当前机器人序列号为 ${trimmed}`;
        }
        return trimmed;
    }
    extractRobotMode(text) {
        const modeMatch = text.match(/Robotmode\s*:\s*([A-Za-z0-9_\-]+)/i);
        if (modeMatch?.[1]) {
            return modeMatch[1];
        }
        return undefined;
    }
    extractValueByLabels(text, labels) {
        for (const label of labels) {
            const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s+/g, '\\s*');
            const match = text.match(new RegExp(`${escaped}\\s*:\\s*([^,;，。\\n]+)`, 'i'));
            if (match?.[1]) {
                return match[1].trim();
            }
        }
        return undefined;
    }
    stripQuotes(text) {
        if (text.startsWith('"') && text.endsWith('"') && text.length >= 2) {
            return text.slice(1, -1);
        }
        return text;
    }
};
exports.CommandsService = CommandsService;
exports.CommandsService = CommandsService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [gateway_state_service_1.GatewayStateService])
], CommandsService);
//# sourceMappingURL=commands.service.js.map