'use strict';

var EventBus = require('../core/EventBus.js');

/* ────────────────────────────────────────────────────────────────
   Performance LEGENDARY — Frame Budgeting, Memory Leak
   Detection, Auto-LOD, Worker Offloading
   
   Sprint L11: Advanced performance management and profiling.
──────────────────────────────────────────────────────────────── */
const DEFAULT_BUDGET_CONFIG = {
    targetFrameMs: 16.67,
    allocations: {
        physics: 0.25,
        update: 0.30,
        render: 0.35,
        audio: 0.05,
        network: 0.05,
    },
    warningThreshold: 1.5,
    sampleWindow: 60,
};
class FrameBudgetManager {
    constructor(config) {
        this.events = new EventBus.EventBus();
        this.currentFrame = new Map();
        this.history = [];
        this.frameCount = 0;
        this.startTimes = new Map();
        // Rolling averages
        this.rollingTotals = [];
        this.rollingSubsystems = new Map();
        this.config = { ...DEFAULT_BUDGET_CONFIG, ...config };
        if (config === null || config === void 0 ? void 0 : config.allocations) {
            this.config.allocations = { ...DEFAULT_BUDGET_CONFIG.allocations, ...config.allocations };
        }
    }
    /** Mark the start of a subsystem's work in this frame. */
    beginSubsystem(name) {
        this.startTimes.set(name, performance.now());
    }
    /** Mark the end of a subsystem's work in this frame. */
    endSubsystem(name) {
        var _a;
        const start = this.startTimes.get(name);
        if (start === undefined)
            return;
        const elapsed = performance.now() - start;
        this.currentFrame.set(name, ((_a = this.currentFrame.get(name)) !== null && _a !== void 0 ? _a : 0) + elapsed);
        this.startTimes.delete(name);
    }
    /** Complete the current frame, generate report, advance to next. */
    endFrame() {
        var _a;
        this.frameCount++;
        const subsystems = [];
        let totalMs = 0;
        for (const [name, actual] of this.currentFrame) {
            const allocation = (_a = this.config.allocations[name]) !== null && _a !== void 0 ? _a : 0;
            const budgetMs = this.config.targetFrameMs * allocation;
            const overBudget = actual > budgetMs;
            const utilization = budgetMs > 0 ? (actual / budgetMs) * 100 : 0;
            subsystems.push({
                subsystem: name,
                budgetMs,
                actualMs: actual,
                overBudget,
                utilizationPercent: utilization,
            });
            totalMs += actual;
            // Rolling averages
            let arr = this.rollingSubsystems.get(name);
            if (!arr) {
                arr = [];
                this.rollingSubsystems.set(name, arr);
            }
            arr.push(actual);
            if (arr.length > this.config.sampleWindow)
                arr.shift();
        }
        this.rollingTotals.push(totalMs);
        if (this.rollingTotals.length > this.config.sampleWindow)
            this.rollingTotals.shift();
        const snapshot = {
            frameNumber: this.frameCount,
            totalMs,
            overBudget: totalMs > this.config.targetFrameMs,
            subsystems,
        };
        this.history.push(snapshot);
        if (this.history.length > this.config.sampleWindow)
            this.history.shift();
        if (totalMs > this.config.targetFrameMs * this.config.warningThreshold) {
            this.events.emit('budget:warning', snapshot);
        }
        this.currentFrame.clear();
        return snapshot;
    }
    /** Get the average frame time over the rolling window. */
    getAverageFrameMs() {
        if (this.rollingTotals.length === 0)
            return 0;
        let sum = 0;
        for (const t of this.rollingTotals)
            sum += t;
        return sum / this.rollingTotals.length;
    }
    /** Get average time for a specific subsystem. */
    getSubsystemAverageMs(name) {
        const arr = this.rollingSubsystems.get(name);
        if (!arr || arr.length === 0)
            return 0;
        let sum = 0;
        for (const t of arr)
            sum += t;
        return sum / arr.length;
    }
    /** Get latest snapshot. */
    getLatest() {
        return this.history.length > 0 ? this.history[this.history.length - 1] : null;
    }
    /** Get the worst-case frame in history. */
    getWorstFrame() {
        if (this.history.length === 0)
            return null;
        let worst = this.history[0];
        for (const s of this.history) {
            if (s.totalMs > worst.totalMs)
                worst = s;
        }
        return worst;
    }
    /** Format a concise budget text report. */
    formatReport() {
        const latest = this.getLatest();
        if (!latest)
            return 'No data yet.';
        const lines = [
            `Frame #${latest.frameNumber} — Total: ${latest.totalMs.toFixed(2)}ms / ${this.config.targetFrameMs.toFixed(2)}ms budget`,
            '',
        ];
        for (const ss of latest.subsystems) {
            const bar = ss.overBudget ? '!' : ' ';
            lines.push(`  ${bar} ${ss.subsystem.padEnd(10)} ${ss.actualMs.toFixed(2)}ms / ${ss.budgetMs.toFixed(2)}ms (${ss.utilizationPercent.toFixed(0)}%)`);
        }
        const avg = this.getAverageFrameMs();
        lines.push('');
        lines.push(`  Avg: ${avg.toFixed(2)}ms (${(1000 / avg).toFixed(0)} FPS)`);
        return lines.join('\n');
    }
    /** Reset all collected data. */
    reset() {
        this.currentFrame.clear();
        this.startTimes.clear();
        this.history.length = 0;
        this.rollingTotals.length = 0;
        this.rollingSubsystems.clear();
        this.frameCount = 0;
    }
}
class MemoryLeakDetector {
    constructor(maxSnapshots = 120, checkIntervalMs = 1000) {
        this.events = new EventBus.EventBus();
        this.trackers = new Map();
        this.snapshots = [];
        this.lastCheckTime = 0;
        this.maxSnapshots = maxSnapshots;
        this.checkIntervalMs = checkIntervalMs;
    }
    /** Track an allocation (e.g., new entity, texture, sound). */
    trackAlloc(category, count = 1) {
        let tracker = this.trackers.get(category);
        if (!tracker) {
            tracker = { category, count: 0, peakCount: 0, totalAllocated: 0, totalFreed: 0 };
            this.trackers.set(category, tracker);
        }
        tracker.count += count;
        tracker.totalAllocated += count;
        if (tracker.count > tracker.peakCount) {
            tracker.peakCount = tracker.count;
        }
    }
    /** Track a deallocation / free. */
    trackFree(category, count = 1) {
        const tracker = this.trackers.get(category);
        if (!tracker)
            return;
        tracker.count = Math.max(0, tracker.count - count);
        tracker.totalFreed += count;
    }
    /** Take a snapshot of current allocations. */
    takeSnapshot() {
        let heapUsed = null;
        let heapTotal = null;
        // performance.memory is non-standard (Chrome only)
        const perf = performance;
        if (perf.memory) {
            heapUsed = perf.memory.usedJSHeapSize;
            heapTotal = perf.memory.totalJSHeapSize;
        }
        let totalActive = 0;
        const trackersClone = new Map();
        for (const [k, v] of this.trackers) {
            trackersClone.set(k, { ...v });
            totalActive += v.count;
        }
        const snap = {
            timestamp: performance.now(),
            trackers: trackersClone,
            totalActive,
            heapUsed,
            heapTotal,
        };
        this.snapshots.push(snap);
        if (this.snapshots.length > this.maxSnapshots)
            this.snapshots.shift();
        return snap;
    }
    /** Call from game loop — takes periodic snapshots and checks for leaks. */
    update() {
        const now = performance.now();
        if (now - this.lastCheckTime < this.checkIntervalMs)
            return;
        this.lastCheckTime = now;
        this.takeSnapshot();
        const suspects = this.detectLeaks();
        if (suspects.length > 0) {
            this.events.emit('memory:leak-suspect', suspects);
        }
    }
    /** Analyze snapshots for continuous growth patterns. */
    detectLeaks() {
        if (this.snapshots.length < 10)
            return [];
        const window = 30; // compare last 30 snapshots
        const start = Math.max(0, this.snapshots.length - window);
        const firstSnap = this.snapshots[start];
        const lastSnap = this.snapshots[this.snapshots.length - 1];
        const elapsedSec = (lastSnap.timestamp - firstSnap.timestamp) / 1000;
        if (elapsedSec < 1)
            return [];
        const suspects = [];
        for (const [cat, current] of lastSnap.trackers) {
            const startTracker = firstSnap.trackers.get(cat);
            if (!startTracker)
                continue;
            const growth = current.count - startTracker.count;
            if (growth <= 0)
                continue;
            const growthRate = growth / elapsedSec;
            // Determine severity
            let severity = 'low';
            if (growthRate > 10)
                severity = 'high';
            else if (growthRate > 2)
                severity = 'medium';
            // Only report if there's consistent growth (not just a burst)
            let isConsistent = true;
            let prevCount = startTracker.count;
            for (let i = start + 1; i < this.snapshots.length; i++) {
                const snapTracker = this.snapshots[i].trackers.get(cat);
                if (snapTracker && snapTracker.count < prevCount) {
                    isConsistent = false;
                    break;
                }
                if (snapTracker)
                    prevCount = snapTracker.count;
            }
            if (isConsistent && growth >= 5) {
                suspects.push({
                    category: cat,
                    growthRate,
                    currentCount: current.count,
                    windowCount: startTracker.count,
                    severity,
                });
            }
        }
        return suspects;
    }
    /** Get current tracker for a category. */
    getTracker(category) {
        return this.trackers.get(category);
    }
    /** Get all trackers. */
    get allTrackers() {
        return Array.from(this.trackers.values());
    }
    /** Format a human-readable leak report. */
    formatReport() {
        const suspects = this.detectLeaks();
        if (suspects.length === 0)
            return 'No memory leaks detected.';
        const lines = ['Memory Leak Suspects:', ''];
        for (const s of suspects) {
            const icon = s.severity === 'high' ? '!!!' : s.severity === 'medium' ? '!!' : '!';
            lines.push(`  ${icon} ${s.category}: ${s.currentCount} active (+${s.growthRate.toFixed(1)}/s) [${s.severity}]`);
        }
        return lines.join('\n');
    }
    /** Reset all tracking data. */
    reset() {
        this.trackers.clear();
        this.snapshots.length = 0;
        this.lastCheckTime = 0;
    }
}
const QUALITY_PRESETS = {
    ultra: {
        level: 'ultra',
        renderScale: 1.0,
        drawDistance: 2000,
        shadowQuality: 3,
        particleMultiplier: 1.0,
        postProcessing: true,
        physicsFidelity: 1.0,
    },
    high: {
        level: 'high',
        renderScale: 1.0,
        drawDistance: 1500,
        shadowQuality: 2,
        particleMultiplier: 0.8,
        postProcessing: true,
        physicsFidelity: 1.0,
    },
    medium: {
        level: 'medium',
        renderScale: 0.85,
        drawDistance: 1000,
        shadowQuality: 1,
        particleMultiplier: 0.5,
        postProcessing: false,
        physicsFidelity: 1.0,
    },
    low: {
        level: 'low',
        renderScale: 0.7,
        drawDistance: 600,
        shadowQuality: 0,
        particleMultiplier: 0.25,
        postProcessing: false,
        physicsFidelity: 0.5,
    },
    potato: {
        level: 'potato',
        renderScale: 0.5,
        drawDistance: 300,
        shadowQuality: 0,
        particleMultiplier: 0.1,
        postProcessing: false,
        physicsFidelity: 0.5,
    },
};
const QUALITY_ORDER = ['potato', 'low', 'medium', 'high', 'ultra'];
class AutoLODManager {
    constructor(initialLevel = 'high', config) {
        this.events = new EventBus.EventBus();
        this.fpsHistory = [];
        this.lastChangeTime = 0;
        this.stableAboveSince = 0;
        this.config = {
            targetFps: 60,
            downgradeThreshold: 50,
            upgradeThreshold: 58,
            stabilityWindow: 5,
            cooldownSec: 3,
            ...config,
        };
        this.currentLevel = initialLevel;
        this.currentSettings = { ...QUALITY_PRESETS[initialLevel] };
    }
    get level() { return this.currentLevel; }
    get settings() { return this.currentSettings; }
    /** Call each frame with current FPS. */
    update(currentFps) {
        this.fpsHistory.push(currentFps);
        if (this.fpsHistory.length > 120)
            this.fpsHistory.shift();
        const now = performance.now() / 1000;
        if (now - this.lastChangeTime < this.config.cooldownSec)
            return;
        const avgFps = this.getAverageFps(30); // Last 30 frames
        // Downgrade if below threshold
        if (avgFps < this.config.downgradeThreshold) {
            this.stableAboveSince = 0;
            this.downgrade(now);
            return;
        }
        // Upgrade if consistently above threshold
        if (avgFps >= this.config.upgradeThreshold) {
            if (this.stableAboveSince === 0) {
                this.stableAboveSince = now;
            }
            if (now - this.stableAboveSince >= this.config.stabilityWindow) {
                this.upgrade(now);
                this.stableAboveSince = 0;
            }
        }
        else {
            this.stableAboveSince = 0;
        }
    }
    downgrade(now) {
        const idx = QUALITY_ORDER.indexOf(this.currentLevel);
        if (idx <= 0)
            return; // Already at lowest
        this.setLevel(QUALITY_ORDER[idx - 1], now);
    }
    upgrade(now) {
        const idx = QUALITY_ORDER.indexOf(this.currentLevel);
        if (idx >= QUALITY_ORDER.length - 1)
            return; // Already at highest
        this.setLevel(QUALITY_ORDER[idx + 1], now);
    }
    /** Manually set quality level. */
    setLevel(level, now) {
        if (level === this.currentLevel)
            return;
        const prev = this.currentLevel;
        this.currentLevel = level;
        this.currentSettings = { ...QUALITY_PRESETS[level] };
        this.lastChangeTime = now !== null && now !== void 0 ? now : performance.now() / 1000;
        this.events.emit('lod:changed', { from: prev, to: level, settings: this.currentSettings });
    }
    /** Get a quality preset. */
    getPreset(level) {
        return { ...QUALITY_PRESETS[level] };
    }
    getAverageFps(samples) {
        const start = Math.max(0, this.fpsHistory.length - samples);
        let sum = 0;
        let count = 0;
        for (let i = start; i < this.fpsHistory.length; i++) {
            sum += this.fpsHistory[i];
            count++;
        }
        return count > 0 ? sum / count : 0;
    }
}
/**
 * Abstract worker pool for offloading CPU-heavy tasks.
 * Concrete implementations provide the actual Worker script.
 */
class WorkerPool {
    constructor(workerScriptUrl, config) {
        var _a;
        this.events = new EventBus.EventBus();
        this.workers = [];
        this.busyWorkers = new Set();
        this.taskQueue = [];
        this.activeTasks = new Map();
        this.nextTaskId = 1;
        this.workerScriptUrl = workerScriptUrl;
        const defaultPoolSize = typeof navigator !== 'undefined' ? Math.max(1, ((_a = navigator.hardwareConcurrency) !== null && _a !== void 0 ? _a : 4) - 1) : 2;
        this.config = {
            poolSize: defaultPoolSize,
            maxQueueSize: 100,
            ...config,
        };
    }
    /** Initialize the worker pool. */
    init() {
        for (let i = 0; i < this.config.poolSize; i++) {
            const worker = new Worker(this.workerScriptUrl);
            worker.onmessage = (ev) => this.handleResult(i, ev.data);
            worker.onerror = (ev) => this.handleError(i, ev.message);
            this.workers.push(worker);
        }
    }
    /** Submit a task to the worker pool. Returns a promise resolved when done. */
    submit(type, data) {
        if (this.taskQueue.length >= this.config.maxQueueSize) {
            return Promise.reject(new Error('[WorkerPool] Queue full'));
        }
        const task = {
            id: this.nextTaskId++,
            type,
            data,
            status: 'pending',
            result: null,
            error: null,
            submittedAt: performance.now(),
            completedAt: null,
        };
        return new Promise((resolve, reject) => {
            this.taskQueue.push(task);
            this.activeTasks.set(task.id, {
                task,
                resolve: resolve,
                reject,
            });
            this.dispatchNext();
        });
    }
    dispatchNext() {
        if (this.taskQueue.length === 0)
            return;
        for (let i = 0; i < this.workers.length; i++) {
            if (this.busyWorkers.has(i))
                continue;
            const task = this.taskQueue.shift();
            if (!task)
                return;
            task.status = 'running';
            this.busyWorkers.add(i);
            this.workers[i].postMessage({ taskId: task.id, type: task.type, data: task.data });
            return;
        }
    }
    handleResult(workerIndex, data) {
        this.busyWorkers.delete(workerIndex);
        const entry = this.activeTasks.get(data.taskId);
        if (entry) {
            entry.task.status = 'done';
            entry.task.result = data.result;
            entry.task.completedAt = performance.now();
            this.activeTasks.delete(data.taskId);
            entry.resolve(data.result);
            this.events.emit('worker:complete', { taskId: data.taskId });
        }
        this.dispatchNext();
    }
    handleError(workerIndex, message) {
        this.busyWorkers.delete(workerIndex);
        // Find which task was assigned to this worker (first active task)
        for (const [id, entry] of this.activeTasks) {
            if (entry.task.status === 'running') {
                entry.task.status = 'error';
                entry.task.error = message;
                entry.task.completedAt = performance.now();
                this.activeTasks.delete(id);
                entry.reject(new Error(`[WorkerPool] Task ${id} error: ${message}`));
                break;
            }
        }
        this.dispatchNext();
    }
    /** Number of idle workers. */
    get idleCount() {
        return this.workers.length - this.busyWorkers.size;
    }
    /** Number of queued tasks. */
    get queueSize() {
        return this.taskQueue.length;
    }
    /** Terminate all workers. */
    dispose() {
        for (const w of this.workers)
            w.terminate();
        this.workers.length = 0;
        this.busyWorkers.clear();
        this.taskQueue.length = 0;
        for (const [, entry] of this.activeTasks) {
            entry.reject(new Error('[WorkerPool] Pool disposed'));
        }
        this.activeTasks.clear();
    }
}
/* ══════════════════════════════════════════════════════════════
   Performance Utilities
   ══════════════════════════════════════════════════════════════ */
/** Simple high-resolution timer. */
class PerfTimer {
    constructor() {
        this.marks = new Map();
    }
    mark(name) {
        this.marks.set(name, performance.now());
    }
    measure(startMark, endMark) {
        var _a;
        const start = this.marks.get(startMark);
        if (start === undefined)
            return 0;
        const end = endMark ? ((_a = this.marks.get(endMark)) !== null && _a !== void 0 ? _a : performance.now()) : performance.now();
        return end - start;
    }
    clear() {
        this.marks.clear();
    }
}
/** Object pool for reducing garbage collection pressure. */
class ObjectPool {
    constructor(factory, resetFn, maxSize = 1000) {
        this.pool = [];
        this.factory = factory;
        this.resetFn = resetFn;
        this.maxSize = maxSize;
    }
    /** Get an object from the pool (or create a new one). */
    acquire() {
        if (this.pool.length > 0) {
            return this.pool.pop();
        }
        return this.factory();
    }
    /** Return an object to the pool after use. */
    release(obj) {
        if (this.pool.length >= this.maxSize)
            return;
        this.resetFn(obj);
        this.pool.push(obj);
    }
    /** Pre-populate the pool. */
    prewarm(count) {
        for (let i = 0; i < count; i++) {
            if (this.pool.length >= this.maxSize)
                break;
            this.pool.push(this.factory());
        }
    }
    get available() {
        return this.pool.length;
    }
    clear() {
        this.pool.length = 0;
    }
}

exports.AutoLODManager = AutoLODManager;
exports.FrameBudgetManager = FrameBudgetManager;
exports.MemoryLeakDetector = MemoryLeakDetector;
exports.ObjectPool = ObjectPool;
exports.PerfTimer = PerfTimer;
exports.QUALITY_PRESETS = QUALITY_PRESETS;
exports.WorkerPool = WorkerPool;
//# sourceMappingURL=PerformanceAdvanced.js.map
