"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.buildDisplayedScenes = buildDisplayedScenes;
exports.buildAnalysisIntervals = buildAnalysisIntervals;
exports.isAnalysisEligibleScene = isAnalysisEligibleScene;
exports.selectAnalysisEligibleScenes = selectAnalysisEligibleScenes;
exports.filterDisplayedScenesForSelection = filterDisplayedScenesForSelection;
exports.computePriority = computePriority;
const helpers_1 = require("../strategies/helpers");
const domainManifest_1 = require("../config/domainManifest");
const PROBLEM_THRESHOLDS = {
    cold_start: { durationMs: 1000 },
    warm_start: { durationMs: 600 },
    hot_start: { durationMs: 200 },
    scroll: { fps: 50 },
    inertial_scroll: { fps: 50 },
    tap: { durationMs: 200 },
    long_press: { durationMs: 500 },
    navigation: { durationMs: 500 },
    anr: { durationMs: 5000 },
    window_transition: { durationMs: 500 },
};
const SCENE_DISPLAY_NAMES = {
    cold_start: '冷启动',
    warm_start: '温启动',
    hot_start: '热启动',
    scroll_start: '滑动启动',
    scroll: '滑动浏览',
    inertial_scroll: '惯性滑动',
    navigation: '页面跳转',
    app_switch: '应用切换',
    app_foreground: '应用内',
    home_screen: '桌面',
    screen_on: '屏幕点亮',
    screen_off: '屏幕熄灭',
    screen_sleep: '屏幕休眠',
    screen_unlock: '解锁屏幕',
    notification: '通知操作',
    split_screen: '分屏操作',
    pip: '画中画',
    tap: '点击',
    long_press: '长按',
    idle: '空闲',
    jank_region: '性能问题区间',
    back_key: '返回键',
    home_key: 'Home键',
    recents_key: '最近任务键',
    anr: 'ANR',
    ime_show: '键盘弹出',
    ime_hide: '键盘收起',
    window_transition: '窗口转场',
};
const SCENE_DEDUPE_TOLERANCE_NS = 150000000n;
const SCROLL_CHAIN_TOLERANCE_NS = 250000000n;
const CONTEXT_WINDOW_NS = 750000000n;
const MAX_CONTEXT_ROWS_PER_GROUP = 6;
const CLEAN_TIMELINE_SCENE_TYPES = new Set([
    'cold_start',
    'warm_start',
    'hot_start',
    'scroll',
    'inertial_scroll',
    'tap',
    'long_press',
    'idle',
    'app_foreground',
    'home_screen',
    'screen_on',
    'screen_off',
    'screen_sleep',
    'screen_unlock',
    'notification',
    'split_screen',
    'pip',
    'back_key',
    'home_key',
    'recents_key',
    'anr',
    'ime_show',
    'ime_hide',
    'window_transition',
]);
/** Known launcher / home-screen package patterns */
const LAUNCHER_PATTERNS = [
    'miui.home', 'launcher', 'trebuchet', 'lawnchair',
    'nexuslauncher', 'home', 'oneplus.launcher',
];
function isLauncherPackage(pkg) {
    const lower = pkg.toLowerCase();
    return LAUNCHER_PATTERNS.some((p) => lower.includes(p));
}
function buildDisplayedScenes(envelopes) {
    const scenes = [];
    const jankRowsForFallback = [];
    const rowsByStep = new Map();
    let hasGestureLikeScene = false;
    let traceDurationSec = 0;
    for (const env of envelopes) {
        if (env?.meta?.skillId !== 'scene_reconstruction')
            continue;
        const stepId = env.meta?.stepId;
        if (!stepId)
            continue;
        const rows = (0, helpers_1.payloadToObjectRows)(env.data);
        rowsByStep.set(stepId, rows);
        if (stepId === 'trace_time_range') {
            const first = rows[0];
            if (first?.duration_sec)
                traceDurationSec = Number(first.duration_sec) || 0;
            continue;
        }
        if (rows.length === 0)
            continue;
        if (stepId === 'app_launches') {
            for (const row of rows) {
                const scene = sceneFromAppLaunch(row, scenes.length);
                if (scene)
                    scenes.push(scene);
            }
        }
        else if (stepId === 'user_gestures') {
            for (const row of rows) {
                const scene = sceneFromUserGesture(row, scenes.length);
                if (scene) {
                    scenes.push(scene);
                    hasGestureLikeScene = true;
                }
            }
        }
        else if (stepId === 'inertial_scrolls') {
            for (const row of rows) {
                const scene = sceneFromInertialScroll(row, scenes.length);
                if (scene) {
                    scenes.push(scene);
                    hasGestureLikeScene = true;
                }
            }
        }
        else if (stepId === 'idle_periods') {
            for (const row of rows) {
                const scene = sceneFromIdlePeriod(row, scenes.length);
                if (scene)
                    scenes.push(scene);
            }
        }
        else if (stepId === 'top_app_changes') {
            for (const row of rows) {
                const scene = sceneFromTopAppChange(row, scenes.length);
                if (scene)
                    scenes.push(scene);
            }
        }
        else if (stepId === 'scroll_initiation') {
            // Previously missing from the legacy extractor's handled step list.
            for (const row of rows) {
                const scene = sceneFromScrollInitiation(row, scenes.length);
                if (scene)
                    scenes.push(scene);
            }
        }
        else if (stepId === 'screen_state_changes') {
            // Previously missing from the legacy extractor's handled step list.
            for (const row of rows) {
                const scene = sceneFromScreenStateChange(row, scenes.length);
                if (scene)
                    scenes.push(scene);
            }
        }
        else if (stepId === 'system_events') {
            for (const row of rows) {
                const scene = sceneFromSystemEvent(row, scenes.length);
                if (scene)
                    scenes.push(scene);
            }
        }
        else if (stepId === 'navigation_keys' || stepId === 'gesture_navigation') {
            for (const row of rows) {
                const scene = sceneFromNavigationKey(row, scenes.length);
                if (scene)
                    scenes.push(scene);
            }
        }
        else if (stepId === 'anr_events') {
            for (const row of rows) {
                const scene = sceneFromAnrEvent(row, scenes.length);
                if (scene)
                    scenes.push(scene);
            }
        }
        else if (stepId === 'ime_events') {
            for (const row of rows) {
                const scene = sceneFromImeEvent(row, scenes.length);
                if (scene)
                    scenes.push(scene);
            }
        }
        else if (stepId === 'window_transitions') {
            for (const row of rows) {
                const scene = sceneFromWindowTransition(row, scenes.length);
                if (scene)
                    scenes.push(scene);
            }
        }
        else if (stepId === 'jank_events') {
            jankRowsForFallback.push(...rows);
        }
    }
    if (!hasGestureLikeScene && jankRowsForFallback.length > 0) {
        const intervals = aggregateJankFramesToIntervals(jankRowsForFallback);
        for (const interval of intervals) {
            if (interval.jankCount < 3)
                continue;
            scenes.push({
                id: `jank_events-${scenes.length}`,
                sceneType: 'jank_region',
                sourceStepId: 'jank_events',
                startTs: interval.startTs,
                endTs: interval.endTs,
                durationMs: interval.durationMs,
                processName: 'jank_region',
                label: `${displayNameOf('jank_region')} (${interval.jankCount} 帧掉帧)`,
                metadata: {
                    jankCount: interval.jankCount,
                    severity: interval.severity,
                    fallback: true,
                },
                severity: interval.severity === 'severe' ? 'bad' : 'warning',
                analysisState: 'not_planned',
            });
        }
    }
    enrichDisplayedScenes(scenes, rowsByStep);
    // Sort by startTs so the timeline rendering and Stage 3 prompt see scenes
    // in chronological order regardless of which skill step produced them
    // (the scene_reconstruction skill's step order is structural, not temporal).
    // Pre-compute BigInt keys once to avoid O(N log N) string→BigInt re-parsing.
    const sortKeys = new Map(scenes.map((s) => [s.id, safeBigInt(s.startTs)]));
    scenes.sort((a, b) => {
        const ai = sortKeys.get(a.id) ?? null;
        const bi = sortKeys.get(b.id) ?? null;
        if (ai === null || bi === null)
            return 0;
        if (ai < bi)
            return -1;
        if (ai > bi)
            return 1;
        return 0;
    });
    return { scenes, traceDurationSec };
}
function buildAnalysisIntervals(scenes, options) {
    const manifest = options.manifest ?? domainManifest_1.DEFAULT_DOMAIN_MANIFEST;
    const routes = (0, domainManifest_1.getSceneReconstructionRoutes)(options.routeProfile ?? 'legacy', manifest);
    if (routes.length === 0 || scenes.length === 0)
        return [];
    // Score each scene then pick a matching route in priority order.
    const scored = scenes
        .filter(isAnalysisEligibleScene)
        .map((scene) => ({ scene, priority: computePriority(scene) }))
        .sort((a, b) => b.priority - a.priority);
    const intervals = [];
    for (const { scene, priority } of scored) {
        if (intervals.length >= options.cap)
            break;
        const route = findMatchingRoute(scene.sceneType, routes);
        if (!route)
            continue;
        intervals.push({
            displayedSceneId: scene.id,
            priority,
            routeRuleId: route.id,
            skillId: route.directSkillId,
            params: resolveParams(route, scene),
        });
    }
    return intervals;
}
function isAnalysisEligibleScene(scene) {
    return scene.analysisEligible !== false &&
        scene.sceneRole !== 'marker' &&
        scene.sceneRole !== 'context';
}
function selectAnalysisEligibleScenes(scenes, selection) {
    return filterDisplayedScenesForSelection(scenes, selection)
        .filter(isAnalysisEligibleScene);
}
function filterDisplayedScenesForSelection(scenes, selection) {
    if (!selection || selection.scope === 'all')
        return scenes;
    if (selection.scope === 'scene_types') {
        const selectedTypes = new Set(selection.sceneTypes ?? []);
        if (selectedTypes.size === 0)
            return [];
        return scenes.filter((scene) => selectedTypes.has(scene.sceneType));
    }
    if (selection.scope === 'scene_ids') {
        const selectedIds = new Set(selection.sceneIds ?? []);
        if (selectedIds.size === 0)
            return [];
        return scenes.filter((scene) => selectedIds.has(scene.id));
    }
    return [];
}
/**
 * Compute the same numeric priority the legacy strategy used: 90 when the
 * scene exceeds its threshold (a "problem" scene), 50 otherwise.
 */
function computePriority(scene) {
    const threshold = PROBLEM_THRESHOLDS[scene.sceneType];
    if (!threshold)
        return 50;
    if (threshold.durationMs != null && scene.durationMs > threshold.durationMs) {
        return 90;
    }
    if (threshold.fps != null) {
        const avgFps = Number(scene.metadata?.averageFps);
        if (Number.isFinite(avgFps) && avgFps < threshold.fps)
            return 90;
    }
    return 50;
}
// ---------------------------------------------------------------------------
// Per-step factories
// ---------------------------------------------------------------------------
function sceneFromAppLaunch(row, index) {
    const startTs = String(row.ts ?? '');
    const dur = String(row.dur ?? '');
    const endTs = safeAddNs(startTs, dur);
    if (!startTs || !dur || !endTs)
        return null;
    const durationMs = nsToMs(dur);
    if (!Number.isFinite(durationMs))
        return null;
    const startupType = String(row.startup_type ?? '').toLowerCase();
    const sceneType = startupType === 'warm' ? 'warm_start'
        : startupType === 'hot' ? 'hot_start'
            : 'cold_start';
    const startupIdRaw = Number(row.startup_id ?? row.startupId);
    const startupId = Number.isFinite(startupIdRaw) && startupIdRaw > 0 ? startupIdRaw : index + 1;
    const ttidMs = numericOrUndefined(row.ttid_ms ?? row.ttidMs);
    const ttfdMs = numericOrUndefined(row.ttfd_ms ?? row.ttfdMs);
    return {
        id: `app_launches-${index}`,
        sceneType,
        sourceStepId: 'app_launches',
        startTs,
        endTs,
        durationMs,
        processName: String(row.package ?? '') || 'unknown',
        label: `${displayNameOf(sceneType)} (${durationMs}ms)`,
        metadata: {
            startupId,
            startupType: startupType || undefined,
            startup_id: startupId,
            startup_type: startupType || undefined,
            ttidMs,
            ttfdMs,
            ttid_ms: ttidMs,
            ttfd_ms: ttfdMs,
        },
        severity: severityFor(sceneType, durationMs),
        analysisState: 'not_planned',
    };
}
function sceneFromUserGesture(row, index) {
    const startTs = String(row.ts ?? '');
    const dur = String(row.dur ?? '');
    const endTs = safeAddNs(startTs, dur);
    if (!startTs || !dur || !endTs)
        return null;
    const durationMs = nsToMs(dur);
    if (!Number.isFinite(durationMs))
        return null;
    const gestureType = String(row.gesture_type ?? '').toLowerCase();
    const sceneType = gestureType === 'scroll' ? 'scroll'
        : gestureType === 'long_press' ? 'long_press'
            : 'tap';
    return {
        id: `user_gestures-${index}`,
        sceneType,
        sourceStepId: 'user_gestures',
        startTs,
        endTs,
        durationMs,
        processName: resolveProcessName(row),
        label: `${displayNameOf(sceneType)} (${durationMs}ms)`,
        metadata: {
            confidence: row.confidence,
            moveCount: row.move_count,
        },
        severity: severityFor(sceneType, durationMs, row),
        analysisState: 'not_planned',
    };
}
function sceneFromInertialScroll(row, index) {
    const startTs = String(row.ts ?? '');
    const dur = String(row.dur ?? '');
    const endTs = safeAddNs(startTs, dur);
    if (!startTs || !dur || !endTs)
        return null;
    const durationMs = nsToMs(dur);
    if (!Number.isFinite(durationMs))
        return null;
    return {
        id: `inertial_scrolls-${index}`,
        sceneType: 'inertial_scroll',
        sourceStepId: 'inertial_scrolls',
        startTs,
        endTs,
        durationMs,
        processName: resolveProcessName(row),
        label: `${displayNameOf('inertial_scroll')} (${durationMs}ms)`,
        metadata: {
            frameCount: row.frame_count,
            jankFrames: row.jank_frames,
        },
        severity: severityFor('inertial_scroll', durationMs, row),
        analysisState: 'not_planned',
    };
}
function sceneFromIdlePeriod(row, index) {
    const startTs = String(row.ts ?? '');
    const dur = String(row.dur ?? '');
    const endTs = safeAddNs(startTs, dur);
    if (!startTs || !dur || !endTs)
        return null;
    const durationMs = nsToMs(dur);
    if (!Number.isFinite(durationMs))
        return null;
    return {
        id: `idle_periods-${index}`,
        sceneType: 'idle',
        sourceStepId: 'idle_periods',
        startTs,
        endTs,
        durationMs,
        processName: 'system',
        label: `${displayNameOf('idle')} (${durationMs}ms)`,
        metadata: {
            confidence: row.confidence,
        },
        severity: 'good',
        analysisState: 'not_planned',
    };
}
function sceneFromTopAppChange(row, index) {
    const startTs = String(row.ts ?? '');
    const dur = String(row.dur ?? '');
    const endTs = safeAddNs(startTs, dur);
    if (!startTs || !dur || !endTs)
        return null;
    const durationMs = nsToMs(dur);
    if (!Number.isFinite(durationMs))
        return null;
    const pkg = String(row.app_package ?? '') || 'unknown';
    const isLauncher = isLauncherPackage(pkg);
    const sceneType = isLauncher ? 'home_screen' : 'app_foreground';
    const label = isLauncher
        ? `${displayNameOf('home_screen')} (${formatDuration(durationMs)})`
        : `${displayNameOf('app_foreground')} (${formatDuration(durationMs)})`;
    return {
        id: `top_app_changes-${index}`,
        sceneType,
        sourceStepId: 'top_app_changes',
        startTs,
        endTs,
        durationMs,
        processName: pkg,
        label,
        metadata: {},
        severity: 'good',
        analysisState: 'not_planned',
    };
}
function formatDuration(ms) {
    return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${Math.round(ms)}ms`;
}
function sceneFromScrollInitiation(row, index) {
    const startTs = String(row.ts ?? '');
    if (!startTs)
        return null;
    const dur = String(row.dur ?? '0');
    const endTs = safeAddNs(startTs, dur) ?? startTs;
    const durationMs = nsToMs(dur);
    const safeDurationMs = Number.isFinite(durationMs) ? durationMs : 0;
    return {
        id: `scroll_initiation-${index}`,
        sceneType: 'scroll_start',
        sourceStepId: 'scroll_initiation',
        startTs,
        endTs,
        durationMs: safeDurationMs,
        processName: resolveProcessName(row),
        label: `${displayNameOf('scroll_start')} (${safeDurationMs}ms)`,
        metadata: {
            latencyMs: numericOrUndefined(row.latency_ms ?? row.latencyMs),
        },
        severity: 'good',
        analysisState: 'not_planned',
    };
}
function sceneFromScreenStateChange(row, index) {
    const startTs = String(row.ts ?? '');
    if (!startTs)
        return null;
    const dur = String(row.dur ?? '0');
    const endTs = safeAddNs(startTs, dur) ?? startTs;
    const durationMs = nsToMs(dur);
    const safeDurationMs = Number.isFinite(durationMs) ? durationMs : 0;
    // The skill emits Chinese event labels (`点亮` / `熄灭` / `休眠`) on the
    // `event` column; mirror agentRoutes.ts:mapScreenStateEventToSceneType so
    // we map them to the same scene types.
    const eventText = String(row.event ?? row.state ?? row.screen_state ?? '').trim();
    const sceneType = eventText.includes('点亮') ? 'screen_on'
        : eventText.includes('熄灭') ? 'screen_off'
            : eventText.includes('休眠') ? 'screen_sleep'
                : null;
    if (!sceneType)
        return null;
    return {
        id: `screen_state_changes-${index}`,
        sceneType,
        sourceStepId: 'screen_state_changes',
        startTs,
        endTs,
        durationMs: safeDurationMs,
        processName: 'system',
        label: displayNameOf(sceneType),
        metadata: {
            event: eventText,
        },
        severity: 'good',
        analysisState: 'not_planned',
    };
}
function sceneFromSystemEvent(row, index) {
    const startTs = String(row.ts ?? '');
    if (!startTs)
        return null;
    const dur = String(row.dur ?? '0');
    const endTs = safeAddNs(startTs, dur) ?? startTs;
    const durationMs = nsToMs(dur);
    const safeDurationMs = Number.isFinite(durationMs) ? durationMs : 0;
    const rawType = String(row.event_type ?? '').trim();
    const sceneType = rawType === 'screen_unlock' ? 'screen_unlock'
        : rawType === 'notification' ? 'notification'
            : rawType === 'split_screen' ? 'split_screen'
                : rawType === 'pip' ? 'pip'
                    : null;
    if (!sceneType)
        return null;
    const eventText = String(row.event ?? '').trim();
    return {
        id: `system_events-${index}`,
        sceneType,
        sourceStepId: 'system_events',
        startTs,
        endTs,
        durationMs: safeDurationMs,
        processName: 'system',
        label: `${displayNameOf(sceneType)} (${formatDuration(safeDurationMs)})`,
        metadata: {
            event: eventText,
            eventType: rawType,
        },
        severity: 'good',
        analysisState: 'not_planned',
    };
}
// ---------------------------------------------------------------------------
// New scene factories: navigation keys, ANR, IME, window transitions
// ---------------------------------------------------------------------------
function sceneFromNavigationKey(row, index) {
    const startTs = String(row.ts ?? '');
    const dur = String(row.dur ?? '0');
    const endTs = safeAddNs(startTs, dur) ?? startTs;
    if (!startTs)
        return null;
    const durationMs = nsToMs(dur);
    const safeDurationMs = Number.isFinite(durationMs) ? durationMs : 0;
    const keyName = String(row.key_name ?? '').trim();
    if (keyName !== 'back_key' && keyName !== 'home_key' && keyName !== 'recents_key')
        return null;
    return {
        id: `navigation_keys-${index}`,
        sceneType: keyName,
        sourceStepId: 'navigation_keys',
        startTs,
        endTs,
        durationMs: safeDurationMs,
        processName: 'system',
        label: `${displayNameOf(keyName)} (${formatDuration(safeDurationMs)})`,
        metadata: { keyName },
        severity: 'good',
        analysisState: 'not_planned',
    };
}
function sceneFromAnrEvent(row, index) {
    const startTs = String(row.ts ?? '');
    const dur = String(row.dur ?? '5000000000');
    const endTs = safeAddNs(startTs, dur) ?? startTs;
    if (!startTs)
        return null;
    const durationMs = nsToMs(dur);
    const safeDurationMs = Number.isFinite(durationMs) ? durationMs : 5000;
    return {
        id: `anr_events-${index}`,
        sceneType: 'anr',
        sourceStepId: 'anr_events',
        startTs,
        endTs,
        durationMs: safeDurationMs,
        processName: String(row.process_name ?? '') || 'unknown',
        label: `ANR (${formatDuration(safeDurationMs)})`,
        metadata: {
            processName: row.process_name,
            anrType: row.anr_type,
        },
        severity: 'bad',
        analysisState: 'not_planned',
    };
}
function sceneFromImeEvent(row, index) {
    const startTs = String(row.ts ?? '');
    const dur = String(row.dur ?? '0');
    const endTs = safeAddNs(startTs, dur) ?? startTs;
    if (!startTs)
        return null;
    const durationMs = nsToMs(dur);
    const safeDurationMs = Number.isFinite(durationMs) ? durationMs : 0;
    const action = String(row.ime_action ?? '').trim();
    if (action !== 'ime_show' && action !== 'ime_hide')
        return null;
    const sceneType = action;
    return {
        id: `ime_events-${index}`,
        sceneType,
        sourceStepId: 'ime_events',
        startTs,
        endTs,
        durationMs: safeDurationMs,
        processName: 'system',
        label: `${displayNameOf(sceneType)} (${formatDuration(safeDurationMs)})`,
        metadata: {},
        severity: 'good',
        analysisState: 'not_planned',
    };
}
function sceneFromWindowTransition(row, index) {
    const startTs = String(row.ts ?? '');
    const dur = String(row.dur ?? '0');
    const endTs = safeAddNs(startTs, dur) ?? startTs;
    if (!startTs || !dur)
        return null;
    const durationMs = nsToMs(dur);
    if (!Number.isFinite(durationMs))
        return null;
    return {
        id: `window_transitions-${index}`,
        sceneType: 'window_transition',
        sourceStepId: 'window_transitions',
        startTs,
        endTs,
        durationMs,
        processName: 'system_server',
        label: `${displayNameOf('window_transition')} (${formatDuration(durationMs)})`,
        metadata: {
            transitionType: row.transition_type,
        },
        severity: severityFor('window_transition', durationMs),
        analysisState: 'not_planned',
    };
}
// ---------------------------------------------------------------------------
// Scene contract enrichment and deterministic fusion
// ---------------------------------------------------------------------------
function enrichDisplayedScenes(scenes, rowsByStep) {
    seedSceneContracts(scenes);
    mergeCleanTimelineRows(scenes, getRows(rowsByStep, 'clean_timeline'));
    attachRuntimeContext(scenes, rowsByStep);
    linkScrollMarkersAndFling(scenes);
    finalizeSceneContracts(scenes);
}
function seedSceneContracts(scenes) {
    for (const scene of scenes) {
        const primaryRef = evidenceRefForScene(scene, 'primary');
        scene.sceneRole ?? (scene.sceneRole = defaultSceneRole(scene));
        scene.analysisEligible ?? (scene.analysisEligible = scene.sceneRole === 'action');
        scene.evidenceRefs = mergeEvidenceRefs(scene.evidenceRefs, [primaryRef]);
        scene.confidenceReasons = uniqueStrings([
            ...(scene.confidenceReasons ?? []),
            `primary:${scene.sourceStepId}`,
        ]);
        scene.conflicts ?? (scene.conflicts = []);
    }
}
function mergeCleanTimelineRows(scenes, rows) {
    rows.forEach((row, index) => {
        const sceneType = normalizeCleanTimelineSceneType(row.event_type);
        if (!sceneType)
            return;
        const ref = evidenceRefForRow('clean_timeline', row, index, 'supporting');
        const existing = findMatchingSceneForRow(scenes, sceneType, row);
        if (existing) {
            addSupportingEvidence(existing, ref, row, 'clean_timeline');
            return;
        }
        const scene = sceneFromCleanTimeline(row, index);
        if (!scene)
            return;
        scene.evidenceRefs = mergeEvidenceRefs(scene.evidenceRefs, [ref]);
        scene.confidenceReasons = uniqueStrings([
            ...(scene.confidenceReasons ?? []),
            'derived:clean_timeline',
        ]);
        scenes.push(scene);
    });
}
function sceneFromCleanTimeline(row, index) {
    const sceneType = normalizeCleanTimelineSceneType(row.event_type);
    if (!sceneType)
        return null;
    const startTs = String(row.ts ?? '');
    if (!startTs)
        return null;
    const dur = String(row.dur ?? '0');
    const endTs = safeAddNs(startTs, dur) ?? startTs;
    const durationMs = nsToMs(dur);
    const safeDurationMs = Number.isFinite(durationMs) ? durationMs : 0;
    const eventId = String(row.event_id ?? '').trim();
    const processName = String(row.app_package ?? '').trim() || defaultProcessNameForSceneType(sceneType);
    const sceneRole = defaultSceneRoleForType(sceneType);
    return {
        id: eventId ? `clean_timeline-${eventId}` : `clean_timeline-${sceneType}-${startTs}-${index}`,
        sceneType,
        sourceStepId: 'clean_timeline',
        startTs,
        endTs,
        durationMs: safeDurationMs,
        processName,
        label: `${displayNameOf(sceneType)} (${formatDuration(safeDurationMs)})`,
        metadata: {
            eventId: eventId || undefined,
            event: row.event,
            eventType: row.event_type,
            timeOffset: row.time_offset,
            rating: row.rating,
        },
        severity: severityFromRating(row.rating) ?? severityFor(sceneType, safeDurationMs, row),
        sceneRole,
        analysisEligible: sceneRole === 'action',
        analysisState: 'not_planned',
    };
}
function normalizeCleanTimelineSceneType(value) {
    const sceneType = String(value ?? '').trim();
    if (!sceneType)
        return null;
    if (sceneType === 'system')
        return null;
    return CLEAN_TIMELINE_SCENE_TYPES.has(sceneType) ? sceneType : null;
}
function findMatchingSceneForRow(scenes, sceneType, row) {
    const rowStart = safeBigInt(row.ts);
    if (rowStart === null)
        return null;
    const rowEnd = rowStart + (safeBigInt(row.dur) ?? 0n);
    return scenes.find((scene) => {
        if (!areSceneTypesEquivalent(scene.sceneType, sceneType))
            return false;
        const sceneStart = safeBigInt(scene.startTs);
        const sceneEnd = safeBigInt(scene.endTs);
        if (sceneStart === null || sceneEnd === null)
            return false;
        return rangesOverlapOrClose(sceneStart, sceneEnd, rowStart, rowEnd, SCENE_DEDUPE_TOLERANCE_NS);
    }) ?? null;
}
function areSceneTypesEquivalent(left, right) {
    if (left === right)
        return true;
    const appContext = new Set(['app_foreground', 'home_screen']);
    if (appContext.has(left) && appContext.has(right))
        return true;
    return false;
}
function addSupportingEvidence(scene, ref, row, reason) {
    scene.evidenceRefs = mergeEvidenceRefs(scene.evidenceRefs, [ref]);
    scene.confidenceReasons = uniqueStrings([...(scene.confidenceReasons ?? []), `support:${reason}`]);
    scene.context = {
        ...(scene.context ?? {}),
        cleanTimeline: appendBoundedContext(scene.context?.cleanTimeline, compactContextRow(row)),
    };
    const supportingApp = String(row.app_package ?? '').trim();
    if (supportingApp && isMeaningfulProcess(scene.processName) && scene.processName !== supportingApp) {
        scene.conflicts = [
            ...(scene.conflicts ?? []),
            {
                type: 'app_mismatch',
                severity: 'warning',
                message: `scene app ${scene.processName} differs from ${reason} app ${supportingApp}`,
                evidenceRefs: [evidenceRefForScene(scene, 'primary'), ref],
            },
        ];
    }
}
function attachRuntimeContext(scenes, rowsByStep) {
    const operationRows = getRows(rowsByStep, 'operation_chain');
    const activityRows = getRows(rowsByStep, 'activity_lifecycle');
    const appStateRows = [
        ...getRows(rowsByStep, 'app_state_tracking'),
        ...getRows(rowsByStep, 'app_states'),
    ];
    const deviceRows = getRows(rowsByStep, 'device_state');
    for (const scene of scenes) {
        const operationChain = contextRowsNearScene(operationRows, scene, CONTEXT_WINDOW_NS);
        const activityLifecycle = contextRowsNearScene(activityRows, scene, CONTEXT_WINDOW_NS);
        const appState = contextRowsNearScene(appStateRows, scene, CONTEXT_WINDOW_NS, (row) => sameMeaningfulProcess(scene.processName, row.app_package));
        const deviceState = contextRowsNearScene(deviceRows, scene, CONTEXT_WINDOW_NS);
        const context = {
            ...(scene.context ?? {}),
            ...(operationChain.length > 0 ? { operationChain } : {}),
            ...(activityLifecycle.length > 0 ? { activityLifecycle } : {}),
            ...(appState.length > 0 ? { appState } : {}),
            ...(deviceState.length > 0 ? { deviceState } : {}),
        };
        if (Object.keys(context).length > 0) {
            scene.context = context;
        }
        if (operationChain.length > 0) {
            scene.evidenceRefs = mergeEvidenceRefs(scene.evidenceRefs, [
                evidenceRefForContext('operation_chain', operationChain[0]),
            ]);
            scene.confidenceReasons = uniqueStrings([...(scene.confidenceReasons ?? []), 'context:operation_chain']);
        }
    }
}
function contextRowsNearScene(rows, scene, windowNs, predicate) {
    const sceneStart = safeBigInt(scene.startTs);
    const sceneEnd = safeBigInt(scene.endTs);
    if (sceneStart === null || sceneEnd === null)
        return [];
    const start = sceneStart - windowNs;
    const end = sceneEnd + windowNs;
    const selected = [];
    for (const row of rows) {
        if (predicate && !predicate(row))
            continue;
        const rowTs = safeBigInt(row.ts);
        if (rowTs === null || rowTs < start || rowTs > end)
            continue;
        selected.push(compactContextRow(row));
        if (selected.length >= MAX_CONTEXT_ROWS_PER_GROUP)
            break;
    }
    return selected;
}
function linkScrollMarkersAndFling(scenes) {
    const scrolls = scenes.filter(scene => scene.sceneType === 'scroll');
    const flings = scenes.filter(scene => scene.sceneType === 'inertial_scroll');
    const markers = scenes.filter(scene => scene.sceneType === 'scroll_start');
    for (const marker of markers) {
        marker.sceneRole = 'marker';
        marker.analysisEligible = false;
        const parent = findContainingOrNearestScene(marker, scrolls, SCROLL_CHAIN_TOLERANCE_NS);
        if (!parent)
            continue;
        linkParentChild(parent, marker);
        marker.confidenceReasons = uniqueStrings([...(marker.confidenceReasons ?? []), 'linked:scroll_start']);
    }
    for (const fling of flings) {
        const parent = findContainingOrNearestScene(fling, scrolls, SCROLL_CHAIN_TOLERANCE_NS, true);
        if (!parent)
            continue;
        linkParentChild(parent, fling);
        parent.confidenceReasons = uniqueStrings([...(parent.confidenceReasons ?? []), 'linked:inertial_scroll']);
        fling.confidenceReasons = uniqueStrings([...(fling.confidenceReasons ?? []), 'linked:active_scroll']);
    }
}
function findContainingOrNearestScene(target, candidates, toleranceNs, requireSameProcess = false) {
    const targetStart = safeBigInt(target.startTs);
    const targetEnd = safeBigInt(target.endTs);
    if (targetStart === null || targetEnd === null)
        return null;
    let best = null;
    for (const scene of candidates) {
        if (scene.id === target.id)
            continue;
        if (requireSameProcess && !sameMeaningfulProcess(scene.processName, target.processName))
            continue;
        const sceneStart = safeBigInt(scene.startTs);
        const sceneEnd = safeBigInt(scene.endTs);
        if (sceneStart === null || sceneEnd === null)
            continue;
        if (!rangesOverlapOrClose(sceneStart, sceneEnd, targetStart, targetEnd, toleranceNs))
            continue;
        const distance = targetStart >= sceneStart && targetStart <= sceneEnd
            ? 0n
            : minBigInt(absBigInt(targetStart - sceneEnd), absBigInt(sceneStart - targetEnd));
        if (!best || distance < best.distance) {
            best = { scene, distance };
        }
    }
    return best?.scene ?? null;
}
function linkParentChild(parent, child) {
    child.parentSceneId = parent.id;
    parent.childSceneIds = uniqueStrings([...(parent.childSceneIds ?? []), child.id]);
}
function finalizeSceneContracts(scenes) {
    for (const scene of scenes) {
        scene.sceneRole ?? (scene.sceneRole = defaultSceneRole(scene));
        scene.analysisEligible ?? (scene.analysisEligible = scene.sceneRole === 'action');
        scene.evidenceRefs = mergeEvidenceRefs(scene.evidenceRefs, [evidenceRefForScene(scene, 'primary')]);
        scene.conflicts = dedupeConflicts(scene.conflicts ?? []);
        const score = computeConfidenceScore(scene);
        scene.confidenceScore = score;
        scene.confidenceLevel = confidenceLevelForScore(score);
        scene.confidenceReasons = uniqueStrings(scene.confidenceReasons ?? []);
    }
}
function computeConfidenceScore(scene) {
    const sourceBase = baseConfidenceForSource(scene.sourceStepId);
    const evidenceCount = scene.evidenceRefs?.length ?? 0;
    const supportBonus = Math.min(0.12, Math.max(0, evidenceCount - 1) * 0.04);
    const metadataScore = confidenceScoreFromMetadata(scene.metadata?.confidence);
    const conflictPenalty = Math.min(0.25, (scene.conflicts ?? []).length * 0.08);
    const rolePenalty = scene.sceneRole === 'marker' ? 0.08 : scene.sceneRole === 'context' ? 0.05 : 0;
    const raw = Math.max(sourceBase, metadataScore ?? 0) + supportBonus - conflictPenalty - rolePenalty;
    return Math.max(0.35, Math.min(0.95, Number(raw.toFixed(2))));
}
function baseConfidenceForSource(sourceStepId) {
    if (sourceStepId === 'app_launches')
        return 0.9;
    if (sourceStepId === 'user_gestures')
        return 0.78;
    if (sourceStepId === 'inertial_scrolls')
        return 0.76;
    if (sourceStepId === 'scroll_initiation')
        return 0.64;
    if (sourceStepId === 'system_events')
        return 0.72;
    if (sourceStepId === 'screen_state_changes')
        return 0.78;
    if (sourceStepId === 'top_app_changes')
        return 0.72;
    if (sourceStepId === 'idle_periods')
        return 0.62;
    if (sourceStepId === 'clean_timeline')
        return 0.68;
    if (sourceStepId === 'jank_events')
        return 0.58;
    return 0.7;
}
function confidenceScoreFromMetadata(value) {
    if (typeof value === 'number' && Number.isFinite(value)) {
        if (value > 1)
            return Math.min(0.95, value / 100);
        return Math.max(0, Math.min(0.95, value));
    }
    const text = String(value ?? '').trim().toLowerCase();
    if (!text)
        return null;
    if (text === '高' || text === 'high')
        return 0.86;
    if (text === '中' || text === 'medium')
        return 0.74;
    if (text === '低' || text === 'low')
        return 0.58;
    return null;
}
function confidenceLevelForScore(score) {
    if (score >= 0.82)
        return 'high';
    if (score >= 0.65)
        return 'medium';
    return 'low';
}
function defaultSceneRole(scene) {
    return defaultSceneRoleForType(scene.sceneType);
}
function defaultSceneRoleForType(sceneType) {
    if (sceneType === 'scroll_start')
        return 'marker';
    if (sceneType === 'app_foreground' || sceneType === 'home_screen' || sceneType === 'idle') {
        return 'context';
    }
    return 'action';
}
function defaultProcessNameForSceneType(sceneType) {
    if (sceneType === 'screen_unlock'
        || sceneType === 'notification'
        || sceneType === 'split_screen'
        || sceneType === 'pip'
        || sceneType === 'screen_on'
        || sceneType === 'screen_off'
        || sceneType === 'screen_sleep'
        || sceneType === 'ime_show'
        || sceneType === 'ime_hide'
        || sceneType === 'window_transition') {
        return 'system';
    }
    return 'unknown';
}
function severityFromRating(value) {
    const text = String(value ?? '');
    if (!text)
        return null;
    if (text.includes('🔴'))
        return 'bad';
    if (text.includes('🟡'))
        return 'warning';
    if (text.includes('🟢'))
        return 'good';
    return null;
}
function evidenceRefForScene(scene, role) {
    return {
        sourceStepId: scene.sourceStepId,
        role,
        rowSelector: {
            ts: scene.startTs,
            sceneType: scene.sceneType,
        },
    };
}
function evidenceRefForRow(sourceStepId, row, rowIndex, role) {
    const ref = {
        sourceStepId,
        rowIndex,
        role,
    };
    const eventId = String(row.event_id ?? '').trim();
    if (eventId)
        ref.eventId = eventId;
    const selector = {};
    if (row.ts !== undefined && row.ts !== null)
        selector.ts = String(row.ts);
    const eventType = row.event_type ?? row.gesture_type ?? row.key_name ?? row.ime_action;
    if (eventType !== undefined && eventType !== null)
        selector.eventType = String(eventType);
    if (Object.keys(selector).length > 0)
        ref.rowSelector = selector;
    return ref;
}
function evidenceRefForContext(sourceStepId, row) {
    const selector = {};
    if (row.ts !== undefined && row.ts !== null)
        selector.ts = String(row.ts);
    if (row.event !== undefined && row.event !== null)
        selector.event = String(row.event);
    if (row.category !== undefined && row.category !== null)
        selector.category = String(row.category);
    return {
        sourceStepId,
        role: 'context',
        ...(Object.keys(selector).length > 0 ? { rowSelector: selector } : {}),
    };
}
function mergeEvidenceRefs(existing, next) {
    const merged = [...(existing ?? [])];
    for (const ref of next) {
        const key = evidenceRefKey(ref);
        if (merged.some(item => evidenceRefKey(item) === key))
            continue;
        merged.push(ref);
    }
    return merged;
}
function evidenceRefKey(ref) {
    const selector = ref.rowSelector ? JSON.stringify(ref.rowSelector) : '';
    return `${ref.sourceStepId}:${ref.eventId ?? ''}:${ref.rowIndex ?? ''}:${ref.role ?? ''}:${selector}`;
}
function dedupeConflicts(conflicts) {
    const seen = new Set();
    const result = [];
    for (const conflict of conflicts) {
        const key = `${conflict.type}:${conflict.severity}:${conflict.message}`;
        if (seen.has(key))
            continue;
        seen.add(key);
        result.push(conflict);
    }
    return result;
}
function getRows(rowsByStep, stepId) {
    return rowsByStep.get(stepId) ?? [];
}
function compactContextRow(row) {
    const compact = {};
    for (const key of [
        'event_id',
        'time_offset',
        'ts',
        'dur',
        'dur_ms',
        'event_type',
        'event',
        'category',
        'priority',
        'app_package',
        'activity_name',
        'lifecycle_event',
        'oom_adj',
        'state_label',
        'value',
    ]) {
        if (row[key] !== undefined && row[key] !== null)
            compact[key] = row[key];
    }
    return compact;
}
function appendBoundedContext(existing, next) {
    const rows = [...(existing ?? [])];
    const key = JSON.stringify(next);
    if (!rows.some(row => JSON.stringify(row) === key))
        rows.push(next);
    return rows.slice(0, MAX_CONTEXT_ROWS_PER_GROUP);
}
function rangesOverlapOrClose(aStart, aEnd, bStart, bEnd, toleranceNs) {
    if (aEnd + toleranceNs < bStart)
        return false;
    if (bEnd + toleranceNs < aStart)
        return false;
    return true;
}
function sameMeaningfulProcess(left, right) {
    const l = String(left ?? '').trim();
    const r = String(right ?? '').trim();
    if (!isMeaningfulProcess(l) || !isMeaningfulProcess(r))
        return false;
    return l === r;
}
function isMeaningfulProcess(value) {
    const text = String(value ?? '').trim();
    return !!text && text !== 'unknown' && text !== 'system';
}
function uniqueStrings(values) {
    return Array.from(new Set(values.filter(Boolean)));
}
function absBigInt(value) {
    return value < 0n ? -value : value;
}
function minBigInt(left, right) {
    return left < right ? left : right;
}
function aggregateJankFramesToIntervals(rows) {
    if (rows.length === 0)
        return [];
    const MERGE_GAP_NS = 500000000n; // 500ms
    const intervals = [];
    const sortedRows = [...rows].sort((a, b) => {
        const aTs = safeBigInt(a.ts);
        const bTs = safeBigInt(b.ts);
        if (aTs === null || bTs === null)
            return 0;
        return aTs < bTs ? -1 : aTs > bTs ? 1 : 0;
    });
    let currentStart = safeBigInt(sortedRows[0].ts);
    let currentEnd = currentStart !== null
        ? currentStart + (safeBigInt(sortedRows[0].dur) ?? 0n)
        : null;
    if (currentStart === null || currentEnd === null)
        return [];
    let jankCount = 1;
    let severities = [String(sortedRows[0].jank_severity_type ?? '')];
    for (let i = 1; i < sortedRows.length; i++) {
        const rowTs = safeBigInt(sortedRows[i].ts);
        const rowDur = safeBigInt(sortedRows[i].dur) ?? 0n;
        if (rowTs === null)
            continue;
        if (rowTs - currentEnd < MERGE_GAP_NS) {
            const rowEnd = rowTs + rowDur;
            if (rowEnd > currentEnd)
                currentEnd = rowEnd;
            jankCount++;
            severities.push(String(sortedRows[i].jank_severity_type ?? ''));
        }
        else {
            intervals.push({
                startTs: currentStart.toString(),
                endTs: currentEnd.toString(),
                durationMs: Number((currentEnd - currentStart) / 1000000n),
                jankCount,
                severity: severities.includes('Full') ? 'severe' : 'mild',
            });
            currentStart = rowTs;
            currentEnd = rowTs + rowDur;
            jankCount = 1;
            severities = [String(sortedRows[i].jank_severity_type ?? '')];
        }
    }
    intervals.push({
        startTs: currentStart.toString(),
        endTs: currentEnd.toString(),
        durationMs: Number((currentEnd - currentStart) / 1000000n),
        jankCount,
        severity: severities.includes('Full') ? 'severe' : 'mild',
    });
    return intervals;
}
// ---------------------------------------------------------------------------
// Route resolution
// ---------------------------------------------------------------------------
function findMatchingRoute(sceneType, routes) {
    for (const route of routes) {
        if ((0, domainManifest_1.matchesSceneReconstructionRoute)(sceneType, route))
            return route;
    }
    return null;
}
function resolveParams(route, scene) {
    const params = { ...(route.skillParams ?? {}) };
    for (const [paramKey, fieldPath] of Object.entries(route.paramMapping ?? {})) {
        const value = readSceneField(scene, fieldPath);
        if (value !== undefined && value !== null) {
            params[paramKey] = value;
        }
    }
    return params;
}
function readSceneField(scene, fieldPath) {
    // Top-level scene field aliases used historically by the manifest's
    // paramMapping (e.g. 'startTs', 'endTs', 'durationMs', 'processName').
    const sceneAny = scene;
    if (fieldPath in sceneAny)
        return sceneAny[fieldPath];
    // Dot-path into metadata, e.g. 'metadata.startupId' or just 'startupId'.
    if (fieldPath.includes('.')) {
        return getNestedField(scene, fieldPath);
    }
    if (scene.metadata && fieldPath in scene.metadata) {
        return scene.metadata[fieldPath];
    }
    return undefined;
}
function getNestedField(obj, path) {
    const parts = path.split('.');
    let current = obj;
    for (const part of parts) {
        if (current == null)
            return undefined;
        current = current[part];
    }
    return current;
}
// ---------------------------------------------------------------------------
// Severity helpers
// ---------------------------------------------------------------------------
function severityFor(sceneType, durationMs, row) {
    const threshold = PROBLEM_THRESHOLDS[sceneType];
    if (!threshold)
        return 'unknown';
    if (threshold.durationMs != null && durationMs > threshold.durationMs)
        return 'bad';
    if (threshold.fps != null && row) {
        const avgFps = Number(row.averageFps ?? row.average_fps);
        if (Number.isFinite(avgFps) && avgFps < threshold.fps)
            return 'bad';
    }
    return 'good';
}
function displayNameOf(sceneType) {
    return SCENE_DISPLAY_NAMES[sceneType] ?? sceneType;
}
// ---------------------------------------------------------------------------
// Numeric / BigInt helpers
// ---------------------------------------------------------------------------
function nsToMs(ns) {
    try {
        return Number(BigInt(ns) / 1000000n);
    }
    catch {
        return NaN;
    }
}
function safeAddNs(startTs, durNs) {
    try {
        return (BigInt(startTs) + BigInt(durNs)).toString();
    }
    catch {
        return null;
    }
}
function safeBigInt(value) {
    if (typeof value === 'bigint')
        return value;
    if (typeof value === 'number' && Number.isFinite(value)) {
        try {
            return BigInt(Math.trunc(value));
        }
        catch {
            return null;
        }
    }
    if (typeof value === 'string') {
        const s = value.trim();
        if (!s || !/^-?\d+$/.test(s))
            return null;
        try {
            return BigInt(s);
        }
        catch {
            return null;
        }
    }
    return null;
}
function numericOrUndefined(value) {
    const n = Number(value);
    return Number.isFinite(n) ? n : undefined;
}
function resolveProcessName(row) {
    const appPackage = String(row.app_package ?? '').trim();
    if (appPackage)
        return appPackage;
    const eventText = String(row.event ?? '');
    const m = eventText.match(/\[([^\]]+)\]\s*$/);
    if (m)
        return m[1];
    return 'unknown';
}
//# sourceMappingURL=sceneIntervalBuilder.js.map