import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { promises as fs } from "fs";
import * as path from "path";
import { pathToFileURL } from "url";
import { logger } from "../monitoring/logger.js";
const DEFAULT_LOAD_TIMEOUT = 3e4;
const ALLOWED_PERMISSION_PATTERNS = [
  /^network:[a-z0-9.-]+$/,
  /^storage:(local|session)$/,
  /^frames:(read|write)$/,
  /^events:(emit|listen)$/
];
const BLOCKED_DOMAINS = [
  "localhost",
  "127.0.0.1",
  "0.0.0.0",
  "*.local",
  "internal.*"
];
class ExtensionLoader {
  loadedExtensions = /* @__PURE__ */ new Map();
  extensionInstances = /* @__PURE__ */ new Map();
  eventHandlers = /* @__PURE__ */ new Map();
  grantedPermissions = /* @__PURE__ */ new Map();
  /**
   * Load an extension from various sources
   */
  async loadExtension(options) {
    const { source, timeout = DEFAULT_LOAD_TIMEOUT } = options;
    try {
      const { type, uri } = this.parseSource(source);
      logger.info("Loading extension", { source, type });
      const loadPromise = this.loadFromSource(type, uri, options);
      const result = await this.withTimeout(loadPromise, timeout);
      if (!result.success || !result.extension) {
        return result;
      }
      const validation = this.validateExtension(result.extension);
      if (!validation.valid) {
        return {
          success: false,
          error: `Extension validation failed: ${validation.errors.join(", ")}`,
          warnings: validation.warnings
        };
      }
      if (!options.skipPermissionCheck) {
        const permissionCheck = await this.verifyPermissions(
          result.extension,
          options.permissions
        );
        if (!permissionCheck.success) {
          return permissionCheck;
        }
      }
      const initResult = await this.initializeExtension(
        result.extension,
        options
      );
      if (!initResult.success) {
        return initResult;
      }
      const extensionId = this.generateExtensionId(result.extension);
      this.registerExtension(
        extensionId,
        result.extension,
        source,
        type,
        options
      );
      logger.info("Extension loaded successfully", {
        extensionId,
        name: result.extension.name,
        version: result.extension.version
      });
      return {
        success: true,
        extension: result.extension,
        extensionId,
        warnings: validation.warnings
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      logger.error("Failed to load extension", { source, error: message });
      return {
        success: false,
        error: `Failed to load extension: ${message}`
      };
    }
  }
  /**
   * Unload an extension
   */
  async unloadExtension(extensionId) {
    const extension = this.extensionInstances.get(extensionId);
    const state = this.loadedExtensions.get(extensionId);
    if (!extension || !state) {
      logger.warn("Extension not found for unload", { extensionId });
      return false;
    }
    try {
      await extension.destroy();
      this.extensionInstances.delete(extensionId);
      this.loadedExtensions.delete(extensionId);
      this.grantedPermissions.delete(extensionId);
      logger.info("Extension unloaded", { extensionId });
      return true;
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      logger.error("Failed to unload extension", {
        extensionId,
        error: message
      });
      return false;
    }
  }
  /**
   * Get all loaded extensions
   */
  getLoadedExtensions() {
    return Array.from(this.loadedExtensions.values());
  }
  /**
   * Get extension by ID
   */
  getExtension(extensionId) {
    return this.extensionInstances.get(extensionId);
  }
  /**
   * Get extension state by ID
   */
  getExtensionState(extensionId) {
    return this.loadedExtensions.get(extensionId);
  }
  /**
   * Parse source string to determine type and URI
   */
  parseSource(source) {
    if (source.startsWith("https://") || source.startsWith("http://")) {
      return { type: "url", uri: source };
    }
    if (source.startsWith("file://")) {
      return { type: "file", uri: source.slice(7) };
    }
    if (source.startsWith("npm:")) {
      return { type: "npm", uri: source.slice(4) };
    }
    if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../")) {
      return { type: "file", uri: source };
    }
    return { type: "npm", uri: source };
  }
  /**
   * Load extension from source based on type
   */
  async loadFromSource(type, uri, options) {
    switch (type) {
      case "url":
        return this.loadFromUrl(uri);
      case "file":
        return this.loadFromFile(uri);
      case "npm":
        return this.loadFromNpm(uri);
      default:
        return {
          success: false,
          error: `Unknown source type: ${type}`
        };
    }
  }
  /**
   * Load extension from URL
   */
  async loadFromUrl(url) {
    try {
      const parsedUrl = new URL(url);
      if (this.isBlockedDomain(parsedUrl.hostname)) {
        return {
          success: false,
          error: `Blocked domain: ${parsedUrl.hostname}`
        };
      }
      if (parsedUrl.protocol !== "https:" && process.env["NODE_ENV"] === "production") {
        return {
          success: false,
          error: "Only HTTPS URLs are allowed in production"
        };
      }
      const response = await fetch(url);
      if (!response.ok) {
        return {
          success: false,
          error: `HTTP ${response.status}: ${response.statusText}`
        };
      }
      const code = await response.text();
      const manifestUrl = url.replace(/\.js$/, ".manifest.json");
      let manifest;
      try {
        const manifestResponse = await fetch(manifestUrl);
        if (manifestResponse.ok) {
          manifest = await manifestResponse.json();
        }
      } catch {
      }
      const extension = await this.evaluateExtensionCode(code, url);
      if (manifest) {
        extension.manifest = manifest;
      }
      return { success: true, extension };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return {
        success: false,
        error: `Failed to load from URL: ${message}`
      };
    }
  }
  /**
   * Load extension from local file
   */
  async loadFromFile(filePath) {
    try {
      const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
      try {
        await fs.access(absolutePath);
      } catch {
        return {
          success: false,
          error: `File not found: ${absolutePath}`
        };
      }
      const manifestPath = absolutePath.replace(/\.js$/, ".manifest.json");
      let manifest;
      try {
        const manifestContent = await fs.readFile(manifestPath, "utf-8");
        manifest = JSON.parse(manifestContent);
      } catch {
        try {
          const packagePath = path.join(
            path.dirname(absolutePath),
            "package.json"
          );
          const packageContent = await fs.readFile(packagePath, "utf-8");
          const pkg = JSON.parse(packageContent);
          if (pkg.stackmemory) {
            manifest = pkg.stackmemory;
          }
        } catch {
        }
      }
      const fileUrl = pathToFileURL(absolutePath).href;
      const module = await import(fileUrl);
      const extension = module.default || module;
      if (!this.isExtensionLike(extension)) {
        return {
          success: false,
          error: "Module does not export a valid extension"
        };
      }
      if (manifest) {
        extension.manifest = manifest;
      }
      return { success: true, extension };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return {
        success: false,
        error: `Failed to load from file: ${message}`
      };
    }
  }
  /**
   * Load extension from NPM package
   */
  async loadFromNpm(packageName) {
    try {
      const { name, version } = this.parseNpmPackage(packageName);
      let module;
      try {
        module = await import(name);
      } catch {
        try {
          const nodeModulesPath = path.join(
            process.cwd(),
            "node_modules",
            name
          );
          const packageJsonPath = path.join(nodeModulesPath, "package.json");
          const packageJson = JSON.parse(
            await fs.readFile(packageJsonPath, "utf-8")
          );
          const entryPoint = packageJson.main || "index.js";
          const entryPath = path.join(nodeModulesPath, entryPoint);
          module = await import(pathToFileURL(entryPath).href);
        } catch (innerError) {
          return {
            success: false,
            error: `Package not found: ${name}. Try running: npm install ${name}`
          };
        }
      }
      const extension = module.default || module;
      if (!this.isExtensionLike(extension)) {
        return {
          success: false,
          error: "Package does not export a valid extension"
        };
      }
      return { success: true, extension };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return {
        success: false,
        error: `Failed to load from NPM: ${message}`
      };
    }
  }
  /**
   * Evaluate extension code (for URL sources)
   */
  async evaluateExtensionCode(code, sourceUrl) {
    const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString("base64")}`;
    try {
      const module = await import(dataUrl);
      const extension = module.default || module;
      if (!this.isExtensionLike(extension)) {
        throw new Error("Code does not export a valid extension");
      }
      return extension;
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      throw new Error(`Failed to evaluate extension code: ${message}`);
    }
  }
  /**
   * Check if object looks like an extension
   */
  isExtensionLike(obj) {
    if (!obj || typeof obj !== "object") return false;
    const ext = obj;
    return typeof ext["name"] === "string" && typeof ext["version"] === "string" && typeof ext["init"] === "function" && typeof ext["destroy"] === "function";
  }
  /**
   * Validate extension structure and metadata
   */
  validateExtension(extension) {
    const errors = [];
    const warnings = [];
    if (!extension.name || typeof extension.name !== "string") {
      errors.push("Extension must have a valid name");
    }
    if (!extension.version || typeof extension.version !== "string") {
      errors.push("Extension must have a valid version");
    }
    if (extension.version && !/^\d+\.\d+\.\d+/.test(extension.version)) {
      warnings.push("Version should follow semver format (x.y.z)");
    }
    if (typeof extension.init !== "function") {
      errors.push("Extension must have an init method");
    }
    if (typeof extension.destroy !== "function") {
      errors.push("Extension must have a destroy method");
    }
    if (extension.tools) {
      if (!Array.isArray(extension.tools)) {
        errors.push("Extension tools must be an array");
      } else {
        extension.tools.forEach((tool, index) => {
          if (!tool.name) {
            errors.push(`Tool at index ${index} must have a name`);
          }
          if (!tool.execute || typeof tool.execute !== "function") {
            errors.push(
              `Tool "${tool.name || index}" must have an execute function`
            );
          }
        });
      }
    }
    if (extension.manifest) {
      const manifestValidation = this.validateManifest(extension.manifest);
      errors.push(...manifestValidation.errors);
      warnings.push(...manifestValidation.warnings);
    }
    return {
      valid: errors.length === 0,
      errors,
      warnings
    };
  }
  /**
   * Validate extension manifest
   */
  validateManifest(manifest) {
    const errors = [];
    const warnings = [];
    if (!manifest.name) {
      errors.push("Manifest must have a name");
    }
    if (!manifest.version) {
      errors.push("Manifest must have a version");
    }
    if (!manifest.permissions || !Array.isArray(manifest.permissions)) {
      errors.push("Manifest must have a permissions array");
    } else {
      manifest.permissions.forEach((perm) => {
        if (!this.isValidPermission(perm)) {
          errors.push(`Invalid permission: ${perm}`);
        }
      });
    }
    return { valid: errors.length === 0, errors, warnings };
  }
  /**
   * Check if permission string is valid
   */
  isValidPermission(permission) {
    return ALLOWED_PERMISSION_PATTERNS.some(
      (pattern) => pattern.test(permission)
    );
  }
  /**
   * Verify extension permissions
   */
  async verifyPermissions(extension, overridePermissions) {
    const requestedPermissions = overridePermissions || extension.manifest?.permissions || [];
    for (const perm of requestedPermissions) {
      if (perm.startsWith("network:")) {
        const domain = perm.slice(8);
        if (this.isBlockedDomain(domain)) {
          return {
            success: false,
            error: `Permission denied: network access to ${domain} is blocked`
          };
        }
      }
    }
    logger.debug("Permission verification passed", {
      extension: extension.name,
      permissions: requestedPermissions
    });
    return { success: true, extension };
  }
  /**
   * Initialize extension with context
   */
  async initializeExtension(extension, options) {
    try {
      const extensionId = this.generateExtensionId(extension);
      const permissions = options.permissions || extension.manifest?.permissions || [];
      const context = this.createExtensionContext(extensionId, permissions);
      this.grantedPermissions.set(extensionId, new Set(permissions));
      await extension.init(context);
      return { success: true, extension };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return {
        success: false,
        error: `Extension initialization failed: ${message}`
      };
    }
  }
  /**
   * Create extension context with permission-gated access
   */
  createExtensionContext(extensionId, permissions) {
    const permSet = new Set(permissions);
    const context = {
      extensionId,
      permissions,
      // State management (always available, scoped to extension)
      state: {
        get: async (key) => {
          return void 0;
        },
        set: async (key, value) => {
        },
        delete: async (key) => {
        }
      },
      // Sandboxed fetch (always available)
      fetch: this.createSandboxedFetch(extensionId, permSet),
      // Event system
      emit: (event, data) => {
        if (!permSet.has("events:emit")) {
          logger.warn("Extension lacks events:emit permission", {
            extensionId,
            event
          });
          return;
        }
        this.emitEvent(event, data);
      },
      on: (event, handler) => {
        if (!permSet.has("events:listen")) {
          logger.warn("Extension lacks events:listen permission", {
            extensionId,
            event
          });
          return () => {
          };
        }
        return this.addEventListener(event, handler);
      },
      off: (event, handler) => {
        this.removeEventListener(event, handler);
      }
    };
    if (permSet.has("frames:read") || permSet.has("frames:write")) {
      context.frames = {
        get: async (frameId) => {
          return void 0;
        },
        list: async () => {
          return [];
        }
      };
      if (permSet.has("frames:write")) {
        context.frames.create = async (options) => {
          throw new Error("Frame creation not implemented");
        };
        context.frames.update = async (frameId, data) => {
          throw new Error("Frame update not implemented");
        };
      }
    }
    return context;
  }
  /**
   * Create sandboxed fetch that respects network permissions
   */
  createSandboxedFetch(extensionId, permissions) {
    return async (input, init) => {
      const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
      const parsedUrl = new URL(url);
      const domain = parsedUrl.hostname;
      const hasPermission = Array.from(permissions).some((perm) => {
        if (!perm.startsWith("network:")) return false;
        const allowedDomain = perm.slice(8);
        if (allowedDomain.startsWith("*.")) {
          const baseDomain = allowedDomain.slice(2);
          return domain.endsWith(baseDomain);
        }
        return domain === allowedDomain;
      });
      if (!hasPermission) {
        throw new Error(`Network access denied for domain: ${domain}`);
      }
      if (this.isBlockedDomain(domain)) {
        throw new Error(`Network access blocked for domain: ${domain}`);
      }
      return fetch(input, init);
    };
  }
  /**
   * Check if domain is blocked
   */
  isBlockedDomain(domain) {
    return BLOCKED_DOMAINS.some((blocked) => {
      if (blocked.startsWith("*.")) {
        return domain.endsWith(blocked.slice(1));
      }
      if (blocked.endsWith(".*")) {
        return domain.startsWith(blocked.slice(0, -1));
      }
      return domain === blocked;
    });
  }
  /**
   * Register loaded extension
   */
  registerExtension(extensionId, extension, source, sourceType, options) {
    const state = {
      id: extensionId,
      name: extension.name,
      version: extension.version,
      source,
      sourceType,
      permissions: options.permissions || extension.manifest?.permissions || [],
      loadedAt: Date.now(),
      status: "active"
    };
    this.loadedExtensions.set(extensionId, state);
    this.extensionInstances.set(extensionId, extension);
  }
  /**
   * Generate unique extension ID
   */
  generateExtensionId(extension) {
    return `${extension.name}@${extension.version}`;
  }
  /**
   * Parse NPM package string
   */
  parseNpmPackage(packageName) {
    const atIndex = packageName.lastIndexOf("@");
    if (atIndex > 0) {
      return {
        name: packageName.slice(0, atIndex),
        version: packageName.slice(atIndex + 1)
      };
    }
    return { name: packageName };
  }
  /**
   * Wrap promise with timeout
   */
  async withTimeout(promise, timeoutMs) {
    return Promise.race([
      promise,
      new Promise(
        (_, reject) => setTimeout(
          () => reject(new Error(`Operation timed out after ${timeoutMs}ms`)),
          timeoutMs
        )
      )
    ]);
  }
  /**
   * Emit event to all listeners
   */
  emitEvent(event, data) {
    const handlers = this.eventHandlers.get(event);
    if (!handlers) return;
    handlers.forEach((handler) => {
      try {
        const result = handler(data);
        if (result instanceof Promise) {
          result.catch((error) => {
            logger.error("Event handler error", { event, error });
          });
        }
      } catch (error) {
        logger.error("Event handler error", { event, error });
      }
    });
  }
  /**
   * Add event listener
   */
  addEventListener(event, handler) {
    if (!this.eventHandlers.has(event)) {
      this.eventHandlers.set(event, /* @__PURE__ */ new Set());
    }
    this.eventHandlers.get(event).add(handler);
    return () => this.removeEventListener(event, handler);
  }
  /**
   * Remove event listener
   */
  removeEventListener(event, handler) {
    const handlers = this.eventHandlers.get(event);
    if (handlers) {
      handlers.delete(handler);
    }
  }
  /**
   * Disable an extension without unloading
   */
  async disableExtension(extensionId) {
    const state = this.loadedExtensions.get(extensionId);
    if (!state) return false;
    state.status = "disabled";
    return true;
  }
  /**
   * Enable a disabled extension
   */
  async enableExtension(extensionId) {
    const state = this.loadedExtensions.get(extensionId);
    if (!state || state.status !== "disabled") return false;
    state.status = "active";
    return true;
  }
  /**
   * Unload all extensions
   */
  async unloadAll() {
    const extensionIds = Array.from(this.loadedExtensions.keys());
    for (const extensionId of extensionIds) {
      await this.unloadExtension(extensionId);
    }
  }
}
let loaderInstance;
function getExtensionLoader() {
  if (!loaderInstance) {
    loaderInstance = new ExtensionLoader();
  }
  return loaderInstance;
}
async function loadExtension(source, options) {
  const loader = getExtensionLoader();
  return loader.loadExtension({ source, ...options });
}
async function unloadExtension(extensionId) {
  const loader = getExtensionLoader();
  return loader.unloadExtension(extensionId);
}
export {
  ExtensionLoader,
  getExtensionLoader,
  loadExtension,
  unloadExtension
};
//# sourceMappingURL=loader.js.map
