"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PackageManager = void 0;
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const genesis_1 = require("@edenapp/genesis");
const ipc_1 = require("../ipc");
const PackageHandler_1 = require("./PackageHandler");
const tsyringe_1 = require("tsyringe");
let PackageManager = class PackageManager extends ipc_1.EdenEmitter {
    appsDirectory;
    prebuiltAppsDirectory;
    installedApps = new Map();
    packageHandler;
    permissionRegistry;
    constructor(ipcBridge, appsDirectory, distPath, commandRegistry, permissionRegistry) {
        super(ipcBridge);
        this.appsDirectory = appsDirectory;
        this.prebuiltAppsDirectory = path.join(distPath, "apps", "prebuilt");
        this.permissionRegistry = permissionRegistry;
        // Create and register handler
        this.packageHandler = new PackageHandler_1.PackageHandler(this);
        commandRegistry.registerManager(this.packageHandler);
    }
    /**
     * Initialize the package manager
     */
    async initialize() {
        // Ensure apps directory exists
        await fs.mkdir(this.appsDirectory, { recursive: true });
        // Load prebuilt apps first (system apps)
        await this.loadPrebuiltApps();
        // Load installed apps
        await this.loadInstalledApps();
        const prebuiltCount = Array.from(this.installedApps.values()).filter((app) => app.isPrebuilt).length;
        const installedCount = this.installedApps.size - prebuiltCount;
        console.log(`PackageManager initialized. Found ${prebuiltCount} prebuilt apps and ${installedCount} installed apps.`);
    }
    /**
     * Load prebuilt apps from dist/apps/prebuilt
     */
    async loadPrebuiltApps() {
        try {
            // Check if prebuilt directory exists
            try {
                await fs.access(this.prebuiltAppsDirectory);
            }
            catch {
                console.log("No prebuilt apps directory found, skipping...");
                return;
            }
            const entries = await fs.readdir(this.prebuiltAppsDirectory, {
                withFileTypes: true,
            });
            for (const entry of entries) {
                if (entry.isDirectory()) {
                    try {
                        const manifestPath = path.join(this.prebuiltAppsDirectory, entry.name, "manifest.json");
                        const manifestContent = await fs.readFile(manifestPath, "utf-8");
                        const manifest = JSON.parse(manifestContent);
                        // Mark as prebuilt
                        manifest.isPrebuilt = true;
                        // Check for dev manifest (dev server running)
                        // Look in source apps directory, not dist
                        const devManifestPath = path.join(__dirname, "../../../apps", // Go up to project root, then into apps
                        ...entry.name.split("."), ".dev-manifest.json");
                        try {
                            const devManifestContent = await fs.readFile(devManifestPath, "utf-8");
                            const devManifest = JSON.parse(devManifestContent);
                            if (devManifest.devMode &&
                                devManifest.devUrl &&
                                manifest.frontend) {
                                // Override frontend entry with dev server URL
                                manifest.frontend.entry = devManifest.devUrl;
                                console.log(`Loaded prebuilt app: ${manifest.name} (dev mode: ${devManifest.devUrl})`);
                            }
                            else {
                                console.log(`Loaded prebuilt app: ${manifest.name}`);
                            }
                        }
                        catch {
                            // No dev manifest, use production build
                            console.log(`Loaded prebuilt app: ${manifest.name}`);
                        }
                        this.installedApps.set(manifest.id, manifest);
                        // Register app permissions
                        if (manifest.permissions && manifest.permissions.length > 0) {
                            this.permissionRegistry.registerApp(manifest.id, manifest.permissions);
                        }
                    }
                    catch (error) {
                        console.warn(`Failed to load prebuilt app from ${entry.name}:`, error);
                    }
                }
            }
        }
        catch (error) {
            console.error("Failed to load prebuilt apps:", error);
        }
    }
    /**
     * Load all installed apps from disk
     */
    async loadInstalledApps() {
        try {
            const entries = await fs.readdir(this.appsDirectory, {
                withFileTypes: true,
            });
            for (const entry of entries) {
                if (entry.isDirectory()) {
                    try {
                        const manifestPath = path.join(this.appsDirectory, entry.name, "manifest.json");
                        const manifestContent = await fs.readFile(manifestPath, "utf-8");
                        const manifest = JSON.parse(manifestContent);
                        this.installedApps.set(manifest.id, manifest);
                        // Register app permissions
                        if (manifest.permissions && manifest.permissions.length > 0) {
                            this.permissionRegistry.registerApp(manifest.id, manifest.permissions);
                        }
                    }
                    catch (error) {
                        console.warn(`Failed to load app from ${entry.name}:`, error);
                    }
                }
            }
        }
        catch (error) {
            console.error("Failed to load installed apps:", error);
        }
    }
    /**
     * Install an app from a .edenite file
     */
    async installApp(edenitePath) {
        // Check if file exists
        try {
            await fs.access(edenitePath);
        }
        catch {
            throw new Error(`File not found: ${edenitePath}`);
        }
        // Check if it's a .edenite file
        if (!edenitePath.endsWith(".edenite")) {
            throw new Error("Invalid file format. Please select a .edenite file.\n" +
                "You can create .edenite files using the genesis bundler:\n" +
                "  npm install -g @edenapp/genesis\n" +
                "  genesis build <app-directory>");
        }
        // Get info from the archive first (validates format and reads manifest)
        const info = await genesis_1.GenesisBundler.getInfo(edenitePath);
        if (!info.success || !info.manifest) {
            throw new Error(info.error || "Invalid .edenite file: could not read manifest");
        }
        const manifest = info.manifest;
        // Validate manifest
        this.validateManifest(manifest);
        // Check for reserved app IDs
        if (manifest.id === "com.eden") {
            throw new Error(`App ID "${manifest.id}" is reserved for Eden system use.\n` +
                `Please choose a different app ID.`);
        }
        // Check if already installed
        if (this.installedApps.has(manifest.id)) {
            throw new Error(`App ${manifest.id} is already installed.\n` +
                `Please uninstall the existing version first.`);
        }
        // Extract to apps directory using genesis
        const targetPath = path.join(this.appsDirectory, manifest.id);
        const result = await genesis_1.GenesisBundler.extract({
            edenitePath,
            outputDirectory: targetPath,
            verbose: false,
            verifyChecksum: true,
        });
        if (!result.success) {
            throw new Error(result.error || "Failed to extract .edenite file");
        }
        // Register app and its permissions
        this.installedApps.set(manifest.id, manifest);
        if (manifest.permissions && manifest.permissions.length > 0) {
            this.permissionRegistry.registerApp(manifest.id, manifest.permissions);
        }
        this.notify("installed", { manifest });
        return manifest;
    }
    /**
     * Uninstall an app
     */
    async uninstallApp(appId) {
        const manifest = this.installedApps.get(appId);
        if (!manifest) {
            return false;
        }
        // Prevent uninstalling prebuilt apps
        if (manifest.isPrebuilt) {
            throw new Error(`Cannot uninstall ${manifest.name}: this is a system app.`);
        }
        // Note: Stopping the app is the responsibility of ProcessManager.
        // The handler should coordinate this, or we assume it's stopped.
        // For safety, we might want to emit an event "request-app-stop" or similar?
        // Or just let the caller handle it.
        // The user said "packagemanager (install/uninstallation)".
        // I will assume the caller (PackageHandler or Eden) ensures the app is stopped.
        // Remove from disk
        const appPath = path.join(this.appsDirectory, appId);
        await fs.rm(appPath, { recursive: true, force: true });
        // Unregister
        this.installedApps.delete(appId);
        this.notify("uninstalled", { appId });
        return true;
    }
    /**
     * Get list of installed apps
     * @param showHidden - If true, includes overlay apps and daemons (hidden by default)
     */
    getInstalledApps(showHidden = false) {
        const apps = Array.from(this.installedApps.values());
        return showHidden
            ? apps
            : apps.filter((app) => !app.overlay && !!app.frontend?.entry);
    }
    /**
     * Get manifest for an app
     */
    getAppManifest(appId) {
        return this.installedApps.get(appId);
    }
    /**
     * Validate app manifest
     */
    validateManifest(manifest) {
        if (!manifest.id || !manifest.name || !manifest.version) {
            throw new Error("Invalid manifest: missing required fields (id, name, version)");
        }
        // At least one of frontend or backend must be defined
        if (!manifest.frontend?.entry && !manifest.backend?.entry) {
            throw new Error("Invalid manifest: must have at least frontend.entry or backend.entry");
        }
        if (manifest.backend && !manifest.backend.entry) {
            throw new Error("Invalid manifest: backend.entry must be specified when backend is defined");
        }
    }
    /**
     * Get the installation path for an app
     */
    getAppPath(appId) {
        const manifest = this.installedApps.get(appId);
        if (!manifest) {
            return undefined;
        }
        // Return the appropriate path based on whether it's prebuilt
        if (manifest.isPrebuilt) {
            return path.join(this.prebuiltAppsDirectory, appId);
        }
        else {
            return path.join(this.appsDirectory, appId);
        }
    }
    /**
     * Reload an app (for hot reload support)
     * This will notify that the app has been updated
     */
    async reloadApp(appId) {
        const manifest = this.installedApps.get(appId);
        if (!manifest) {
            throw new Error(`App ${appId} not found`);
        }
        // Reload the manifest from disk
        const appPath = this.getAppPath(appId);
        if (!appPath) {
            throw new Error(`App path not found for ${appId}`);
        }
        const manifestPath = path.join(appPath, "manifest.json");
        const manifestContent = await fs.readFile(manifestPath, "utf-8");
        const updatedManifest = JSON.parse(manifestContent);
        // Preserve prebuilt flag
        if (manifest.isPrebuilt) {
            updatedManifest.isPrebuilt = true;
        }
        // Update in-memory manifest
        this.installedApps.set(appId, updatedManifest);
        // Notify about the reload (ProcessManager should handle restarting)
        this.notify("installed", { manifest: updatedManifest });
    }
    /**
     * Get the app icon as a base64 data URL
     */
    async getAppIcon(appId) {
        const manifest = this.installedApps.get(appId);
        if (!manifest?.icon) {
            return undefined;
        }
        const appPath = this.getAppPath(appId);
        if (!appPath) {
            return undefined;
        }
        const iconPath = path.join(appPath, manifest.icon);
        try {
            const iconBuffer = await fs.readFile(iconPath);
            const ext = path.extname(manifest.icon).toLowerCase();
            const mimeType = this.getMimeType(ext);
            return `data:${mimeType};base64,${iconBuffer.toString("base64")}`;
        }
        catch (error) {
            console.warn(`Failed to read icon for ${appId}:`, error);
            return undefined;
        }
    }
    /**
     * Get MIME type for an image file extension
     */
    getMimeType(ext) {
        const mimeTypes = {
            ".png": "image/png",
            ".jpg": "image/jpeg",
            ".jpeg": "image/jpeg",
            ".svg": "image/svg+xml",
            ".gif": "image/gif",
            ".webp": "image/webp",
            ".ico": "image/x-icon",
        };
        return mimeTypes[ext] || "application/octet-stream";
    }
};
exports.PackageManager = PackageManager;
exports.PackageManager = PackageManager = __decorate([
    (0, tsyringe_1.singleton)(),
    (0, tsyringe_1.injectable)(),
    (0, ipc_1.EdenNamespace)("package"),
    __param(0, (0, tsyringe_1.inject)(ipc_1.IPCBridge)),
    __param(1, (0, tsyringe_1.inject)("appsDirectory")),
    __param(2, (0, tsyringe_1.inject)("distPath")),
    __param(3, (0, tsyringe_1.inject)(ipc_1.CommandRegistry)),
    __param(4, (0, tsyringe_1.inject)(ipc_1.PermissionRegistry)),
    __metadata("design:paramtypes", [ipc_1.IPCBridge, String, String, ipc_1.CommandRegistry,
        ipc_1.PermissionRegistry])
], PackageManager);
//# sourceMappingURL=PackageManager.js.map