"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.RenderingPipelineTeachingService = void 0;
const pipelineDocService_1 = require("./pipelineDocService");
const pipelineSkillLoader_1 = require("./pipelineSkillLoader");
const skillExecutor_1 = require("./skillEngine/skillExecutor");
const skillLoader_1 = require("./skillEngine/skillLoader");
const traceProcessorService_1 = require("./traceProcessorService");
const traceProcessorRowUtils_1 = require("../utils/traceProcessorRowUtils");
const teaching_types_1 = require("../types/teaching.types");
const teaching_config_1 = require("../config/teaching.config");
const DEFAULT_SUBVARIANTS = {
    buffer_mode: 'UNKNOWN',
    flutter_engine: 'N/A',
    webview_mode: 'N/A',
    game_engine: 'N/A',
};
const OBSERVED_SOURCE_PRIORITY = [
    'selection',
    'visible_window',
    'package_or_process_hint',
    'active_rendering_process_fallback',
];
const DEFAULT_KEY_SLICE_NAMES = [
    'Choreographer#doFrame',
    'performTraversals',
    'DrawFrame',
    'syncFrameState',
    'dequeueBuffer',
    'queueBuffer',
    'BLASTBufferQueue',
    'setTransactionState',
    'applyTransaction',
    'latchBuffer',
    'eglSwapBuffers',
    'vkQueuePresent',
    'DrawGL',
    'DrawFunctor',
    'onMessageReceived',
    'FrameTimeline',
    'present',
    'HWC',
];
const POINT_SELECTION_CONTEXT_NS = 50000000;
const MAX_CRITICAL_TASK_ROOTS = 6;
const MAX_CRITICAL_PATH_ROWS_PER_ROOT = 36;
const MAX_CRITICAL_TASKS = 40;
const PRODUCER_PIPELINE_PATTERNS = [
    { test: /WEBVIEW|CHROME/i, title: 'WebView/Chromium producer', role: 'producer' },
    { test: /FLUTTER|IMPELLER/i, title: 'Flutter producer', role: 'producer' },
    { test: /RN_|REACT_NATIVE|SKIA_RENDERER/i, title: 'React Native producer', role: 'producer' },
    { test: /TEXTUREVIEW/i, title: 'TextureView producer', role: 'producer' },
    { test: /SURFACEVIEW/i, title: 'SurfaceView producer', role: 'producer' },
    { test: /OPENGL|VULKAN|ANGLE|GAME/i, title: 'GL/Vulkan producer', role: 'producer' },
    { test: /CAMERA|VIDEO|IMAGEREADER|HARDWARE_BUFFER/i, title: 'Media producer', role: 'producer' },
];
function teachingWarning(code, severity, message, source) {
    return { code, severity, message, source };
}
function sqlLiteral(value) {
    return `'${value.replace(/'/g, "''")}'`;
}
function sqlLikeLiteral(value) {
    return sqlLiteral(`%${value.replace(/[\\%_]/g, (match) => `\\${match}`)}%`);
}
function safeNumber(value) {
    if (value === undefined || value === null || value === '')
        return undefined;
    const parsed = (0, traceProcessorRowUtils_1.toNumber)(value, Number.NaN);
    return Number.isFinite(parsed) ? parsed : undefined;
}
function normalizeSubvariants(rawResults) {
    const step = rawResults?.subvariants;
    const row = Array.isArray(step?.data) && step.data.length > 0
        ? step.data[0]
        : null;
    if (!row)
        return DEFAULT_SUBVARIANTS;
    return {
        buffer_mode: String(row.buffer_mode ?? DEFAULT_SUBVARIANTS.buffer_mode),
        flutter_engine: String(row.flutter_engine ?? DEFAULT_SUBVARIANTS.flutter_engine),
        webview_mode: String(row.webview_mode ?? DEFAULT_SUBVARIANTS.webview_mode),
        game_engine: String(row.game_engine ?? DEFAULT_SUBVARIANTS.game_engine),
    };
}
function normalizeActiveProcesses(raw) {
    if (!Array.isArray(raw))
        return [];
    return raw
        .map((row) => {
        const item = row;
        const processName = String(item.processName ?? item.process_name ?? item.name ?? '').trim();
        if (!processName)
            return null;
        return {
            upid: (0, traceProcessorRowUtils_1.toNumber)(item.upid, 0),
            processName,
            frameCount: (0, traceProcessorRowUtils_1.toNumber)(item.frameCount ?? item.frame_count ?? item.count, 0),
            renderThreadTid: (0, traceProcessorRowUtils_1.toNumber)(item.renderThreadTid ?? item.render_thread_tid ?? item.tid, 0),
        };
    })
        .filter((value) => value !== null);
}
function normalizePinInstructions(raw) {
    if (!Array.isArray(raw))
        return [];
    const instructions = [];
    for (const row of raw) {
        const item = row;
        const pattern = (0, traceProcessorRowUtils_1.toOptionalString)(item.pattern);
        const matchBy = (0, traceProcessorRowUtils_1.toOptionalString)(item.matchBy ?? item.match_by);
        const reason = (0, traceProcessorRowUtils_1.toOptionalString)(item.reason);
        if (!pattern || !matchBy || !reason)
            continue;
        const instruction = {
            pattern,
            matchBy: matchBy,
            priority: (0, traceProcessorRowUtils_1.toNumber)(item.priority, 0),
            reason,
        };
        if (item.expand !== undefined) {
            instruction.expand = Boolean(item.expand);
        }
        if (item.mainThreadOnly !== undefined || item.main_thread_only !== undefined) {
            instruction.mainThreadOnly = Boolean(item.mainThreadOnly ?? item.main_thread_only);
        }
        if (item.smartPin !== undefined) {
            instruction.smartPin = Boolean(item.smartPin);
        }
        if (item.skipPin !== undefined) {
            instruction.skipPin = Boolean(item.skipPin);
        }
        if (Array.isArray(item.activeProcessNames)) {
            instruction.activeProcessNames = item.activeProcessNames.map((name) => String(name));
        }
        instructions.push(instruction);
    }
    return instructions;
}
function buildDetectionResponse(detection, subvariants) {
    return {
        detected: detection.detected,
        primaryPipelineId: detection.primaryPipelineId,
        primaryConfidence: detection.primaryConfidence,
        primary_pipeline: {
            id: detection.primaryPipelineId,
            confidence: detection.primaryConfidence,
        },
        candidates: detection.candidates,
        features: detection.features,
        subvariants,
        traceRequirementsMissing: detection.traceRequirementsMissing,
        trace_requirements_missing: detection.traceRequirementsMissing,
    };
}
function fallbackTeachingContent(primaryPipelineId, docPath) {
    return {
        title: `渲染管线: ${primaryPipelineId}`,
        summary: '未找到对应的文档内容。',
        mermaidBlocks: [],
        threadRoles: [],
        keySlices: [],
        docPath,
    };
}
function normalizeKeySliceName(name) {
    return name
        .replace(/\s*\((hint|optional|可选).*?\)\s*/gi, '')
        .trim();
}
function collectKeySliceNames(teachingContent) {
    const names = new Set();
    for (const name of DEFAULT_KEY_SLICE_NAMES) {
        names.add(normalizeKeySliceName(name));
    }
    for (const name of teachingContent?.keySlices || []) {
        const normalized = normalizeKeySliceName(String(name));
        if (normalized)
            names.add(normalized);
    }
    return Array.from(names).filter(Boolean);
}
function extractTimeRange(input) {
    const selection = input.selectionContext || {};
    const visibleWindow = input.visibleWindow || {};
    const selectionStart = safeNumber(selection.startTs ?? selection.start ?? selection.ts);
    const selectionDur = safeNumber(selection.dur ?? selection.duration);
    const selectionEnd = safeNumber(selection.endTs ?? selection.end);
    if (selectionStart !== undefined && (selectionEnd !== undefined || selectionDur !== undefined)) {
        const rawEnd = selectionEnd ?? selectionStart + Math.max(selectionDur || 0, 0);
        return {
            startTs: selectionStart,
            endTs: rawEnd > selectionStart ? rawEnd : selectionStart + POINT_SELECTION_CONTEXT_NS,
            source: 'selection',
        };
    }
    const visibleStart = safeNumber(visibleWindow.startTs ?? visibleWindow.start ?? visibleWindow.visibleStartTs ?? visibleWindow.visibleStart);
    const visibleEnd = safeNumber(visibleWindow.endTs ?? visibleWindow.end ?? visibleWindow.visibleEndTs ?? visibleWindow.visibleEnd);
    if (visibleStart !== undefined && visibleEnd !== undefined && visibleEnd > visibleStart) {
        return { startTs: visibleStart, endTs: visibleEnd, source: 'visible_window' };
    }
    const requestStart = safeNumber(input.startTs);
    const requestEnd = safeNumber(input.endTs);
    if (requestStart !== undefined && requestEnd !== undefined && requestEnd > requestStart) {
        return { startTs: requestStart, endTs: requestEnd, source: 'request' };
    }
    return undefined;
}
function stageFromRow(row) {
    const stage = (0, traceProcessorRowUtils_1.toOptionalString)(row.stage);
    if (stage)
        return stage;
    const name = String(row.name ?? '').toLowerCase();
    const threadName = String(row.thread_name ?? '').toLowerCase();
    const processName = String(row.process_name ?? '').toLowerCase();
    if (name.includes('choreographer') || name.includes('traversal'))
        return 'app_frame';
    if (threadName === 'renderthread' || name.includes('drawframe') || name.includes('syncframestate')) {
        return 'render_thread';
    }
    if (name.includes('queuebuffer') || name.includes('blast') || name.includes('transaction')) {
        return 'buffer_queue_transaction';
    }
    if (processName.includes('surfaceflinger') || name.includes('latchbuffer')) {
        return 'surfaceflinger_composition';
    }
    if (threadName.includes('hwc') || name.includes('present'))
        return 'present';
    return 'producer';
}
function roleForStage(stage, processName, threadName) {
    const processLower = (processName || '').toLowerCase();
    const threadLower = (threadName || '').toLowerCase();
    if (stage === 'app_frame')
        return 'app';
    if (stage === 'render_thread')
        return 'render_thread';
    if (stage === 'buffer_queue_transaction')
        return 'buffer_queue';
    if (stage === 'surfaceflinger_composition')
        return 'surfaceflinger';
    if (stage === 'present')
        return 'hwc_present';
    if (processLower.includes('surfaceflinger'))
        return 'surfaceflinger';
    if (threadLower.includes('hwc'))
        return 'hwc_present';
    return 'producer';
}
function laneIdFor(role, processName, threadName, suffix) {
    const stable = [role, processName, threadName, suffix]
        .filter(Boolean)
        .join(':')
        .replace(/[^a-zA-Z0-9_-]+/g, '_')
        .replace(/^_+|_+$/g, '')
        .toLowerCase();
    return stable || role;
}
function laneTitle(role, processName, threadName) {
    if (role === 'app')
        return `${processName || 'App'} main`;
    if (role === 'render_thread')
        return `${processName || 'App'} RenderThread`;
    if (role === 'buffer_queue')
        return 'BufferQueue / Transaction';
    if (role === 'surfaceflinger')
        return 'SurfaceFlinger';
    if (role === 'hwc_present')
        return 'HWC / present';
    if (role === 'critical_task')
        return `Critical task: ${threadName || processName || 'scheduler edge'}`;
    return threadName || processName || 'Producer';
}
function makeTrackHint(role, processName, threadName) {
    if (threadName) {
        return {
            matchBy: 'thread',
            pattern: threadName,
            processName,
            threadName,
            mainThreadOnly: role === 'app',
        };
    }
    if (processName) {
        return {
            matchBy: 'process',
            pattern: processName,
            processName,
        };
    }
    if (role === 'surfaceflinger') {
        return { matchBy: 'process', pattern: 'surfaceflinger', processName: 'surfaceflinger' };
    }
    return undefined;
}
function sortLaneRole(role) {
    return {
        app: 0,
        render_thread: 1,
        producer: 2,
        buffer_queue: 3,
        surfaceflinger: 4,
        hwc_present: 5,
        critical_task: 6,
        unknown: 7,
    }[role];
}
function stripPrefix(value, prefix) {
    return value.startsWith(prefix) ? value.slice(prefix.length).trim() : undefined;
}
function classifyWakerKind(threadName, tid, irqContext) {
    if (irqContext)
        return 'irq';
    if (tid === 0)
        return 'swapper';
    if (threadName && /^swapper(\/\d+)?$/.test(threadName))
        return 'swapper';
    if (threadName)
        return 'thread';
    return 'unknown';
}
function sameLaneOwner(lane, processName, threadName) {
    return lane.processName === processName && lane.threadName === threadName;
}
function taskOwnerLabel(task) {
    return [task.processName, task.threadName].filter(Boolean).join(' / ') || task.name || 'critical task';
}
class RenderingPipelineTeachingService {
    constructor(traceProcessorService = (0, traceProcessorService_1.getTraceProcessorService)()) {
        this.traceProcessorService = traceProcessorService;
    }
    async analyze(input) {
        const detectionBundle = await this.loadDetectionBundle(input);
        const detection = buildDetectionResponse(detectionBundle.bundle.detection, detectionBundle.subvariants);
        const teachingContent = detectionBundle.bundle.teachingContent ||
            fallbackTeachingContent(detection.primaryPipelineId, detectionBundle.bundle.docPath);
        const observed = await this.buildObservedFlow(input, detectionBundle.bundle, detection);
        const pinPlan = this.buildPinPlan(detectionBundle.bundle.pinInstructions, observed.observedFlow);
        const overlayPlan = this.buildOverlayPlan(teachingContent, observed.observedFlow);
        const warnings = this.buildWarnings(detectionBundle, observed.observedFlow, observed.queryWarnings, teachingContent, pinPlan, overlayPlan);
        return {
            success: true,
            schemaVersion: 'teaching.pipeline.v2',
            detection,
            observedFlow: observed.observedFlow,
            teaching: teachingContent,
            teachingContent,
            pinPlan,
            overlayPlan,
            warnings,
            pinInstructions: detectionBundle.bundle.pinInstructions,
            activeRenderingProcesses: detectionBundle.bundle.activeRenderingProcesses,
        };
    }
    async loadDetectionBundle(input) {
        await (0, skillLoader_1.ensureSkillRegistryInitialized)();
        const skillExecutor = new skillExecutor_1.SkillExecutor(this.traceProcessorService);
        skillExecutor.registerSkills(skillLoader_1.skillRegistry.getAllSkills());
        const result = await skillExecutor.execute('rendering_pipeline_detection', input.traceId, {
            package: input.packageName || input.processName || '',
        });
        if (!result.success) {
            throw new Error(result.error || 'Pipeline detection failed');
        }
        const subvariants = normalizeSubvariants(result.rawResults);
        const pipelineBundle = this.extractPipelineBundle(result);
        if (pipelineBundle) {
            return {
                bundle: pipelineBundle,
                subvariants,
                warnings: [],
            };
        }
        return {
            bundle: await this.buildFallbackBundle(result),
            subvariants,
            warnings: [
                teachingWarning('PIPELINE_BUNDLE_MISSING', 'warning', 'rendering_pipeline_detection did not return pipeline_bundle; used legacy fallback parsing.', 'detection'),
            ],
        };
    }
    extractPipelineBundle(result) {
        const data = result.rawResults?.pipeline_bundle?.data;
        if (!data || typeof data !== 'object' || Array.isArray(data))
            return null;
        const bundle = data;
        const detectionRaw = bundle.detection;
        if (!detectionRaw || typeof detectionRaw.primaryPipelineId !== 'string')
            return null;
        const primaryPipelineId = String(detectionRaw.primaryPipelineId || teaching_config_1.TEACHING_DEFAULTS.pipelineId);
        const primaryConfidence = (0, teaching_types_1.validateConfidence)(detectionRaw.primaryConfidence, teaching_config_1.TEACHING_DEFAULTS.confidence);
        const detection = {
            detected: detectionRaw.detected !== false,
            primaryPipelineId,
            primaryConfidence,
            candidates: Array.isArray(detectionRaw.candidates)
                ? (0, teaching_types_1.parseCandidates)(detectionRaw.candidates, teaching_config_1.TEACHING_LIMITS.maxCandidates)
                : [{ id: primaryPipelineId, confidence: primaryConfidence }],
            features: Array.isArray(detectionRaw.features) ? (0, teaching_types_1.parseFeatures)(detectionRaw.features) : [],
            traceRequirementsMissing: Array.isArray(detectionRaw.traceRequirementsMissing)
                ? detectionRaw.traceRequirementsMissing.map((value) => String(value))
                : [],
        };
        return {
            detection,
            teachingContent: this.normalizeTeachingContent(bundle.teachingContent, bundle.docPath),
            pinInstructions: normalizePinInstructions(bundle.pinInstructions),
            activeRenderingProcesses: normalizeActiveProcesses(bundle.activeRenderingProcesses),
            docPath: String(bundle.docPath || teaching_config_1.TEACHING_DEFAULTS.docPath),
        };
    }
    async buildFallbackBundle(result) {
        const rawResults = result.rawResults || {};
        const pipelineRow = this.firstObjectRow(rawResults.determine_pipeline);
        const traceRequirementsRow = this.firstObjectRow(rawResults.trace_requirements);
        const activeRenderingProcesses = (0, teaching_types_1.validateActiveProcesses)(rawResults[teaching_config_1.TEACHING_STEP_IDS.activeProcesses]);
        const primaryPipelineId = String(pipelineRow?.primary_pipeline_id || teaching_config_1.TEACHING_DEFAULTS.pipelineId);
        const primaryConfidence = (0, teaching_types_1.validateConfidence)(pipelineRow?.primary_confidence, teaching_config_1.TEACHING_DEFAULTS.confidence);
        const candidates = pipelineRow?.candidates_list
            ? (0, teaching_types_1.parseCandidates)(pipelineRow.candidates_list, teaching_config_1.TEACHING_LIMITS.maxCandidates)
            : [{ id: primaryPipelineId, confidence: primaryConfidence }];
        const features = (0, teaching_types_1.parseFeatures)(pipelineRow?.features_list);
        const traceRequirementsMissing = traceRequirementsRow
            ? Object.values(traceRequirementsRow)
                .filter((value) => typeof value === 'string' && value.trim().length > 0)
                .map((value) => value.trim())
            : [];
        const docPath = String(pipelineRow?.doc_path || teaching_config_1.TEACHING_DEFAULTS.docPath);
        await (0, pipelineSkillLoader_1.ensurePipelineSkillsInitialized)();
        const yamlTeaching = pipelineSkillLoader_1.pipelineSkillLoader.getTeachingContent(primaryPipelineId);
        const mdTeaching = (0, pipelineDocService_1.getPipelineDocService)().getTeachingContent(primaryPipelineId);
        const teachingContent = yamlTeaching
            ? (0, teaching_types_1.transformTeachingContent)(yamlTeaching, pipelineSkillLoader_1.pipelineSkillLoader.getPipelineMeta(primaryPipelineId)?.doc_path || docPath)
            : mdTeaching
                ? {
                    title: mdTeaching.title,
                    summary: mdTeaching.summary,
                    mermaidBlocks: mdTeaching.mermaidBlocks,
                    threadRoles: mdTeaching.threadRoles,
                    keySlices: mdTeaching.keySlices,
                    docPath: mdTeaching.docPath,
                }
                : null;
        const smartFilterConfigs = pipelineSkillLoader_1.pipelineSkillLoader.getSmartFilterConfigs(primaryPipelineId);
        const pinInstructions = pipelineSkillLoader_1.pipelineSkillLoader
            .getAutoPinInstructions(primaryPipelineId)
            .slice(0, teaching_config_1.TEACHING_LIMITS.maxPinInstructions)
            .map((inst) => {
            const hasSmartFilter = inst.smart_filter?.enabled ?? smartFilterConfigs.has(inst.pattern);
            const rawInstruction = {
                pattern: inst.pattern,
                match_by: inst.match_by,
                priority: inst.priority,
                reason: inst.reason,
                expand: inst.expand,
                main_thread_only: inst.main_thread_only,
                smart_filter: hasSmartFilter ? (inst.smart_filter || { enabled: true }) : undefined,
            };
            const transformed = (0, teaching_types_1.transformPinInstruction)(rawInstruction, activeRenderingProcesses);
            if (transformed.smartPin && !transformed.skipPin) {
                transformed.reason = `${inst.reason} (${activeRenderingProcesses.length} 活跃进程)`;
            }
            return transformed;
        });
        return {
            detection: {
                detected: !!pipelineRow,
                primaryPipelineId,
                primaryConfidence,
                candidates,
                features,
                traceRequirementsMissing,
            },
            teachingContent,
            pinInstructions,
            activeRenderingProcesses,
            docPath,
        };
    }
    firstObjectRow(step) {
        if (!step?.data)
            return null;
        if (Array.isArray(step.data)) {
            const row = step.data.find((item) => item && typeof item === 'object' && !Array.isArray(item));
            return row ? row : null;
        }
        if (typeof step.data === 'object')
            return step.data;
        return null;
    }
    normalizeTeachingContent(raw, docPath) {
        if (!raw || typeof raw !== 'object')
            return null;
        const content = raw;
        const title = (0, traceProcessorRowUtils_1.toOptionalString)(content.title);
        if (!title)
            return null;
        return {
            title,
            summary: String(content.summary || ''),
            mermaidBlocks: Array.isArray(content.mermaidBlocks)
                ? content.mermaidBlocks.map((block) => String(block))
                : [],
            threadRoles: Array.isArray(content.threadRoles)
                ? content.threadRoles.map((role) => {
                    const item = role;
                    return {
                        thread: String(item.thread || ''),
                        responsibility: String(item.responsibility || ''),
                        traceTag: item.traceTag === undefined ? undefined : String(item.traceTag),
                    };
                })
                : [],
            keySlices: Array.isArray(content.keySlices)
                ? content.keySlices.map((sliceName) => String(sliceName))
                : [],
            docPath: String(content.docPath || docPath || ''),
        };
    }
    async buildObservedFlow(input, bundle, detection) {
        const timeRange = extractTimeRange(input);
        const keySliceNames = collectKeySliceNames(bundle.teachingContent);
        const fallbackProcess = bundle.activeRenderingProcesses[0]?.processName;
        const effectiveProcessName = input.packageName || input.processName || fallbackProcess || undefined;
        const fallbackUsed = !input.packageName && !input.processName && fallbackProcess
            ? 'active_rendering_process'
            : undefined;
        const queryWarnings = [];
        let rows = [];
        try {
            rows = await this.queryObservedEventRows(input.traceId, keySliceNames, effectiveProcessName, timeRange);
        }
        catch (error) {
            queryWarnings.push(`Observed flow query failed: ${error?.message || error}`);
        }
        const events = rows.map((row, index) => ({
            id: `event-${index + 1}-${row.ts}`,
            stage: row.stage,
            name: row.name,
            ts: row.ts,
            dur: row.dur,
            durMs: (0, traceProcessorRowUtils_1.nsToMs)(row.dur),
            processName: row.processName,
            threadName: row.threadName,
            trackId: row.trackId,
            utid: row.utid,
            upid: row.upid,
            evidenceSource: 'observed_slice_query',
            confidence: 0.9,
        }));
        let lanes = this.buildLanes(input, bundle, detection, events);
        let eventsWithLanes = events.map((event) => ({
            ...event,
            laneId: this.matchEventLane(event, lanes),
        }));
        const criticalTaskResult = await this.buildCriticalTasks(input.traceId, eventsWithLanes);
        lanes = this.mergeCriticalTaskLanes(lanes, criticalTaskResult.criticalTasks);
        const criticalTasks = criticalTaskResult.criticalTasks.map((task) => ({
            ...task,
            laneId: task.laneId || this.matchOwnerLane(task.processName, task.threadName, lanes),
        }));
        eventsWithLanes = eventsWithLanes.map((event) => {
            const directTask = criticalTasks.find((task) => task.rootEventId === event.id && task.kind === 'direct_wakeup');
            return directTask
                ? {
                    ...event,
                    threadStateId: directTask.threadStateId,
                    criticalTaskId: directTask.id,
                }
                : event;
        });
        const dependencies = this.buildDependencies(lanes, eventsWithLanes, criticalTasks);
        const missingSignals = this.computeMissingSignals(eventsWithLanes, lanes, bundle, criticalTasks);
        const completenessWarnings = [...queryWarnings];
        completenessWarnings.push(...criticalTaskResult.warnings);
        if (eventsWithLanes.length === 0) {
            completenessWarnings.push('No key rendering slices were observed for the current context.');
        }
        if (fallbackUsed) {
            completenessWarnings.push(`No package/process hint was provided; used ${fallbackProcess} as fallback.`);
        }
        const completenessLevel = eventsWithLanes.length >= 5 && missingSignals.length <= 1
            ? 'high'
            : eventsWithLanes.length > 0
                ? 'medium'
                : 'low';
        return {
            observedFlow: {
                schemaVersion: 'observed-flow.v1',
                context: {
                    traceId: input.traceId,
                    packageName: input.packageName,
                    processName: input.processName || effectiveProcessName,
                    timeRange,
                    selection: input.selectionContext,
                    sourcePriority: OBSERVED_SOURCE_PRIORITY,
                    fallbackUsed,
                },
                lanes,
                events: eventsWithLanes,
                dependencies,
                criticalTasks,
                completeness: {
                    level: completenessLevel,
                    missingSignals,
                    warnings: completenessWarnings,
                },
            },
            queryWarnings,
        };
    }
    async queryObservedEventRows(traceId, keySliceNames, processName, timeRange) {
        const exactNames = keySliceNames.map(normalizeKeySliceName).filter(Boolean);
        const exactClause = exactNames.length > 0
            ? `s.name IN (${exactNames.map(sqlLiteral).join(', ')})`
            : '0';
        const likeFragments = [
            'Choreographer',
            'DrawFrame',
            'syncFrameState',
            'performTraversals',
            'queueBuffer',
            'dequeueBuffer',
            'BLASTBufferQueue',
            'Transaction',
            'latchBuffer',
            'eglSwapBuffers',
            'vkQueuePresent',
            'DrawGL',
            'DrawFunctor',
            'FrameTimeline',
            'present',
            'HWC',
        ];
        const likeClause = likeFragments
            .map((fragment) => `s.name LIKE ${sqlLikeLiteral(fragment)} ESCAPE '\\'`)
            .join(' OR ');
        const timeClause = timeRange
            ? `AND s.ts >= ${timeRange.startTs} AND s.ts < ${timeRange.endTs}`
            : '';
        const processClause = processName
            ? `AND (
          p.name = ${sqlLiteral(processName)}
          OR p.name LIKE ${sqlLikeLiteral(processName)} ESCAPE '\\'
          OR p.name LIKE '%surfaceflinger%'
          OR p.name = 'system_server'
        )`
            : '';
        const sql = `
      SELECT
        s.ts AS ts,
        s.dur AS dur,
        s.name AS name,
        s.track_id AS track_id,
        tt.utid AS utid,
        t.upid AS upid,
        t.name AS thread_name,
        p.name AS process_name,
        CASE
          WHEN lower(s.name) LIKE '%choreographer%doframe%' OR lower(s.name) LIKE '%traversal%' THEN 'app_frame'
          WHEN t.name = 'RenderThread' OR lower(s.name) LIKE '%drawframe%' OR lower(s.name) LIKE '%syncframestate%' THEN 'render_thread'
          WHEN lower(s.name) LIKE '%queuebuffer%' OR lower(s.name) LIKE '%dequeuebuffer%' OR lower(s.name) LIKE '%blast%' OR lower(s.name) LIKE '%transaction%' THEN 'buffer_queue_transaction'
          WHEN lower(p.name) LIKE '%surfaceflinger%' OR lower(s.name) LIKE '%latchbuffer%' THEN 'surfaceflinger_composition'
          WHEN lower(t.name) LIKE '%hwc%' OR lower(s.name) LIKE '%present%' THEN 'present'
          ELSE 'producer'
        END AS stage
      FROM slice s
      LEFT JOIN thread_track tt ON s.track_id = tt.id
      LEFT JOIN thread t ON tt.utid = t.utid
      LEFT JOIN process p ON t.upid = p.upid
      WHERE s.dur > 0
        ${timeClause}
        ${processClause}
        AND (${exactClause} OR ${likeClause})
      ORDER BY s.ts
      LIMIT 200
    `;
        const result = await this.traceProcessorService.query(traceId, sql);
        return (0, traceProcessorRowUtils_1.rowsToObjects)(result).map((row) => {
            const stage = stageFromRow(row);
            return {
                ts: (0, traceProcessorRowUtils_1.toNumber)(row.ts, 0),
                dur: (0, traceProcessorRowUtils_1.toNumber)(row.dur, 0),
                name: String(row.name || ''),
                trackId: safeNumber(row.track_id),
                utid: safeNumber(row.utid),
                upid: safeNumber(row.upid),
                threadName: (0, traceProcessorRowUtils_1.toOptionalString)(row.thread_name) || undefined,
                processName: (0, traceProcessorRowUtils_1.toOptionalString)(row.process_name) || undefined,
                stage,
            };
        });
    }
    buildLanes(input, bundle, detection, events) {
        const lanes = new Map();
        const pipelineIds = detection.candidates.length > 0
            ? detection.candidates.map((candidate) => candidate.id)
            : [detection.primaryPipelineId];
        const addLane = (role, processName, threadName, confidence, evidenceSource, title, suffix) => {
            const id = laneIdFor(role, processName, threadName, suffix);
            const existing = lanes.get(id);
            if (existing) {
                existing.confidence = Math.max(existing.confidence, confidence);
                return;
            }
            lanes.set(id, {
                id,
                role,
                title: title || laneTitle(role, processName, threadName),
                processName,
                threadName,
                trackHint: makeTrackHint(role, processName, threadName),
                pipelineIds,
                confidence,
                evidenceSource,
            });
        };
        for (const process of bundle.activeRenderingProcesses) {
            addLane('app', process.processName, 'main', 0.75, 'active_rendering_processes');
            if (process.renderThreadTid > 0) {
                addLane('render_thread', process.processName, 'RenderThread', 0.75, 'active_rendering_processes');
            }
        }
        for (const event of events) {
            const role = roleForStage(event.stage, event.processName, event.threadName);
            addLane(role, event.processName, event.threadName, 0.9, 'observed_slice_query');
        }
        const detectionText = [
            detection.primaryPipelineId,
            ...detection.candidates.map((candidate) => candidate.id),
            ...detection.features.map((feature) => feature.name),
        ].join(' ');
        for (const pattern of PRODUCER_PIPELINE_PATTERNS) {
            if (!pattern.test.test(detectionText))
                continue;
            addLane(pattern.role, input.packageName || input.processName || bundle.activeRenderingProcesses[0]?.processName, undefined, 0.55, 'detection_features', pattern.title, pattern.title);
        }
        return Array.from(lanes.values()).sort((a, b) => {
            const roleDiff = sortLaneRole(a.role) - sortLaneRole(b.role);
            if (roleDiff !== 0)
                return roleDiff;
            return a.title.localeCompare(b.title);
        });
    }
    matchEventLane(event, lanes) {
        const role = roleForStage(event.stage, event.processName, event.threadName);
        const exact = lanes.find((lane) => lane.role === role &&
            lane.processName === event.processName &&
            lane.threadName === event.threadName);
        if (exact)
            return exact.id;
        const roleMatch = lanes.find((lane) => lane.role === role);
        return roleMatch?.id;
    }
    async buildCriticalTasks(traceId, events) {
        const roots = this.selectCriticalTaskRoots(events);
        if (roots.length === 0)
            return { criticalTasks: [], warnings: [] };
        const warnings = [];
        const criticalTasks = [];
        try {
            const wakeupRows = await this.queryWakeupRows(traceId, roots);
            criticalTasks.push(...this.buildDirectWakeupTasks(wakeupRows));
        }
        catch (error) {
            warnings.push(`Scheduler wakeup query failed: ${error?.message || error}`);
        }
        try {
            await this.traceProcessorService.query(traceId, 'INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;');
            const stackRows = await this.queryCriticalPathRows(traceId, roots);
            criticalTasks.push(...this.buildCriticalPathTasks(stackRows));
        }
        catch (error) {
            warnings.push(`Official critical path query failed: ${error?.message || error}`);
        }
        const deduped = new Map();
        for (const task of criticalTasks) {
            deduped.set(task.id, task);
        }
        const result = Array.from(deduped.values()).slice(0, MAX_CRITICAL_TASKS);
        if (result.length === 0) {
            warnings.push('No scheduler wakeup or official critical path rows were observed for key rendering events.');
        }
        return { criticalTasks: result, warnings };
    }
    selectCriticalTaskRoots(events) {
        const stagePriority = {
            app_frame: 0,
            render_thread: 1,
            producer: 2,
            buffer_queue_transaction: 3,
            surfaceflinger_composition: 4,
            present: 5,
        };
        const seenLaneIds = new Set();
        const roots = [];
        const candidates = events
            .filter((event) => event.utid !== undefined && event.dur > 0)
            .sort((a, b) => (stagePriority[a.stage] ?? 99) - (stagePriority[b.stage] ?? 99) ||
            a.ts - b.ts);
        for (const event of candidates) {
            const key = event.laneId || `${event.processName}:${event.threadName}:${event.utid}`;
            if (seenLaneIds.has(key))
                continue;
            seenLaneIds.add(key);
            roots.push(event);
            if (roots.length >= MAX_CRITICAL_TASK_ROOTS)
                break;
        }
        return roots;
    }
    async queryWakeupRows(traceId, roots) {
        const values = roots
            .map((event) => `(${sqlLiteral(event.id)}, ${event.laneId ? sqlLiteral(event.laneId) : 'NULL'}, ${Math.trunc(event.ts)}, ${Math.trunc(event.ts + event.dur)}, ${Math.trunc(event.utid || 0)})`)
            .join(',\n        ');
        const sql = `
      WITH root_events(event_id, lane_id, event_ts, event_end, event_utid) AS (
        VALUES
        ${values}
      ),
      candidate_state AS (
        SELECT
          root_events.event_id,
          root_events.lane_id,
          target.id AS thread_state_id,
          target.ts,
          target.dur,
          target.utid,
          target.state,
          target.waker_id,
          target.irq_context AS target_irq_context,
          thread.name AS thread_name,
          process.name AS process_name,
          max(0, min(target.ts + target.dur, root_events.event_end) - max(target.ts, root_events.event_ts)) AS overlap_ns,
          abs(target.ts - root_events.event_ts) AS start_distance
        FROM root_events
        JOIN thread_state AS target
          ON target.utid = root_events.event_utid
         AND target.dur > 0
         AND target.ts <= root_events.event_end
         AND target.ts + target.dur >= root_events.event_ts
        LEFT JOIN thread USING(utid)
        LEFT JOIN process USING(upid)
      ),
      ranked_state AS (
        SELECT
          *,
          row_number() OVER (
            PARTITION BY event_id
            ORDER BY overlap_ns DESC, start_distance ASC, ts DESC
          ) AS rn
        FROM candidate_state
      )
      SELECT
        ranked_state.event_id,
        ranked_state.lane_id,
        ranked_state.thread_state_id,
        ranked_state.ts,
        ranked_state.dur,
        ranked_state.utid,
        ranked_state.state,
        ranked_state.target_irq_context,
        ranked_state.thread_name,
        ranked_state.process_name,
        waker.id AS waker_thread_state_id,
        waker.utid AS waker_utid,
        waker.state AS waker_state,
        waker.irq_context AS waker_irq_context,
        waker_thread.tid AS waker_tid,
        waker_thread.name AS waker_thread_name,
        waker_process.name AS waker_process_name
      FROM ranked_state
      LEFT JOIN thread_state AS waker ON ranked_state.waker_id = waker.id
      LEFT JOIN thread AS waker_thread ON waker.utid = waker_thread.utid
      LEFT JOIN process AS waker_process ON waker_thread.upid = waker_process.upid
      WHERE ranked_state.rn = 1
      ORDER BY ranked_state.ts
    `;
        const result = await this.traceProcessorService.query(traceId, sql);
        return (0, traceProcessorRowUtils_1.rowsToObjects)(result).map((row) => ({
            eventId: String(row.event_id || ''),
            eventLaneId: (0, traceProcessorRowUtils_1.toOptionalString)(row.lane_id) || undefined,
            threadStateId: safeNumber(row.thread_state_id),
            ts: (0, traceProcessorRowUtils_1.toNumber)(row.ts, 0),
            dur: (0, traceProcessorRowUtils_1.toNumber)(row.dur, 0),
            utid: safeNumber(row.utid),
            state: (0, traceProcessorRowUtils_1.toOptionalString)(row.state) || undefined,
            threadName: (0, traceProcessorRowUtils_1.toOptionalString)(row.thread_name) || undefined,
            processName: (0, traceProcessorRowUtils_1.toOptionalString)(row.process_name) || undefined,
            wakerThreadStateId: safeNumber(row.waker_thread_state_id),
            wakerUtid: safeNumber(row.waker_utid),
            wakerTid: safeNumber(row.waker_tid),
            wakerState: (0, traceProcessorRowUtils_1.toOptionalString)(row.waker_state) || undefined,
            wakerThreadName: (0, traceProcessorRowUtils_1.toOptionalString)(row.waker_thread_name) || undefined,
            wakerProcessName: (0, traceProcessorRowUtils_1.toOptionalString)(row.waker_process_name) || undefined,
            wakerIrqContext: (0, traceProcessorRowUtils_1.toNullableNumber)(row.waker_irq_context) === 1,
            targetIrqContext: (0, traceProcessorRowUtils_1.toNullableNumber)(row.target_irq_context) === 1,
        }));
    }
    buildDirectWakeupTasks(rows) {
        return rows
            .filter((row) => row.threadStateId !== undefined)
            .map((row) => {
            const hasWaker = row.wakerThreadStateId !== undefined || row.targetIrqContext;
            const wakerKind = classifyWakerKind(row.wakerThreadName, row.wakerTid, row.wakerIrqContext || row.targetIrqContext);
            return {
                id: `critical-task-wakeup-${row.eventId}`,
                kind: 'direct_wakeup',
                rootEventId: row.eventId,
                rootLaneId: row.eventLaneId,
                laneId: row.eventLaneId,
                name: hasWaker
                    ? `direct waker: ${row.wakerThreadName || row.wakerProcessName || wakerKind}`
                    : 'direct waker: not recorded',
                ts: row.ts,
                dur: row.dur,
                durMs: (0, traceProcessorRowUtils_1.nsToMs)(row.dur),
                processName: row.processName,
                threadName: row.threadName,
                utid: row.utid,
                threadStateId: row.threadStateId,
                state: row.state,
                waker: hasWaker
                    ? {
                        threadStateId: row.wakerThreadStateId,
                        utid: row.wakerUtid,
                        processName: row.wakerProcessName,
                        threadName: row.wakerThreadName,
                        state: row.wakerState,
                        irqContext: row.wakerIrqContext || row.targetIrqContext,
                        kind: wakerKind,
                    }
                    : undefined,
                evidenceSource: 'thread_state_waker_id',
                confidence: hasWaker ? 0.85 : 0.55,
            };
        });
    }
    async queryCriticalPathRows(traceId, roots) {
        const rows = [];
        for (const root of roots) {
            const sql = `
        SELECT
          ${sqlLiteral(root.id)} AS root_event_id,
          ${root.laneId ? sqlLiteral(root.laneId) : 'NULL'} AS root_lane_id,
          cr.id AS entity_id,
          cr.ts,
          cr.dur,
          cr.utid,
          cr.stack_depth,
          cr.name,
          cr.table_name,
          thread.name AS thread_name,
          process.name AS process_name
        FROM _critical_path_stack(${Math.trunc(root.utid || 0)}, ${Math.trunc(root.ts)}, ${Math.trunc(root.dur)}, 1, 1, 1, 1) AS cr
        LEFT JOIN thread USING(utid)
        LEFT JOIN process USING(upid)
        WHERE cr.name IS NOT NULL
          AND cr.dur > 0
          AND cr.utid IS NOT NULL
          AND cr.utid != ${Math.trunc(root.utid || 0)}
        ORDER BY cr.ts ASC, cr.stack_depth ASC, cr.utid ASC
        LIMIT ${MAX_CRITICAL_PATH_ROWS_PER_ROOT}
      `;
            const result = await this.traceProcessorService.query(traceId, sql);
            rows.push(...(0, traceProcessorRowUtils_1.rowsToObjects)(result).map((row) => ({
                rootEventId: String(row.root_event_id || ''),
                rootLaneId: (0, traceProcessorRowUtils_1.toOptionalString)(row.root_lane_id) || undefined,
                entityId: safeNumber(row.entity_id),
                ts: (0, traceProcessorRowUtils_1.toNumber)(row.ts, 0),
                dur: (0, traceProcessorRowUtils_1.toNumber)(row.dur, 0),
                utid: safeNumber(row.utid),
                stackDepth: safeNumber(row.stack_depth),
                name: String(row.name || ''),
                tableName: (0, traceProcessorRowUtils_1.toOptionalString)(row.table_name) || undefined,
                threadName: (0, traceProcessorRowUtils_1.toOptionalString)(row.thread_name) || undefined,
                processName: (0, traceProcessorRowUtils_1.toOptionalString)(row.process_name) || undefined,
            })));
        }
        return rows;
    }
    buildCriticalPathTasks(rows) {
        const segments = new Map();
        for (const row of rows) {
            const key = `${row.rootEventId}:${row.ts}:${row.dur}:${row.utid}`;
            let segment = segments.get(key);
            if (!segment) {
                segment = {
                    rootEventId: row.rootEventId,
                    rootLaneId: row.rootLaneId,
                    ts: row.ts,
                    dur: row.dur,
                    utid: row.utid,
                    threadName: row.threadName,
                    processName: row.processName,
                    tableName: row.tableName,
                    stackDepth: row.stackDepth,
                    names: new Set(),
                };
                segments.set(key, segment);
            }
            segment.threadName ?? (segment.threadName = row.threadName);
            segment.processName ?? (segment.processName = row.processName);
            segment.tableName ?? (segment.tableName = row.tableName);
            segment.stackDepth = Math.min(segment.stackDepth ?? row.stackDepth ?? 0, row.stackDepth ?? 0);
            segment.names.add(row.name);
            const state = stripPrefix(row.name, 'blocking thread_state:');
            if (state) {
                segment.state = state;
                segment.threadStateId ?? (segment.threadStateId = row.entityId);
            }
            const processName = stripPrefix(row.name, 'blocking process_name:');
            if (processName)
                segment.processName = processName;
            const threadName = stripPrefix(row.name, 'blocking thread_name:');
            if (threadName)
                segment.threadName = threadName;
        }
        return Array.from(segments.values())
            .map((segment, index) => {
            const names = Array.from(segment.names);
            const sliceName = names.find((name) => !name.startsWith('blocking ') && !name.startsWith('cpu:'));
            return {
                id: `critical-task-path-${segment.rootEventId}-${index + 1}-${segment.ts}`,
                kind: 'critical_path_segment',
                rootEventId: segment.rootEventId,
                rootLaneId: segment.rootLaneId,
                name: sliceName || segment.state || segment.threadName || 'official critical path segment',
                ts: segment.ts,
                dur: segment.dur,
                durMs: (0, traceProcessorRowUtils_1.nsToMs)(segment.dur),
                processName: segment.processName,
                threadName: segment.threadName,
                utid: segment.utid,
                threadStateId: segment.threadStateId,
                state: segment.state,
                tableName: segment.tableName,
                stackDepth: segment.stackDepth,
                evidenceSource: 'official_critical_path_stack',
                confidence: 0.8,
            };
        })
            .filter((task) => task.dur > 0)
            .sort((a, b) => a.ts - b.ts || b.dur - a.dur);
    }
    mergeCriticalTaskLanes(lanes, criticalTasks) {
        const merged = new Map(lanes.map((lane) => [lane.id, lane]));
        const addCriticalLane = (processName, threadName) => {
            if (!processName && !threadName)
                return;
            const existing = Array.from(merged.values()).find((lane) => sameLaneOwner(lane, processName, threadName));
            if (existing)
                return;
            const id = laneIdFor('critical_task', processName, threadName);
            if (merged.has(id))
                return;
            merged.set(id, {
                id,
                role: 'critical_task',
                title: laneTitle('critical_task', processName, threadName),
                processName,
                threadName,
                trackHint: makeTrackHint('critical_task', processName, threadName),
                pipelineIds: [],
                confidence: 0.72,
                evidenceSource: 'official_critical_path_or_wakeup',
            });
        };
        for (const task of criticalTasks) {
            addCriticalLane(task.processName, task.threadName);
            addCriticalLane(task.waker?.processName, task.waker?.threadName);
        }
        return Array.from(merged.values()).sort((a, b) => {
            const roleDiff = sortLaneRole(a.role) - sortLaneRole(b.role);
            if (roleDiff !== 0)
                return roleDiff;
            return a.title.localeCompare(b.title);
        });
    }
    matchOwnerLane(processName, threadName, lanes) {
        const exact = lanes.find((lane) => sameLaneOwner(lane, processName, threadName));
        if (exact)
            return exact.id;
        if (threadName) {
            const threadMatch = lanes.find((lane) => lane.threadName === threadName);
            if (threadMatch)
                return threadMatch.id;
        }
        if (processName) {
            const processMatch = lanes.find((lane) => lane.processName === processName);
            if (processMatch)
                return processMatch.id;
        }
        return undefined;
    }
    buildDependencies(lanes, events, criticalTasks) {
        const dependencies = [];
        const firstByRole = (role) => lanes.find((lane) => lane.role === role);
        const pairs = [
            ['app', 'render_thread', 'produces_to'],
            ['render_thread', 'producer', 'overlaps_with'],
            ['producer', 'buffer_queue', 'produces_to'],
            ['render_thread', 'buffer_queue', 'produces_to'],
            ['buffer_queue', 'surfaceflinger', 'composes_to'],
            ['surfaceflinger', 'hwc_present', 'presents_to'],
        ];
        for (const [fromRole, toRole, relation] of pairs) {
            const from = firstByRole(fromRole);
            const to = firstByRole(toRole);
            if (!from || !to || from.id === to.id)
                continue;
            if (!this.hasObservedDependencyEvidence(from.id, to.id, events, relation))
                continue;
            dependencies.push({
                fromLaneId: from.id,
                toLaneId: to.id,
                relation,
                confidence: 0.65,
                evidenceSource: 'observed_event_order',
            });
        }
        dependencies.push(...this.buildWakeupDependencies(lanes, criticalTasks));
        dependencies.push(...this.buildCriticalPathDependencies(lanes, criticalTasks));
        const seen = new Set();
        return dependencies.filter((dependency) => {
            const key = [
                dependency.fromLaneId,
                dependency.toLaneId,
                dependency.relation,
                dependency.fromTaskId,
                dependency.toTaskId,
            ].join(':');
            if (seen.has(key))
                return false;
            seen.add(key);
            return true;
        });
    }
    buildWakeupDependencies(lanes, criticalTasks) {
        const dependencies = [];
        for (const task of criticalTasks) {
            if (task.kind !== 'direct_wakeup' || !task.waker || !task.rootLaneId)
                continue;
            const fromLaneId = this.matchOwnerLane(task.waker.processName, task.waker.threadName, lanes);
            const toLaneId = task.rootLaneId;
            if (!fromLaneId || fromLaneId === toLaneId)
                continue;
            dependencies.push({
                fromLaneId,
                toLaneId,
                relation: 'wakes_to',
                confidence: task.confidence,
                evidenceSource: 'thread_state_waker_id',
                toEventId: task.rootEventId,
                fromTaskId: task.waker.threadStateId ? `thread_state:${task.waker.threadStateId}` : undefined,
                toTaskId: task.threadStateId ? `thread_state:${task.threadStateId}` : task.id,
                detail: `${task.waker.kind || 'thread'} wakeup -> ${taskOwnerLabel(task)}`,
            });
        }
        return dependencies;
    }
    buildCriticalPathDependencies(lanes, criticalTasks) {
        const dependencies = [];
        for (const task of criticalTasks) {
            if (task.kind !== 'critical_path_segment' || !task.rootLaneId)
                continue;
            const fromLaneId = this.matchOwnerLane(task.processName, task.threadName, lanes);
            const toLaneId = task.rootLaneId;
            if (!fromLaneId || fromLaneId === toLaneId)
                continue;
            dependencies.push({
                fromLaneId,
                toLaneId,
                relation: 'critical_path_to',
                confidence: task.confidence,
                evidenceSource: 'official_critical_path_stack',
                toEventId: task.rootEventId,
                fromTaskId: task.id,
                detail: `${taskOwnerLabel(task)} is on the official critical path for the rendering event.`,
            });
        }
        return dependencies;
    }
    hasObservedDependencyEvidence(fromLaneId, toLaneId, events, relation) {
        const fromEvents = events.filter((event) => event.laneId === fromLaneId);
        const toEvents = events.filter((event) => event.laneId === toLaneId);
        if (!fromEvents.length || !toEvents.length)
            return false;
        return fromEvents.some((from) => {
            const fromEnd = from.ts + Math.max(from.dur, 0);
            return toEvents.some((to) => {
                const toEnd = to.ts + Math.max(to.dur, 0);
                const overlaps = from.ts < toEnd && to.ts < fromEnd;
                if (relation === 'overlaps_with')
                    return overlaps;
                return overlaps || from.ts <= to.ts;
            });
        });
    }
    computeMissingSignals(events, lanes, bundle, criticalTasks = []) {
        const missing = [];
        const hasStage = (stage) => events.some((event) => event.stage === stage);
        const hasRole = (role) => lanes.some((lane) => lane.role === role);
        if (!hasStage('app_frame') && !hasRole('app')) {
            missing.push('app main frame slices');
        }
        if (!hasStage('render_thread') && !hasRole('render_thread')) {
            missing.push('RenderThread draw slices');
        }
        if (!hasStage('surfaceflinger_composition') && !hasRole('surfaceflinger')) {
            missing.push('SurfaceFlinger composition slices');
        }
        if (!hasStage('present') && !hasRole('hwc_present')) {
            missing.push('HWC/present fence slices');
        }
        if (events.length > 0 && criticalTasks.length === 0) {
            missing.push('sched critical path / wakeup relationships');
        }
        for (const requirement of bundle.detection.traceRequirementsMissing) {
            missing.push(requirement);
        }
        return Array.from(new Set(missing));
    }
    buildPinPlan(instructions, observedFlow) {
        const expectedTrackHints = observedFlow.lanes
            .map((lane) => lane.trackHint)
            .filter((hint) => hint !== undefined);
        const warnings = [];
        if (instructions.length === 0) {
            warnings.push('No pin instructions were produced for this pipeline.');
        }
        if (expectedTrackHints.length === 0) {
            warnings.push('No observed lanes have concrete track hints.');
        }
        const status = instructions.length === 0
            ? 'empty'
            : expectedTrackHints.length === 0
                ? 'partial'
                : 'planned';
        return {
            status,
            instructions,
            expectedLaneIds: observedFlow.lanes.map((lane) => lane.id),
            expectedTrackHints,
            summary: status === 'planned'
                ? `${instructions.length} pin instructions planned for ${expectedTrackHints.length} observed lanes.`
                : warnings[0] || 'Pin plan is incomplete.',
            warnings,
        };
    }
    buildOverlayPlan(teachingContent, observedFlow) {
        const keySliceNames = collectKeySliceNames(teachingContent);
        const eventIds = observedFlow.events.map((event) => event.id);
        const warnings = [];
        if (eventIds.length === 0) {
            warnings.push('No observed events are available for timeline overlay.');
        }
        const status = eventIds.length > 0 ? 'ready' : 'empty';
        return {
            status,
            skillId: 'pipeline_key_slices_overlay',
            eventIds,
            keySliceNames,
            timeRange: observedFlow.context.timeRange,
            summary: status === 'ready'
                ? `${eventIds.length} observed events ready for overlay.`
                : 'Overlay has no observed events for the current context.',
            warnings,
        };
    }
    buildWarnings(detectionBundle, observedFlow, queryWarnings, teachingContent, pinPlan, overlayPlan) {
        const warnings = [...detectionBundle.warnings];
        const detection = detectionBundle.bundle.detection;
        if (detection.primaryConfidence < 0.6) {
            warnings.push(teachingWarning('LOW_PIPELINE_CONFIDENCE', 'warning', `Primary pipeline confidence is ${Math.round(detection.primaryConfidence * 100)}%.`, 'detection'));
        }
        for (const missing of detection.traceRequirementsMissing) {
            warnings.push(teachingWarning('TRACE_REQUIREMENT_MISSING', 'warning', missing, 'detection'));
        }
        for (const message of queryWarnings) {
            warnings.push(teachingWarning('OBSERVED_FLOW_QUERY_WARNING', 'warning', message, 'observed_flow'));
        }
        for (const message of observedFlow.completeness.warnings) {
            warnings.push(teachingWarning('OBSERVED_FLOW_INCOMPLETE', 'info', message, 'observed_flow'));
        }
        if (observedFlow.events.length === 0) {
            warnings.push(teachingWarning('NO_OBSERVED_EVENTS', 'warning', 'No real key rendering events were found; teaching content is explanatory only for this trace context.', 'observed_flow'));
        }
        if (!teachingContent.keySlices.length) {
            warnings.push(teachingWarning('TEACHING_KEY_SLICES_EMPTY', 'info', 'Teaching content has no key slice list; overlay used default rendering slice names.', 'teaching'));
        }
        for (const message of pinPlan.warnings) {
            warnings.push(teachingWarning('PIN_PLAN_WARNING', 'info', message, 'pin'));
        }
        for (const message of overlayPlan.warnings) {
            warnings.push(teachingWarning('OVERLAY_PLAN_WARNING', 'info', message, 'overlay'));
        }
        const seen = new Set();
        return warnings.filter((warning) => {
            const key = `${warning.code}:${warning.message}`;
            if (seen.has(key))
                return false;
            seen.add(key);
            return true;
        });
    }
}
exports.RenderingPipelineTeachingService = RenderingPipelineTeachingService;
//# sourceMappingURL=renderingPipelineTeachingService.js.map