"use strict";
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-require-imports */
/**
 * WASM Plugin Registration and Management
 *
 * Manages the global plugin registry and handles plugin registration.
 * Maintains the plugin map and provides lookup functions.
 */
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 __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.restartTimers = exports.wasmPlugins = void 0;
exports.initializeLifecycleFunctions = initializeLifecycleFunctions;
exports.setPluginStatus = setPluginStatus;
exports.registerWasmPlugin = registerWasmPlugin;
exports.getAllWasmPlugins = getAllWasmPlugins;
exports.getWasmPlugin = getWasmPlugin;
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
const debug_1 = __importDefault(require("debug"));
const express_1 = __importDefault(require("express"));
const wasm_runtime_1 = require("../wasm-runtime");
const wasm_storage_1 = require("../wasm-storage");
const plugin_routes_1 = require("./plugin-routes");
const resource_provider_1 = require("../bindings/resource-provider");
const weather_provider_1 = require("../bindings/weather-provider");
const radar_provider_1 = require("../bindings/radar-provider");
const pluginid_1 = require("../../pluginid");
const debug = (0, debug_1.default)('signalk:wasm:loader');
// Global plugin registry
exports.wasmPlugins = new Map();
// Crash recovery timers
exports.restartTimers = new Map();
// Forward declarations for circular dependency resolution
let _startWasmPlugin;
let _updateWasmPluginConfig;
let _unloadWasmPlugin;
let _stopWasmPlugin;
/**
 * Initialize lifecycle function references (called from index.ts to resolve circular dependencies)
 */
function initializeLifecycleFunctions(startWasmPlugin, updateWasmPluginConfig, unloadWasmPlugin, stopWasmPlugin) {
    _startWasmPlugin = startWasmPlugin;
    _updateWasmPluginConfig = updateWasmPluginConfig;
    _unloadWasmPlugin = unloadWasmPlugin;
    _stopWasmPlugin = stopWasmPlugin;
}
/**
 * Helper to update plugin status and sync state property
 */
function setPluginStatus(plugin, status) {
    plugin.status = status;
    plugin.state = status;
}
/**
 * Add Node.js plugin compatibility properties to WASM plugin
 * This allows WASM plugins to be used interchangeably with Node.js plugins
 */
function addNodejsPluginCompat(plugin, pluginId) {
    // Add 'started' getter for Node.js plugin compatibility
    Object.defineProperty(plugin, 'started', {
        get() {
            return this.status === 'running';
        },
        enumerable: true,
        configurable: true
    });
    plugin.stop = async function () {
        if (_stopWasmPlugin) {
            await _stopWasmPlugin(pluginId);
        }
    };
}
/**
 * Mount webapp static files and register with app.webapps for WASM plugins
 * that have the signalk-webapp keyword
 */
function mountWasmWebapp(app, packageName, packageJson, location) {
    const keywords = packageJson.keywords || [];
    // Check if this is a webapp
    if (!keywords.includes('signalk-webapp')) {
        return;
    }
    // Find public folder
    const packagePath = path.join(location, packageName);
    let webappPath = packagePath;
    if (fs.existsSync(path.join(packagePath, 'public'))) {
        webappPath = path.join(packagePath, 'public');
    }
    // Mount static files
    debug(`Mounting WASM webapp /${packageName}: ${webappPath}`);
    app.use('/' + packageName, express_1.default.static(webappPath));
    // Create webapp metadata for admin UI
    const webappMetadata = {
        name: packageName,
        version: packageJson.version,
        description: packageJson.description || '',
        keywords: keywords,
        signalk: packageJson.signalk || {}
    };
    // Register with app.webapps
    if (!app.webapps) {
        app.webapps = [];
    }
    // Avoid duplicates
    if (!app.webapps.find((w) => w.name === packageName)) {
        app.webapps.push(webappMetadata);
        debug(`Registered WASM webapp: ${packageName}`);
    }
    // Also register as embeddable webapp if it has that keyword
    if (keywords.includes('signalk-embeddable-webapp')) {
        if (!app.embeddablewebapps) {
            app.embeddablewebapps = [];
        }
        if (!app.embeddablewebapps.find((w) => w.name === packageName)) {
            app.embeddablewebapps.push(webappMetadata);
            debug(`Registered WASM embeddable webapp: ${packageName}`);
        }
    }
}
/**
 * Register a WASM plugin from package metadata
 */
async function registerWasmPlugin(app, packageName, metadata, location, configPath) {
    debug(`Registering WASM plugin: ${packageName} from ${location}`);
    try {
        // Read package.json to get WASM metadata
        const packageJson = require(path.join(location, packageName, 'package.json'));
        if (!packageJson.wasmManifest) {
            throw new Error('Missing wasmManifest in package.json');
        }
        const wasmPath = path.join(location, packageName, packageJson.wasmManifest);
        // Mount webapp static files if this is a signalk-webapp
        mountWasmWebapp(app, packageName, packageJson, location);
        const capabilities = {
            network: packageJson.wasmCapabilities?.network || false,
            storage: packageJson.wasmCapabilities?.storage || 'vfs-only',
            dataRead: packageJson.wasmCapabilities?.dataRead !== false, // default true
            dataWrite: packageJson.wasmCapabilities?.dataWrite !== false, // default true
            serialPorts: packageJson.wasmCapabilities?.serialPorts || false,
            putHandlers: packageJson.wasmCapabilities?.putHandlers || false,
            httpEndpoints: packageJson.wasmCapabilities?.httpEndpoints || false,
            resourceProvider: packageJson.wasmCapabilities?.resourceProvider || false,
            weatherProvider: packageJson.wasmCapabilities?.weatherProvider || false,
            radarProvider: packageJson.wasmCapabilities?.radarProvider || false,
            rawSockets: packageJson.wasmCapabilities?.rawSockets || false
        };
        // Load WASM module temporarily to extract schema and display name
        const tempVfsRoot = path.join(configPath, 'plugin-config-data', '.temp-' + (0, pluginid_1.derivePluginId)(packageName));
        if (!fs.existsSync(tempVfsRoot)) {
            fs.mkdirSync(tempVfsRoot, { recursive: true });
        }
        const runtime = (0, wasm_runtime_1.getWasmRuntime)();
        const tempInstance = await runtime.loadPlugin(packageName, wasmPath, tempVfsRoot, capabilities, app);
        // Derive plugin ID from npm package name (not from WASM exports)
        // This ensures uniqueness via npm registry and prevents ID conflicts
        const pluginId = (0, pluginid_1.derivePluginId)(packageName);
        // Plugin display name from WASM exports, fallback to package name
        const pluginName = tempInstance.exports.name?.() || packageName;
        const schemaJson = tempInstance.exports.schema();
        const schema = schemaJson ? JSON.parse(schemaJson) : {};
        // Update the plugin instance reference for providers
        (0, resource_provider_1.updateResourceProviderInstance)(pluginId, tempInstance);
        (0, weather_provider_1.updateWeatherProviderInstance)(pluginId, tempInstance);
        (0, radar_provider_1.updateRadarProviderInstance)(pluginId, tempInstance);
        // Now check config using the REAL plugin ID
        const storagePaths = (0, wasm_storage_1.getPluginStoragePaths)(configPath, pluginId, packageName);
        const savedConfig = (0, wasm_storage_1.readPluginConfig)(storagePaths.configFile);
        // If plugin is disabled, create minimal plugin object and return early
        if (savedConfig.enabled === false) {
            debug(`Plugin ${packageName} is disabled, schema already extracted from WASM`);
            // Do NOT write config file here - UI shows "Configure" button when no config file exists
            // Config file will be created when user actually configures the plugin
            // Create a minimal plugin object without keeping WASM loaded
            const plugin = {
                id: pluginId,
                name: pluginName,
                type: 'wasm',
                packageName,
                version: metadata.version || packageJson.version,
                enabled: false,
                enableDebug: savedConfig.enableDebug || false,
                keywords: packageJson.keywords || [],
                packageLocation: location,
                configPath,
                metadata: {
                    id: pluginId,
                    name: pluginName,
                    packageName,
                    version: metadata.version || packageJson.version,
                    wasmManifest: packageJson.wasmManifest,
                    capabilities,
                    packageLocation: location
                },
                instance: null, // Instance was destroyed
                status: 'stopped',
                schema, // Schema extracted from temp load
                configuration: savedConfig.configuration, // Keep undefined/null for UI "Configure" button logic
                crashCount: 0,
                restartBackoff: 1000,
                description: packageJson.description || '',
                state: 'stopped',
                format: tempInstance.format // Preserve format from temp instance
            };
            // Add Node.js plugin compatibility properties
            addNodejsPluginCompat(plugin, pluginId);
            // Register minimal plugin in global map
            exports.wasmPlugins.set(pluginId, plugin);
            // Add to app.plugins array
            if (app.plugins) {
                app.plugins.push(plugin);
            }
            // Add to app.pluginsMap
            if (app.pluginsMap) {
                app.pluginsMap[pluginId] = plugin;
            }
            // Set up basic REST API routes even though plugin is disabled
            // This allows Plugin Config UI to read/write config and enable the plugin
            (0, plugin_routes_1.setupWasmPluginRoutes)(app, plugin, configPath, _updateWasmPluginConfig, _startWasmPlugin, _unloadWasmPlugin, _stopWasmPlugin);
            debug(`Registered disabled WASM plugin: ${pluginId} (${pluginName}) - schema available, instance not loaded`);
            return plugin;
        }
        // Plugin is enabled - proceed with full load
        debug(`Plugin ${packageName} is enabled, initializing VFS and preparing for startup`);
        // Initialize VFS with the real plugin ID
        (0, wasm_storage_1.initializePluginVfs)(storagePaths);
        // Clean up temp VFS
        if (fs.existsSync(tempVfsRoot)) {
            fs.rmSync(tempVfsRoot, { recursive: true, force: true });
        }
        // Write initial config file if it doesn't exist
        if (!fs.existsSync(storagePaths.configFile)) {
            debug(`Creating initial config file for ${packageName}`);
            (0, wasm_storage_1.writePluginConfig)(storagePaths.configFile, savedConfig);
        }
        // Use the instance we already loaded
        const instance = tempInstance;
        // Create plugin object
        const plugin = {
            id: pluginId,
            name: pluginName,
            type: 'wasm',
            packageName,
            version: metadata.version || packageJson.version,
            enabled: savedConfig.enabled || false,
            enableDebug: savedConfig.enableDebug || false,
            keywords: packageJson.keywords || [],
            packageLocation: location,
            configPath,
            metadata: {
                id: pluginId,
                name: pluginName,
                packageName,
                version: metadata.version || packageJson.version,
                wasmManifest: packageJson.wasmManifest,
                capabilities,
                packageLocation: location
            },
            instance,
            status: 'stopped',
            schema,
            configuration: savedConfig.configuration, // Keep undefined/null for UI "Configure" button logic
            crashCount: 0,
            restartBackoff: 1000, // Start with 1 second
            description: packageJson.description || '',
            state: 'stopped',
            format: instance.format // WASM binary format (wasi-p1 or component-model)
        };
        // Add Node.js plugin compatibility properties
        addNodejsPluginCompat(plugin, pluginId);
        // Register in global map
        exports.wasmPlugins.set(pluginId, plugin);
        // Add to app.plugins array for unified plugin management
        if (app.plugins) {
            app.plugins.push(plugin);
        }
        // Add to app.pluginsMap for plugin API compatibility
        if (app.pluginsMap) {
            app.pluginsMap[pluginId] = plugin;
        }
        // Set up REST API routes for this plugin
        (0, plugin_routes_1.setupWasmPluginRoutes)(app, plugin, configPath, _updateWasmPluginConfig, _startWasmPlugin, _unloadWasmPlugin, _stopWasmPlugin);
        debug(`Registered WASM plugin: ${pluginId} (${pluginName})`);
        // Auto-start if enabled
        if (plugin.enabled) {
            await _startWasmPlugin(app, pluginId);
        }
        return plugin;
    }
    catch (error) {
        const errorMsg = error instanceof Error ? error.message : String(error);
        debug(`Failed to register WASM plugin ${packageName}: ${errorMsg}`);
        throw new Error(`Failed to register WASM plugin: ${errorMsg}`);
    }
}
/**
 * Get all WASM plugins
 */
function getAllWasmPlugins() {
    return Array.from(exports.wasmPlugins.values());
}
/**
 * Get a WASM plugin by ID
 */
function getWasmPlugin(pluginId) {
    return exports.wasmPlugins.get(pluginId);
}
//# sourceMappingURL=plugin-registry.js.map