"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.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SkillExecutorAdapter = void 0;
exports.createSkillExecutorAdapter = createSkillExecutorAdapter;
/**
 * Adapter that implements SkillExecutorInterface for decision trees
 */
class SkillExecutorAdapter {
    constructor(options = {}) {
        this.cache = new Map();
        this.enableCache = options.enableCache ?? true;
    }
    /**
     * Execute a skill and return its result
     */
    async execute(skillId, params, context) {
        const cacheKey = this.buildCacheKey(skillId, params, context);
        // Check cache first
        if (this.enableCache && this.cache.has(cacheKey)) {
            console.log(`[SkillExecutorAdapter] Cache hit for skill: ${skillId}`);
            return this.cache.get(cacheKey);
        }
        console.log(`[SkillExecutorAdapter] Executing skill: ${skillId}`);
        console.log(`[SkillExecutorAdapter] Params: ${JSON.stringify(params)}`);
        try {
            // Dynamically import to avoid circular dependencies
            const { SkillAnalysisAdapter } = await Promise.resolve().then(() => __importStar(require('../../services/skillEngine/skillAnalysisAdapter')));
            // Create adapter instance
            const adapter = new SkillAnalysisAdapter(context.traceProcessorService);
            // Build request
            const request = {
                traceId: context.traceId,
                skillId,
                packageName: params.packageName || context.packageName,
                // Pass additional params if the skill supports them
                ...params,
            };
            // Execute skill
            const response = await adapter.analyze(request);
            // Transform result to a more usable format for decision trees
            const result = this.transformResult(skillId, response);
            // Cache the result
            if (this.enableCache) {
                this.cache.set(cacheKey, result);
            }
            return result;
        }
        catch (error) {
            console.error(`[SkillExecutorAdapter] Error executing skill ${skillId}:`, error);
            throw error;
        }
    }
    /**
     * Transform skill response to a flattened format for decision tree evaluation
     */
    transformResult(skillId, response) {
        // Base result with common fields
        const result = {
            skillId: response.skillId,
            success: response.success,
            summary: response.summary,
            diagnostics: response.diagnostics || [],
            sections: response.sections || {},
        };
        // Extract layered data
        if (response.layeredResult?.layers) {
            const layers = response.layeredResult.layers;
            // Keep original layered structure for decision trees that read layers.* directly.
            result.layers = layers;
            result.layeredResult = response.layeredResult;
            // Merge overview data into result
            if (layers.overview) {
                for (const [key, value] of Object.entries(layers.overview)) {
                    if (typeof value === 'object' && value !== null && 'data' in value) {
                        result[key] = value.data;
                    }
                    else {
                        result[key] = value;
                    }
                }
            }
            // Merge list data into result
            if (layers.list) {
                result.sessions = layers.list;
                for (const [key, value] of Object.entries(layers.list)) {
                    if (typeof value === 'object' && value !== null && 'data' in value) {
                        result[`list_${key}`] = value.data;
                    }
                }
            }
            // Include deep data
            if (layers.deep) {
                result.frameDetails = layers.deep;
            }
        }
        // Backfill startup aliases used by decision trees.
        // Layered results are keyed by step id (e.g. get_startups), while
        // startup decision logic may read save_as alias (startups).
        if (skillId === 'startup_analysis') {
            const startupRows = this.extractStartupRows(result, response);
            if (startupRows.length > 0) {
                if (!Array.isArray(result.startups) || result.startups.length === 0) {
                    result.startups = startupRows;
                }
                if (!Array.isArray(result.get_startups) || result.get_startups.length === 0) {
                    result.get_startups = startupRows;
                }
            }
        }
        // Skill-specific transformations
        result.transformed = this.applySkillSpecificTransform(skillId, result, response);
        return result;
    }
    /**
     * Apply skill-specific transformations to make data easier to query
     */
    applySkillSpecificTransform(skillId, result, response) {
        switch (skillId) {
            case 'scrolling_analysis':
                return this.transformScrollingResult(result);
            case 'startup_analysis':
                return this.transformStartupResult(result);
            case 'jank_frame_detail':
                return this.transformJankResult(result);
            case 'cpu_analysis':
                return this.transformCpuResult(result);
            case 'binder_analysis':
                return this.transformBinderResult(result);
            default:
                return result;
        }
    }
    /**
     * Transform scrolling analysis result
     */
    transformScrollingResult(result) {
        // scrolling_analysis currently emits overview step `performance_summary`.
        // Keep compatibility with older `scrolling_summary` naming while preferring the new schema.
        const performanceSummaryRows = this.normalizeRowArray(result.performance_summary);
        const legacySummaryRows = this.normalizeRowArray(result.scrolling_summary);
        const summaryRows = performanceSummaryRows.length > 0 ? performanceSummaryRows : legacySummaryRows;
        const summary = summaryRows[0] || {};
        const sessionRows = this.normalizeRowArray(result.list_scroll_sessions);
        const sessionFpsList = sessionRows
            .map((row) => this.extractNumber(row, ['session_fps', 'fps', 'avg_fps']))
            .filter((v) => v > 0);
        const actualFps = this.extractNumber(summary, ['actual_fps', 'avg_fps', 'averageFps']);
        const rawJankRate = this.extractNumber(summary, ['jank_rate', 'janky_rate', 'jankyRate']);
        const normalizedJankRate = rawJankRate > 1 ? rawJankRate / 100 : rawJankRate;
        const avgFrameDurNs = this.extractNumber(summary, ['avg_frame_dur']);
        const avgFrameDurMs = this.extractNumber(summary, ['avg_frame_time_ms', 'avgFrameTimeMs']);
        const minFpsFromSessions = sessionFpsList.length > 0 ? Math.min(...sessionFpsList) : 0;
        return {
            avg_fps: actualFps,
            min_fps: this.extractNumber(summary, ['min_fps', 'minFps']) || minFpsFromSessions,
            janky_frame_count: this.extractNumber(summary, ['janky_frames', 'janky_frame_count', 'jankyFrameCount', 'janky_count']),
            total_frame_count: this.extractNumber(summary, ['total_frames', 'total_frame_count', 'totalFrameCount', 'total_count']),
            janky_rate: normalizedJankRate,
            jank_rate_pct: rawJankRate > 1 ? rawJankRate : rawJankRate * 100,
            avg_frame_time_ms: avgFrameDurMs || (avgFrameDurNs > 0 ? avgFrameDurNs / 1e6 : 0),
            session_count: sessionRows.length || (result.sessions ? Object.keys(result.sessions).length : 0),
        };
    }
    /**
     * Transform startup analysis result
     *
     * startup_analysis.skill.yaml saves data as 'startups' (save_as: startups),
     * with columns: startup_type, dur_ms, ttid_ms, ttfd_ms, rating, perfetto_start.
     * Phase-level breakdown (process_start_time, etc.) is NOT available from
     * startup_analysis — it would require future startup_detail enhancements.
     */
    transformStartupResult(result) {
        const startups = this.extractStartupRows(result);
        if (startups.length > 0) {
            result.startups = startups;
            if (!Array.isArray(result.get_startups) || result.get_startups.length === 0) {
                result.get_startups = startups;
            }
        }
        const first = startups.length > 0 ? startups[0] : {};
        const summary = result.startup_summary?.data || result.startup_summary || {};
        const source = Object.keys(first).length > 0 ? first : summary;
        return {
            // startup_analysis outputs startup_type (not launch_type)
            launch_type: source.startup_type || source.launch_type || 'cold',
            startup_type: source.startup_type || source.launch_type || 'cold',
            // startup_analysis outputs ttid_ms (not ttid)
            ttid: this.extractNumber(source, ['ttid_ms', 'ttid', 'time_to_initial_display']),
            ttid_ms: this.extractNumber(source, ['ttid_ms']),
            dur_ms: this.extractNumber(source, ['dur_ms', 'duration_ms', 'dur']),
            ttfd: this.extractNumber(source, ['ttfd_ms', 'ttfd', 'time_to_full_display']),
            // Phase breakdown fields — not available from startup_analysis currently.
            // These are placeholders for when startup_detail adds phase-level breakdown.
            process_start_time: this.extractNumber(source, ['process_start_time', 'processStartTime']),
            application_init_time: this.extractNumber(source, ['application_init_time', 'applicationInitTime']),
            activity_create_time: this.extractNumber(source, ['activity_create_time', 'activityCreateTime']),
            first_frame_time: this.extractNumber(source, ['first_frame_time', 'firstFrameTime']),
            zygote_fork_time: this.extractNumber(source, ['zygote_fork_time', 'zygoteForkTime', 'fork_time']),
            layout_inflate_time: this.extractNumber(source, ['layout_inflate_time', 'layoutInflateTime', 'inflate_time']),
        };
    }
    extractStartupRows(result, response) {
        const candidates = [
            result.startups,
            result.get_startups,
            result.list_get_startups,
            response?.sections?.startups,
            response?.sections?.get_startups,
            response?.layeredResult?.layers?.overview?.get_startups,
            response?.layeredResult?.layers?.list?.get_startups,
        ];
        for (const candidate of candidates) {
            const rows = this.normalizeRowArray(candidate);
            if (rows.length > 0) {
                return rows;
            }
        }
        return [];
    }
    normalizeRowArray(value) {
        if (Array.isArray(value)) {
            return value.filter((row) => !!row && typeof row === 'object' && !Array.isArray(row));
        }
        if (!value || typeof value !== 'object') {
            return [];
        }
        if (Array.isArray(value.data)) {
            return this.normalizeRowArray(value.data);
        }
        const columns = Array.isArray(value.columns) ? value.columns : [];
        const rows = Array.isArray(value.rows) ? value.rows : [];
        if (columns.length > 0 && rows.length > 0) {
            return rows
                .filter((row) => Array.isArray(row))
                .map((row) => this.mapColumnsToRow(columns, row));
        }
        return [];
    }
    mapColumnsToRow(columns, row) {
        const mapped = {};
        for (let i = 0; i < columns.length; i++) {
            mapped[columns[i]] = row[i];
        }
        return mapped;
    }
    /**
     * Transform jank analysis result
     */
    transformJankResult(result) {
        const summary = result.jank_summary?.data || result.jank_summary || {};
        const rootCause = this.findFirstRowByStepId(result, ['root_cause_summary', 'root_cause']);
        const reasonCode = String(rootCause?.reason_code || '').toLowerCase();
        const jankResponsibility = String(rootCause?.jank_responsibility || '').toUpperCase();
        const amplificationPath = String(rootCause?.amplification_path || '').toLowerCase();
        const mechanismGroup = String(rootCause?.mechanism_group || '').toLowerCase();
        const diagnosticsText = Array.isArray(result.diagnostics)
            ? result.diagnostics.map((d) => String(d?.message || d?.diagnosis || '')).join(' ')
            : '';
        const diagnosticsLower = diagnosticsText.toLowerCase();
        const appDeadlineMissed = jankResponsibility === 'APP' ||
            amplificationPath.includes('app_deadline') ||
            diagnosticsLower.includes('app deadline');
        const sfStuffing = jankResponsibility === 'SF' ||
            amplificationPath.includes('sf_consumer') ||
            diagnosticsLower.includes('surfaceflinger') ||
            diagnosticsLower.includes('sf stuffing');
        const binderBlocking = reasonCode === 'binder_sync_blocking' ||
            mechanismGroup.includes('blocking') ||
            diagnosticsLower.includes('binder');
        const mainSliceRows = this.findRowsByStepId(result, ['main_thread_slices', 'main_slices']);
        const renderSliceRows = this.findRowsByStepId(result, ['render_thread_slices', 'render_slices']);
        const avgMainDur = this.averageByKey(mainSliceRows, ['dur_ms', 'total_ms']);
        const avgRenderDur = this.averageByKey(renderSliceRows, ['dur_ms', 'total_ms']);
        return {
            janky_frame_count: this.extractNumber(summary, ['janky_frame_count', 'jankyFrameCount']),
            total_frame_count: this.extractNumber(summary, ['total_frame_count', 'totalFrameCount']),
            avg_jank_duration_ms: this.extractNumber(summary, ['avg_jank_duration_ms', 'avgJankDurationMs']),
            max_jank_duration_ms: this.extractNumber(summary, ['max_jank_duration_ms', 'maxJankDurationMs']),
            // Frame breakdown by cause
            main_thread_jank_count: this.extractNumber(summary, ['main_thread_jank_count', 'mainThreadJankCount']),
            render_thread_jank_count: this.extractNumber(summary, ['render_thread_jank_count', 'renderThreadJankCount']),
            gpu_jank_count: this.extractNumber(summary, ['gpu_jank_count', 'gpuJankCount']),
            sf_jank_count: this.extractNumber(summary, ['sf_jank_count', 'sfJankCount']),
            // Decision tree compatibility fields
            app_deadline_missed_ratio: appDeadlineMissed ? 1 : 0,
            sf_stuffing_ratio: sfStuffing ? 1 : 0,
            has_binder_block: binderBlocking,
            avg_do_frame_time: avgMainDur,
            avg_render_time: avgRenderDur,
        };
    }
    findRowsByStepId(result, stepIds) {
        for (const stepId of stepIds) {
            const direct = this.normalizeRowArray(result[stepId]);
            if (direct.length > 0)
                return direct;
            const listRows = this.normalizeRowArray(result[`list_${stepId}`]);
            if (listRows.length > 0)
                return listRows;
        }
        const deep = result.layers?.deep;
        if (deep && typeof deep === 'object') {
            for (const session of Object.values(deep)) {
                if (!session || typeof session !== 'object')
                    continue;
                for (const stepId of stepIds) {
                    const step = session[stepId];
                    if (!step)
                        continue;
                    const rows = this.normalizeRowArray(step.data ?? step);
                    if (rows.length > 0)
                        return rows;
                }
            }
        }
        return [];
    }
    findFirstRowByStepId(result, stepIds) {
        const rows = this.findRowsByStepId(result, stepIds);
        return rows.length > 0 ? rows[0] : {};
    }
    averageByKey(rows, keys) {
        if (!rows || rows.length === 0)
            return 0;
        let sum = 0;
        let count = 0;
        for (const row of rows) {
            const value = this.extractNumber(row, keys);
            if (value > 0) {
                sum += value;
                count += 1;
            }
        }
        return count > 0 ? sum / count : 0;
    }
    /**
     * Transform CPU analysis result
     */
    transformCpuResult(result) {
        const summary = result.cpu_summary?.data || result.cpu_summary || {};
        return {
            avg_cpu_usage: this.extractNumber(summary, ['avg_cpu_usage', 'avgCpuUsage']),
            max_cpu_usage: this.extractNumber(summary, ['max_cpu_usage', 'maxCpuUsage']),
            main_thread_cpu_usage: this.extractNumber(summary, ['main_thread_cpu_usage', 'mainThreadCpuUsage']),
            render_thread_cpu_usage: this.extractNumber(summary, ['render_thread_cpu_usage', 'renderThreadCpuUsage']),
            avg_runnable_time: this.extractNumber(summary, ['avg_runnable_time', 'avgRunnableTime', 'avg_runnable_ms', 'runnable_avg_ms']),
        };
    }
    /**
     * Transform Binder analysis result
     */
    transformBinderResult(result) {
        const summary = result.binder_summary?.data || result.binder_summary || {};
        return {
            total_binder_calls: this.extractNumber(summary, ['total_binder_calls', 'totalBinderCalls']),
            avg_binder_duration_ms: this.extractNumber(summary, ['avg_binder_duration_ms', 'avgBinderDurationMs']),
            max_binder_duration_ms: this.extractNumber(summary, ['max_binder_duration_ms', 'maxBinderDurationMs']),
            blocking_binder_count: this.extractNumber(summary, ['blocking_binder_count', 'blockingBinderCount']),
        };
    }
    /**
     * Extract a number from an object with multiple possible key names
     */
    extractNumber(obj, keys) {
        if (!obj)
            return 0;
        for (const key of keys) {
            if (key in obj) {
                const value = obj[key];
                if (typeof value === 'number')
                    return value;
                if (typeof value === 'string') {
                    const parsed = parseFloat(value);
                    if (!isNaN(parsed))
                        return parsed;
                }
            }
        }
        return 0;
    }
    /**
     * Build a cache key for skill results
     */
    buildCacheKey(skillId, params, context) {
        return `${context.traceId}:${skillId}:${JSON.stringify(params)}`;
    }
    /**
     * Clear the result cache
     */
    clearCache() {
        this.cache.clear();
    }
    /**
     * Get cache statistics
     */
    getCacheStats() {
        return {
            size: this.cache.size,
            keys: Array.from(this.cache.keys()),
        };
    }
}
exports.SkillExecutorAdapter = SkillExecutorAdapter;
/**
 * Create a skill executor adapter instance
 */
function createSkillExecutorAdapter(options) {
    return new SkillExecutorAdapter(options);
}
exports.default = SkillExecutorAdapter;
//# sourceMappingURL=skillExecutorAdapter.js.map