"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
Object.defineProperty(exports, "__esModule", { value: true });
exports.FrameAgent = void 0;
exports.createFrameAgent = createFrameAgent;
/**
 * SmartPerfetto Frame Agent
 *
 * Phase 2.2: AI Agent for frame and scrolling performance analysis
 *
 * This agent specializes in:
 * - Detecting jank frames
 * - Analyzing scrolling performance
 * - Frame production/consumption timing
 * - Present fence analysis
 *
 * Skills wrapped as tools:
 * - jank_frame_detail (comprehensive per-frame analysis)
 * - scrolling_analysis
 * - consumer_jank_detection
 * - sf_frame_consumption
 * - app_frame_production
 * - present_fence_timing
 */
const baseAgent_1 = require("../base/baseAgent");
const adbTools_1 = require("../tools/adbTools");
const thresholds_1 = require("../../../config/thresholds");
const skillCatalog_1 = require("./skillCatalog");
function toNumber(value) {
    if (value === null || value === undefined || value === '')
        return 0;
    const num = Number(value);
    return Number.isFinite(num) ? num : 0;
}
function extractSummaryRow(rawResults, keys) {
    for (const key of keys) {
        const step = rawResults[key];
        if (step && Array.isArray(step.data) && step.data.length > 0) {
            return step.data[0];
        }
    }
    return null;
}
function extractListRows(rawResults, keys) {
    for (const key of keys) {
        const step = rawResults[key];
        if (step && Array.isArray(step.data) && step.data.length > 0) {
            return step.data;
        }
    }
    return null;
}
function parseTsNs(value) {
    if (value === null || value === undefined)
        return null;
    const s = String(value).trim().replace(/^0+/, '');
    if (!/^\d+$/.test(s) || s === '')
        return null;
    return s;
}
function compareTsNs(a, b) {
    if (a.length !== b.length)
        return a.length - b.length;
    if (a < b)
        return -1;
    if (a > b)
        return 1;
    return 0;
}
function buildWindowScope(rawResults, appJankList, consumerJankList) {
    const sessionIds = new Set();
    let startTs = null;
    let endTs = null;
    const addSession = (v) => {
        const n = Number(v);
        if (Number.isFinite(n) && n > 0)
            sessionIds.add(n);
    };
    const addTs = (s, kind) => {
        if (!s)
            return;
        if (kind === 'start') {
            if (startTs === null || compareTsNs(s, startTs) < 0)
                startTs = s;
            return;
        }
        if (endTs === null || compareTsNs(s, endTs) > 0)
            endTs = s;
    };
    const collectFromRows = (rows) => {
        if (!Array.isArray(rows))
            return;
        for (const row of rows) {
            if (!row || typeof row !== 'object')
                continue;
            addSession(row.session_id);
            addSession(row.sessionId);
            addTs(parseTsNs(row.start_ts) || parseTsNs(row.startTs), 'start');
            addTs(parseTsNs(row.end_ts) || parseTsNs(row.endTs) || parseTsNs(row.ts), 'end');
        }
    };
    collectFromRows(appJankList);
    collectFromRows(consumerJankList);
    collectFromRows(extractListRows(rawResults, ['session_jank']));
    collectFromRows(extractListRows(rawResults, ['scroll_sessions']));
    const scope = {
        sessionIds: Array.from(sessionIds).sort((a, b) => a - b),
    };
    if (startTs !== null)
        scope.startTsNs = startTs;
    if (endTs !== null)
        scope.endTsNs = endTs;
    return scope;
}
/**
 * Build a finding for jank detection results.
 *
 * Severity classification uses configurable thresholds:
 * - Critical: rate >= criticalRate OR count >= criticalCount
 * - Warning: rate >= warningRate OR count >= warningCount
 * - Info: below warning thresholds
 *
 * @param skillId - Source skill identifier
 * @param titlePrefix - Prefix for finding title
 * @param jankCount - Number of jank frames
 * @param jankRate - Jank rate as percentage
 * @param details - Additional details to include
 * @param thresholds - Optional custom thresholds (defaults to DEFAULT_JANK_THRESHOLDS)
 */
function buildJankFinding(skillId, titlePrefix, jankCount, jankRate, details, thresholds = thresholds_1.DEFAULT_JANK_THRESHOLDS) {
    const rate = jankRate > 0 ? jankRate : (jankCount > 0 ? (jankCount / Math.max(details.total_frames || 1, 1)) * 100 : 0);
    let severity = 'info';
    // Use configurable thresholds for severity classification
    if (rate >= thresholds.criticalRate || jankCount >= thresholds.criticalCount) {
        severity = 'critical';
    }
    else if (rate >= thresholds.warningRate || jankCount >= thresholds.warningCount) {
        severity = 'warning';
    }
    return {
        id: `${skillId}_${Date.now()}`,
        category: 'frame',
        type: 'issue',
        severity,
        title: `${titlePrefix}: ${jankCount} 帧 (${rate.toFixed(1)}%)`,
        description: '滑动存在明显掉帧',
        source: skillId,
        confidence: thresholds_1.DEFAULT_RAW_FINDING_CONFIDENCE,
        details: { jankCount, jankRate: rate, summary: details },
    };
}
// =============================================================================
// Frame Agent Implementation
// =============================================================================
/**
 * Frame Agent - AI agent for frame and scrolling analysis
 */
class FrameAgent extends baseAgent_1.BaseAgent {
    constructor(modelRouter) {
        super({
            id: 'frame_agent',
            name: 'Frame Analysis Agent',
            domain: 'frame',
            description: 'AI agent specialized in frame timing, jank detection, and scrolling performance analysis',
            tools: [...(0, adbTools_1.getAdbAgentTools)()],
            maxIterations: 3,
            confidenceThreshold: 0.7,
            canDelegate: true,
            delegateTo: ['cpu_agent', 'binder_agent', 'memory_agent'],
        }, modelRouter, skillCatalog_1.FRAME_SKILLS);
    }
    // ==========================================================================
    // Domain-Specific Finding Extraction (Override)
    // ==========================================================================
    /**
     * Frame agent extracts additional findings from raw results beyond diagnostics,
     * including jank summaries, consumer jank, and jank frame lists.
     *
     * Key design: Merge jank statistics from multiple sources into a single Finding
     * to avoid duplicate/conflicting reports (e.g., "13 frames" vs "39 frames" vs "25 frames").
     *
     * Priority order for data source selection (default for user-facing jank analysis):
     * 1. Scrolling skill frame list (get_app_jank_frames/app_jank_frames) - concrete per-frame data
     * 2. Scrolling skill summary (performance_summary) - same detection logic aggregate
     * 3. Consumer-side detection (consumerSummary) - supplemental perspective (SF/HWC layer)
     *
     * IMPORTANT trade-off notes for Android performance experts:
     * - 默认以 scrolling_analysis 定义的“真实掉帧”作为主口径，避免跨技能口径混淆
     * - consumer_jank_detection 保留为补充证据，用于定位 SF/HWC/GPU 层问题
     * - All sources are preserved in evidence[] for expert review
     */
    extractFindingsFromResult(result, skillId, category) {
        // Start with base diagnostic findings
        const findings = super.extractFindingsFromResult(result, skillId, category);
        // Add domain-specific extraction from raw results
        const rawResults = result.rawResults || {};
        const perfSummary = extractSummaryRow(rawResults, ['performance_summary', 'perf_summary']);
        const consumerSummary = extractSummaryRow(rawResults, ['consumer_jank_summary']);
        const appJankList = extractListRows(rawResults, ['get_app_jank_frames', 'app_jank_frames']);
        const consumerJankList = extractListRows(rawResults, ['consumer_jank_frames']);
        const windowScope = buildWindowScope(rawResults, appJankList, consumerJankList);
        // Collect all available jank data sources
        const perfJankCount = perfSummary
            ? toNumber(perfSummary.janky_frames ?? perfSummary.jank_frames ?? perfSummary.jank_count)
            : 0;
        const perfJankRate = perfSummary ? toNumber(perfSummary.jank_rate ?? perfSummary.app_jank_rate) : 0;
        const consumerJankCount = consumerSummary
            ? toNumber(consumerSummary.consumer_jank_frames ?? consumerSummary.jank_frames)
            : 0;
        const consumerJankRate = consumerSummary
            ? toNumber(consumerSummary.consumer_jank_rate)
            : 0;
        const appListJankCount = appJankList?.length ?? 0;
        const consumerListJankCount = consumerJankList?.length ?? 0;
        // Select the most reliable data source
        // Priority: scrolling frame list > scrolling summary > consumer summary/list
        let primaryJankCount = 0;
        let primaryJankRate = 0;
        let dataSource = '';
        const evidenceSources = [];
        if (appListJankCount > 0) {
            primaryJankCount = appListJankCount;
            // Estimate rate if we have total frames from perfSummary
            const totalFrames = perfSummary ? toNumber(perfSummary.total_frames) : 0;
            primaryJankRate = totalFrames > 0 ? (appListJankCount / totalFrames) * 100 : 0;
            dataSource = 'Scrolling 帧列表';
        }
        else if (perfJankCount > 0 || perfJankRate > 0) {
            primaryJankCount = perfJankCount;
            primaryJankRate = perfJankRate;
            dataSource = 'Scrolling 概览';
        }
        else if (consumerJankCount > 0 || consumerJankRate > 0 || consumerListJankCount > 0) {
            primaryJankCount = consumerJankCount > 0 ? consumerJankCount : consumerListJankCount;
            primaryJankRate = consumerJankRate;
            dataSource = '消费端检测(补充)';
        }
        // Build evidence list showing all data sources for transparency
        if (appListJankCount > 0) {
            evidenceSources.push(`Scrolling 帧列表: ${appListJankCount} 帧`);
        }
        if (perfJankCount > 0 || perfJankRate > 0) {
            evidenceSources.push(`Scrolling 概览: ${perfJankCount} 帧 (${perfJankRate.toFixed(1)}%)`);
        }
        if (consumerJankCount > 0 || consumerJankRate > 0) {
            evidenceSources.push(`消费端: ${consumerJankCount} 帧 (${consumerJankRate.toFixed(1)}%)`);
        }
        if (consumerListJankCount > 0) {
            evidenceSources.push(`消费端帧列表: ${consumerListJankCount} 帧`);
        }
        // Generate a single consolidated Finding for jank detection
        if (primaryJankCount > 0 || primaryJankRate > 0) {
            const titlePrefix = windowScope.sessionIds.length === 1
                ? `区间${windowScope.sessionIds[0]} 滑动卡顿检测`
                : '滑动卡顿检测';
            const scopeParts = [];
            if (windowScope.sessionIds.length > 0) {
                scopeParts.push(`session=${windowScope.sessionIds.join(',')}`);
            }
            if (windowScope.startTsNs && windowScope.endTsNs) {
                scopeParts.push(`时间窗=${windowScope.startTsNs}~${windowScope.endTsNs}`);
            }
            const severity = primaryJankRate >= thresholds_1.DEFAULT_JANK_THRESHOLDS.criticalRate ||
                primaryJankCount >= thresholds_1.DEFAULT_JANK_THRESHOLDS.criticalCount
                ? 'critical'
                : primaryJankRate >= thresholds_1.DEFAULT_JANK_THRESHOLDS.warningRate ||
                    primaryJankCount >= thresholds_1.DEFAULT_JANK_THRESHOLDS.warningCount
                    ? 'warning'
                    : 'info';
            findings.push({
                id: `${skillId}_jank_consolidated_${Date.now()}`,
                category: 'frame',
                type: 'issue',
                severity,
                title: `${titlePrefix}: ${primaryJankCount} 帧 (${primaryJankRate.toFixed(1)}%)`,
                description: `数据来源: ${dataSource}${scopeParts.length > 0 ? `；范围: ${scopeParts.join('，')}` : ''}`,
                source: skillId,
                confidence: thresholds_1.DEFAULT_RAW_FINDING_CONFIDENCE,
                details: {
                    jankCount: primaryJankCount,
                    jankRate: primaryJankRate,
                    dataSource,
                    sourceWindow: {
                        sessionIds: windowScope.sessionIds,
                        ...(windowScope.startTsNs && { startTsNs: windowScope.startTsNs }),
                        ...(windowScope.endTsNs && { endTsNs: windowScope.endTsNs }),
                    },
                    // Include all sources for debugging/comparison
                    allSources: {
                        scrollingList: appListJankCount > 0 ? { count: appListJankCount } : null,
                        scrollingSummary: perfJankCount > 0 ? { count: perfJankCount, rate: perfJankRate } : null,
                        consumer: consumerJankCount > 0 ? { count: consumerJankCount, rate: consumerJankRate } : null,
                        consumerList: consumerListJankCount > 0 ? { count: consumerListJankCount } : null,
                    },
                    sample: (appJankList?.slice(0, 5) ?? consumerJankList?.slice(0, 5)) || [],
                },
                evidence: evidenceSources,
            });
        }
        return findings;
    }
    // ==========================================================================
    // Abstract Method Implementations
    // ==========================================================================
    buildUnderstandingPrompt(task) {
        return `你是一个帧性能分析专家 Agent，负责分析 Android 应用的帧渲染和滑动性能问题。

## 任务
${task.description}

## 上下文
- 用户查询: ${task.context.query}
${task.context.hypothesis ? `- 当前假设: ${task.context.hypothesis.description}` : ''}
${task.context.relevantFindings?.length ? `- 相关发现: ${task.context.relevantFindings.map(f => f.title).join(', ')}` : ''}
${this.formatTaskContext(task)}

${this.getToolSectionForPrompt()}

## 任务
分析这个任务，返回你的理解：

请以 JSON 格式返回：
{
  "objective": "任务的主要目标",
  "questions": ["需要回答的关键问题1", "问题2"],
  "relevantAreas": ["相关分析领域"],
  "recommendedTools": ["建议使用的工具名称"],
  "constraints": ["分析约束或限制"],
  "confidence": 0.0-1.0
}`;
    }
    buildPlanningPrompt(understanding, task) {
        return `你是一个帧性能分析专家 Agent，需要规划执行步骤。

## 目标
${understanding.objective}

## 关键问题
${understanding.questions.map((q, i) => `${i + 1}. ${q}`).join('\n')}

${this.getToolSectionForPrompt()}

## 推荐工具
${understanding.recommendedTools.join(', ')}

## 任务
创建分析执行计划。

请以 JSON 格式返回：
{
  "steps": [
    {
      "toolName": "工具名称（必须是上方列出的工具之一）",
      "params": {},
      "purpose": "这一步的目的"
    }
  ],
  "expectedOutcomes": ["预期结果1", "预期结果2"],
  "estimatedTimeMs": 预计执行时间毫秒,
  "confidence": 0.0-1.0
}

注意：每个步骤独立执行，不需要指定步骤依赖。`;
    }
    buildReflectionPrompt(result, task) {
        const findings = result.findings.map(f => `- [${f.severity}] ${f.title}`).join('\n');
        const steps = result.steps.map(s => `- ${s.toolName}: ${s.result.success ? '成功' : '失败'} - ${s.observations.join(', ')}`).join('\n');
        return `你是一个帧性能分析专家 Agent，需要反思分析结果。

## 原始任务
${task.description}

## 执行结果
${steps}

## 发现的问题
${findings || '无'}

## 任务
反思分析结果，评估是否达成目标，识别差距。

请以 JSON 格式返回：
{
  "insights": ["分析洞察1", "洞察2"],
  "objectivesMet": true/false,
  "findingsConfidence": 0.0-1.0,
  "gaps": ["分析差距1", "差距2"],
  "nextSteps": ["建议的后续步骤"],
  "hypothesisUpdates": [
    {
      "hypothesisId": "假设ID（如果有的话）",
      "action": "support/contradict/confirm/reject",
      "reason": "原因"
    }
  ],
  "questionsForOthers": [
    {
      "toAgent": "其他Agent ID",
      "question": "需要问的问题",
      "priority": 1-10
    }
  ]
}`;
    }
    async generateHypotheses(findings, task) {
        const hypotheses = [];
        if (findings.length === 0) {
            return hypotheses;
        }
        // Generate hypotheses based on findings
        const criticalFindings = findings.filter(f => f.severity === 'critical');
        const jankFindings = findings.filter(f => f.title.toLowerCase().includes('jank') || f.title.includes('掉帧'));
        if (jankFindings.length > 0) {
            // Check for patterns in jank
            const hasMainThreadBlocking = findings.some(f => f.title.includes('主线程') || f.title.includes('UI 线程') || f.title.includes('main thread'));
            const hasRenderThreadIssue = findings.some(f => f.title.includes('渲染线程') || f.title.includes('RenderThread'));
            const hasGPUIssue = findings.some(f => f.title.includes('GPU') || f.title.includes('合成'));
            if (hasMainThreadBlocking) {
                hypotheses.push(this.createHypothesis('主线程阻塞导致帧超时', 0.7, jankFindings.map(f => ({
                    id: f.id,
                    description: f.title,
                    source: 'frame_agent',
                    type: 'finding',
                    strength: 0.8,
                }))));
            }
            if (hasRenderThreadIssue) {
                hypotheses.push(this.createHypothesis('RenderThread 渲染耗时过长', 0.6, jankFindings.map(f => ({
                    id: f.id,
                    description: f.title,
                    source: 'frame_agent',
                    type: 'finding',
                    strength: 0.7,
                }))));
            }
            if (hasGPUIssue) {
                hypotheses.push(this.createHypothesis('GPU/合成层存在性能瓶颈', 0.5, jankFindings.map(f => ({
                    id: f.id,
                    description: f.title,
                    source: 'frame_agent',
                    type: 'finding',
                    strength: 0.6,
                }))));
            }
        }
        // Generate hypothesis from critical findings
        for (const finding of criticalFindings) {
            hypotheses.push(this.createHypothesis(`${finding.title} 是主要性能瓶颈`, finding.confidence || 0.7, [{
                    id: finding.id,
                    description: finding.title,
                    source: 'frame_agent',
                    type: 'finding',
                    strength: finding.confidence || 0.8,
                }]));
        }
        return hypotheses;
    }
    getRecommendedTools(context) {
        const query = context.query?.toLowerCase() || '';
        const tools = [];
        const needsConsumerPerspective = query.includes('surfaceflinger') ||
            query.includes('sf') ||
            query.includes('消费端') ||
            query.includes('合成') ||
            query.includes('hwc') ||
            query.includes('gpu') ||
            query.includes('renderthread') ||
            query.includes('display') ||
            query.includes('呈现') ||
            query.includes('fence');
        // Always start with overview analysis
        tools.push('analyze_scrolling');
        if (query.includes('帧') || query.includes('frame')) {
            tools.push('analyze_app_frames');
            tools.push('analyze_sf_frames');
        }
        if (query.includes('滑动') || query.includes('scroll') || query.includes('列表')) {
            tools.push('analyze_scrolling');
        }
        if (query.includes('vsync') || query.includes('fence') || query.includes('延迟')) {
            tools.push('analyze_present_fence');
        }
        if (needsConsumerPerspective) {
            tools.push('detect_consumer_jank');
            tools.push('analyze_surfaceflinger');
        }
        if (query.includes('gpu') || query.includes('渲染') || query.includes('render')) {
            tools.push('analyze_gpu');
        }
        if (query.includes('surfaceflinger') || query.includes('sf') || query.includes('合成') || query.includes('hwc')) {
            tools.push('analyze_surfaceflinger');
        }
        // Default: use scrolling analysis
        if (tools.length === 0) {
            tools.push('analyze_scrolling');
        }
        // Remove duplicates
        return [...new Set(tools)];
    }
}
exports.FrameAgent = FrameAgent;
/**
 * Factory function to create FrameAgent
 */
function createFrameAgent(modelRouter) {
    return new FrameAgent(modelRouter);
}
exports.default = FrameAgent;
//# sourceMappingURL=frameAgent.js.map