import { EventBus } from '../core/EventBus.js';

/* ────────────────────────────────────────────────────────────────
   Developer Tools — LEGENDARY L8
   
   • Hot Module Replacement for game scripts
   • Drag & drop asset pipeline (auto-optimize)
   • One-click deploy (PWA, Electron, Capacitor)
   • Collaborative editing indicators
──────────────────────────────────────────────────────────────── */
class HotModuleReplacer {
    constructor() {
        this.events = new EventBus();
        this.modules = new Map();
        this.acceptCallbacks = new Map();
        this.disposeCallbacks = new Map();
        this.updateHistory = [];
        this.maxHistory = 50;
    }
    /** Register a module with the HMR system. */
    register(id, path, exports$1, dependencies = []) {
        var _a;
        const existing = this.modules.get(id);
        const mod = {
            id,
            path,
            state: 'loaded',
            version: existing ? existing.version + 1 : 1,
            exports: exports$1,
            lastUpdated: Date.now(),
            dependencies,
            dependents: (_a = existing === null || existing === void 0 ? void 0 : existing.dependents) !== null && _a !== void 0 ? _a : [],
        };
        this.modules.set(id, mod);
        // Update dependency graph
        for (const depId of dependencies) {
            const dep = this.modules.get(depId);
            if (dep && !dep.dependents.includes(id)) {
                dep.dependents.push(id);
            }
        }
    }
    /** Register an accept handler — called when the module is hot-replaced. */
    accept(moduleId, callback) {
        let callbacks = this.acceptCallbacks.get(moduleId);
        if (!callbacks) {
            callbacks = [];
            this.acceptCallbacks.set(moduleId, callbacks);
        }
        callbacks.push(callback);
    }
    /** Register a dispose handler — called before the module is replaced. */
    dispose(moduleId, callback) {
        let callbacks = this.disposeCallbacks.get(moduleId);
        if (!callbacks) {
            callbacks = [];
            this.disposeCallbacks.set(moduleId, callbacks);
        }
        callbacks.push(callback);
    }
    /**
     * Hot-replace a module with new exports.
     * Calls dispose callbacks, updates the module, then calls accept callbacks.
     */
    update(moduleId, newExports) {
        const mod = this.modules.get(moduleId);
        if (!mod)
            return false;
        const previousVersion = mod.version;
        const preservedState = {};
        // 1. Call dispose callbacks (allow state preservation)
        mod.state = 'updating';
        const disposeCallbacks = this.disposeCallbacks.get(moduleId);
        if (disposeCallbacks) {
            for (const cb of disposeCallbacks) {
                try {
                    cb(preservedState);
                }
                catch (e) {
                    console.warn(`[HMR] Dispose callback error for ${moduleId}:`, e);
                }
            }
        }
        // 2. Update module
        mod.exports = newExports;
        mod.version++;
        mod.lastUpdated = Date.now();
        mod.state = 'loaded';
        const hmrUpdate = {
            moduleId,
            previousVersion,
            newVersion: mod.version,
            preservedState: Object.keys(preservedState).length > 0 ? preservedState : undefined,
            timestamp: Date.now(),
        };
        this.updateHistory.push(hmrUpdate);
        if (this.updateHistory.length > this.maxHistory)
            this.updateHistory.shift();
        // 3. Call accept callbacks
        const acceptCallbacks = this.acceptCallbacks.get(moduleId);
        if (acceptCallbacks) {
            for (const cb of acceptCallbacks) {
                try {
                    cb(newExports);
                }
                catch (e) {
                    mod.state = 'error';
                    console.warn(`[HMR] Accept callback error for ${moduleId}:`, e);
                    this.events.emit('hmr:error', { moduleId, error: String(e) });
                    return false;
                }
            }
        }
        // 4. Propagate to dependents
        for (const depId of mod.dependents) {
            this.events.emit('hmr:propagate', { from: moduleId, to: depId });
        }
        this.events.emit('hmr:updated', hmrUpdate);
        return true;
    }
    /** Get a module by ID. */
    getModule(id) {
        return this.modules.get(id);
    }
    /** Get all registered modules. */
    get allModules() {
        return Array.from(this.modules.values());
    }
    /** Get update history. */
    get history() {
        return this.updateHistory;
    }
    /** Remove a module from HMR tracking. */
    unregister(moduleId) {
        const mod = this.modules.get(moduleId);
        if (!mod)
            return;
        // Clean up dependency graph
        for (const depId of mod.dependencies) {
            const dep = this.modules.get(depId);
            if (dep) {
                dep.dependents = dep.dependents.filter(d => d !== moduleId);
            }
        }
        this.modules.delete(moduleId);
        this.acceptCallbacks.delete(moduleId);
        this.disposeCallbacks.delete(moduleId);
    }
}
const DEFAULT_PROCESSOR_OPTIONS = {
    maxImageDimension: 2048,
    imageQuality: 0.85,
    preferredImageFormat: 'webp',
    generateThumbnails: true,
    thumbnailSize: 128,
};
const MIME_TO_TYPE = {
    'image/png': 'image',
    'image/jpeg': 'image',
    'image/webp': 'image',
    'image/gif': 'image',
    'image/svg+xml': 'image',
    'audio/mpeg': 'audio',
    'audio/ogg': 'audio',
    'audio/wav': 'audio',
    'audio/webm': 'audio',
    'application/json': 'json',
    'font/ttf': 'font',
    'font/otf': 'font',
    'font/woff': 'font',
    'font/woff2': 'font',
    'text/javascript': 'script',
    'application/javascript': 'script',
};
class AssetPipeline {
    constructor(options) {
        this.events = new EventBus();
        this.assets = new Map();
        this.nextId = 1;
        this.options = { ...DEFAULT_PROCESSOR_OPTIONS, ...options };
    }
    /**
     * Import a file (e.g., from drag & drop or file input).
     * Processes and optimizes the asset based on its type.
     */
    async importFile(file) {
        const type = this.detectType(file);
        const id = `asset_${this.nextId++}`;
        const url = await this.readFileAsDataURL(file);
        const meta = {
            id,
            name: this.sanitizeFilename(file.name),
            type,
            mimeType: file.type,
            sizeBytes: file.size,
            originalName: file.name,
            url,
            importedAt: Date.now(),
            tags: [],
        };
        // Process based on type
        if (type === 'image') {
            const dims = await this.getImageDimensions(url);
            meta.width = dims.width;
            meta.height = dims.height;
            // Resize if needed
            if (dims.width > this.options.maxImageDimension || dims.height > this.options.maxImageDimension) {
                const resized = await this.resizeImage(url, this.options.maxImageDimension);
                meta.url = resized.url;
                meta.width = resized.width;
                meta.height = resized.height;
                meta.sizeBytes = resized.sizeBytes;
            }
        }
        this.assets.set(id, meta);
        this.events.emit('asset:imported', meta);
        return meta;
    }
    /** Import multiple files at once. */
    async importFiles(files) {
        const results = [];
        for (let i = 0; i < files.length; i++) {
            const meta = await this.importFile(files[i]);
            results.push(meta);
        }
        return results;
    }
    /** Setup drag & drop on an HTML element. */
    setupDropZone(element) {
        const onDragOver = (e) => {
            e.preventDefault();
            e.stopPropagation();
            element.classList.add('n2d-asset-dragover');
        };
        const onDragLeave = (e) => {
            e.preventDefault();
            element.classList.remove('n2d-asset-dragover');
        };
        const onDrop = (e) => {
            var _a;
            e.preventDefault();
            e.stopPropagation();
            element.classList.remove('n2d-asset-dragover');
            if (((_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files) && e.dataTransfer.files.length > 0) {
                this.importFiles(e.dataTransfer.files);
            }
        };
        element.addEventListener('dragover', onDragOver);
        element.addEventListener('dragleave', onDragLeave);
        element.addEventListener('drop', onDrop);
        // Return cleanup function
        return () => {
            element.removeEventListener('dragover', onDragOver);
            element.removeEventListener('dragleave', onDragLeave);
            element.removeEventListener('drop', onDrop);
        };
    }
    /** Get an asset by ID. */
    getAsset(id) {
        return this.assets.get(id);
    }
    /** Get all assets. */
    getAllAssets() {
        return Array.from(this.assets.values());
    }
    /** Get assets by type. */
    getAssetsByType(type) {
        const result = [];
        for (const a of this.assets.values()) {
            if (a.type === type)
                result.push(a);
        }
        return result;
    }
    /** Remove an asset. */
    removeAsset(id) {
        const asset = this.assets.get(id);
        if (!asset)
            return;
        // Revoke blob URL if applicable
        if (asset.url.startsWith('blob:')) {
            URL.revokeObjectURL(asset.url);
        }
        this.assets.delete(id);
        this.events.emit('asset:removed', { id });
    }
    /** Tag an asset. */
    tagAsset(id, tag) {
        const asset = this.assets.get(id);
        if (asset && !asset.tags.includes(tag)) {
            asset.tags.push(tag);
        }
    }
    /* ── Internal Helpers ───────────────────────────────────────── */
    detectType(file) {
        var _a, _b, _c;
        const fromMime = MIME_TO_TYPE[file.type];
        if (fromMime)
            return fromMime;
        // Fallback: check extension
        const ext = (_b = (_a = file.name.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
        const extMap = {
            png: 'image', jpg: 'image', jpeg: 'image', webp: 'image', gif: 'image', svg: 'image',
            mp3: 'audio', ogg: 'audio', wav: 'audio', webm: 'audio',
            json: 'json', tmx: 'tilemap', tmj: 'tilemap',
            js: 'script', ts: 'script',
            glsl: 'shader', frag: 'shader', vert: 'shader',
            glb: 'model', gltf: 'model', obj: 'model', fbx: 'model',
            ttf: 'font', otf: 'font', woff: 'font', woff2: 'font',
        };
        return (_c = extMap[ext]) !== null && _c !== void 0 ? _c : 'unknown';
    }
    sanitizeFilename(name) {
        return name.replace(/[^a-zA-Z0-9._-]/g, '_');
    }
    readFileAsDataURL(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = () => resolve(reader.result);
            reader.onerror = () => reject(new Error('Failed to read file'));
            reader.readAsDataURL(file);
        });
    }
    getImageDimensions(url) {
        return new Promise((resolve) => {
            const img = new Image();
            img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
            img.onerror = () => resolve({ width: 0, height: 0 });
            img.src = url;
        });
    }
    resizeImage(url, maxDim) {
        return new Promise((resolve) => {
            const img = new Image();
            img.onload = () => {
                var _a;
                let w = img.naturalWidth;
                let h = img.naturalHeight;
                const ratio = Math.min(maxDim / w, maxDim / h);
                if (ratio < 1) {
                    w = Math.round(w * ratio);
                    h = Math.round(h * ratio);
                }
                const canvas = document.createElement('canvas');
                canvas.width = w;
                canvas.height = h;
                const ctx = canvas.getContext('2d');
                if (ctx)
                    ctx.drawImage(img, 0, 0, w, h);
                const mimeMap = {
                    webp: 'image/webp',
                    jpeg: 'image/jpeg',
                    png: 'image/png',
                };
                const mime = (_a = mimeMap[this.options.preferredImageFormat]) !== null && _a !== void 0 ? _a : 'image/webp';
                const dataUrl = canvas.toDataURL(mime, this.options.imageQuality);
                // Approximate byte size from data URL
                const sizeBytes = Math.round(dataUrl.length * 0.75);
                resolve({ url: dataUrl, width: w, height: h, sizeBytes });
            };
            img.onerror = () => resolve({ url, width: 0, height: 0, sizeBytes: 0 });
            img.src = url;
        });
    }
}
class DeployManager {
    constructor() {
        this.events = new EventBus();
    }
    /** Generate PWA manifest JSON. */
    generatePWAManifest(config) {
        var _a, _b, _c;
        return JSON.stringify({
            name: config.appName,
            short_name: config.appName.slice(0, 12),
            description: config.description,
            start_url: (_a = config.startUrl) !== null && _a !== void 0 ? _a : '/index.html',
            display: 'standalone',
            orientation: 'landscape',
            theme_color: (_b = config.themeColor) !== null && _b !== void 0 ? _b : '#1e1e2e',
            background_color: (_c = config.backgroundColor) !== null && _c !== void 0 ? _c : '#000000',
            icons: config.iconPath ? [
                { src: config.iconPath, sizes: '192x192', type: 'image/png' },
                { src: config.iconPath, sizes: '512x512', type: 'image/png' },
            ] : [],
        }, null, 2);
    }
    /** Generate service worker registration code. */
    generateServiceWorkerScript() {
        return `// Service Worker — auto-generated by nice2dev deploy
const CACHE_NAME = 'nice2dev-game-v1';
const ASSETS = ['/index.html', '/main.js', '/style.css'];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => cached || fetch(event.request))
  );
});
`;
    }
    /** Generate Electron main process entry. */
    generateElectronMain(config) {
        var _a, _b;
        const w = (_a = config.windowWidth) !== null && _a !== void 0 ? _a : 1280;
        const h = (_b = config.windowHeight) !== null && _b !== void 0 ? _b : 720;
        return `// Electron main — auto-generated by nice2dev deploy
const { app, BrowserWindow } = require('electron');
const path = require('path');

function createWindow() {
  const win = new BrowserWindow({
    width: ${w},
    height: ${h},
    title: ${JSON.stringify(config.appName)},
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
    },
  });
  win.loadFile(path.join(__dirname, 'index.html'));
}

app.whenReady().then(createWindow);
app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });
`;
    }
    /** Generate Capacitor config. */
    generateCapacitorConfig(config) {
        var _a;
        return JSON.stringify({
            appId: (_a = config.bundleId) !== null && _a !== void 0 ? _a : `com.nice2dev.${config.appName.toLowerCase().replace(/\s+/g, '')}`,
            appName: config.appName,
            webDir: 'dist',
            bundledWebRuntime: false,
            server: {
                androidScheme: 'https',
            },
        }, null, 2);
    }
    /** Generate Dockerfile for containerized deployment. */
    generateDockerfile() {
        return `# Docker — auto-generated by nice2dev deploy
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
`;
    }
    /** Generate deployment package.json scripts. */
    generateDeployScripts(config) {
        const scripts = {
            build: 'vite build',
            preview: 'vite preview',
        };
        switch (config.target) {
            case 'pwa':
                scripts['build:pwa'] = 'vite build && npx workbox generateSW';
                break;
            case 'electron':
                scripts['build:electron'] = 'vite build && electron-builder';
                scripts['dev:electron'] = 'concurrently "vite" "electron ."';
                break;
            case 'capacitor':
                scripts['build:mobile'] = 'vite build && npx cap sync';
                scripts['run:android'] = 'npx cap run android';
                scripts['run:ios'] = 'npx cap run ios';
                break;
            case 'docker':
                scripts['build:docker'] = 'vite build && docker build -t nice2dev-game .';
                scripts['run:docker'] = 'docker run -p 8080:80 nice2dev-game';
                break;
        }
        return scripts;
    }
    /** Estimate output size based on input assets. */
    estimateOutputSize(assets, minify) {
        let total = 0;
        for (const a of assets)
            total += a.sizeBytes;
        // Rough estimate: minification saves ~30%
        return minify ? Math.round(total * 0.7) : total;
    }
}
const COLLABORATOR_COLORS = [
    '#4fc3f7', '#f06292', '#aed581', '#ffb74d',
    '#ba68c8', '#4db6ac', '#ff8a65', '#90a4ae',
];
class CollaborationManager {
    constructor(localPeerId) {
        this.events = new EventBus();
        this.cursors = new Map();
        this.colorIndex = 0;
        this.staleTimeoutMs = 30000; // 30 seconds
        this.localPeerId = localPeerId;
    }
    /** Update a remote cursor position. */
    updateCursor(peerId, data) {
        if (peerId === this.localPeerId)
            return;
        let cursor = this.cursors.get(peerId);
        if (!cursor) {
            cursor = {
                ...data,
                color: COLLABORATOR_COLORS[this.colorIndex % COLLABORATOR_COLORS.length],
                lastSeen: Date.now(),
            };
            this.colorIndex++;
        }
        else {
            cursor.position = data.position;
            cursor.filePath = data.filePath;
            cursor.displayName = data.displayName;
            cursor.selection = data.selection;
            cursor.lastSeen = Date.now();
        }
        this.cursors.set(peerId, cursor);
        this.events.emit('collab:cursor-moved', cursor);
    }
    /** Remove a collaborator. */
    removeCursor(peerId) {
        this.cursors.delete(peerId);
        this.events.emit('collab:cursor-removed', { peerId });
    }
    /** Get all active cursors (excluding stale ones). */
    getActiveCursors() {
        const now = Date.now();
        const active = [];
        for (const [id, cursor] of this.cursors) {
            if (now - cursor.lastSeen < this.staleTimeoutMs) {
                active.push(cursor);
            }
            else {
                this.cursors.delete(id);
            }
        }
        return active;
    }
    /** Get cursors in a specific file. */
    getCursorsInFile(filePath) {
        return this.getActiveCursors().filter(c => c.filePath === filePath);
    }
    /** Number of active collaborators. */
    get activeCount() {
        return this.getActiveCursors().length;
    }
}

export { AssetPipeline, CollaborationManager, DeployManager, HotModuleReplacer };
//# sourceMappingURL=DevTools.js.map
