import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { z } from "zod";
import { EventEmitter } from "events";
import { ValidationError, ErrorCode, SystemError } from "../errors/index.js";
import { logger } from "../monitoring/logger.js";
const PermissionManifestSchema = z.object({
  name: z.string().min(1).max(100),
  version: z.string().regex(/^\d+\.\d+\.\d+$/),
  permissions: z.array(
    z.discriminatedUnion("type", [
      z.object({
        type: z.literal("network"),
        domains: z.array(z.string().min(1))
      }),
      z.object({
        type: z.literal("storage:local"),
        quotaBytes: z.number().positive().optional()
      }),
      z.object({
        type: z.literal("storage:indexed"),
        quotaBytes: z.number().positive().optional()
      }),
      z.object({
        type: z.literal("frames:read"),
        scope: z.enum(["own", "all"]).optional()
      }),
      z.object({
        type: z.literal("frames:write"),
        scope: z.enum(["own", "all"]).optional()
      }),
      z.object({
        type: z.literal("events:emit"),
        patterns: z.array(z.string()).optional()
      }),
      z.object({
        type: z.literal("events:subscribe"),
        patterns: z.array(z.string()).optional()
      }),
      z.object({ type: z.literal("dom:read") }),
      z.object({ type: z.literal("dom:write") })
    ])
  ),
  description: z.string().max(1e3).optional(),
  author: z.string().max(200).optional(),
  homepage: z.string().url().optional()
});
class SandboxRuntime {
  extensions = /* @__PURE__ */ new Map();
  eventBus = new EventEmitter();
  config;
  frameManager;
  stateSerializer;
  constructor(config = {}) {
    this.config = {
      mode: config.mode ?? "node",
      timeout: config.timeout ?? 3e4,
      memoryLimit: config.memoryLimit,
      networkAllowlist: config.networkAllowlist ?? [],
      enableDevtools: config.enableDevtools ?? false
    };
    logger.info("SandboxRuntime initialized", {
      mode: this.config.mode,
      timeout: this.config.timeout
    });
  }
  /**
   * Set the frame manager API for extensions
   */
  setFrameManager(frameManager) {
    this.frameManager = frameManager;
  }
  /**
   * Set the state serializer API for extensions
   */
  setStateSerializer(stateSerializer) {
    this.stateSerializer = stateSerializer;
  }
  /**
   * Load an extension from a source
   */
  async loadExtension(source) {
    logger.info("Loading extension", { source });
    const extension = await this.resolveExtension(source);
    const manifest = extension.manifest;
    this.validateManifest(manifest);
    if (this.extensions.has(manifest.name)) {
      throw new ValidationError(
        `Extension "${manifest.name}" is already loaded`,
        ErrorCode.VALIDATION_FAILED,
        { extensionName: manifest.name }
      );
    }
    const context = this.createExtensionContext(manifest);
    const state = {
      extension,
      context,
      status: "loading",
      loadedAt: Date.now(),
      source
    };
    this.extensions.set(manifest.name, state);
    try {
      await this.withTimeout(
        extension.init(context),
        this.config.timeout,
        `Extension "${manifest.name}" initialization timed out`
      );
      state.status = "active";
      logger.info("Extension loaded successfully", {
        name: manifest.name,
        version: manifest.version
      });
      return manifest.name;
    } catch (error) {
      state.status = "error";
      state.error = error instanceof Error ? error : new Error(String(error));
      logger.error("Extension initialization failed", {
        name: manifest.name,
        error: state.error.message
      });
      throw error;
    }
  }
  /**
   * Unload an extension
   */
  async unloadExtension(extensionName) {
    const state = this.extensions.get(extensionName);
    if (!state) {
      throw new ValidationError(
        `Extension "${extensionName}" not found`,
        ErrorCode.RESOURCE_NOT_FOUND,
        { extensionName }
      );
    }
    try {
      await this.withTimeout(
        state.extension.destroy(),
        this.config.timeout,
        `Extension "${extensionName}" destruction timed out`
      );
    } catch (error) {
      logger.warn("Extension destruction failed", {
        name: extensionName,
        error: error instanceof Error ? error.message : String(error)
      });
    }
    state.status = "unloaded";
    this.extensions.delete(extensionName);
    logger.info("Extension unloaded", { name: extensionName });
  }
  /**
   * Hot-reload an extension (unload and reload)
   */
  async reloadExtension(extensionName) {
    const state = this.extensions.get(extensionName);
    if (!state) {
      throw new ValidationError(
        `Extension "${extensionName}" not found`,
        ErrorCode.RESOURCE_NOT_FOUND,
        { extensionName }
      );
    }
    const source = state.source;
    await this.unloadExtension(extensionName);
    await this.loadExtension(source);
    logger.info("Extension reloaded", { name: extensionName });
  }
  /**
   * Get extension by name
   */
  getExtension(extensionName) {
    return this.extensions.get(extensionName)?.extension;
  }
  /**
   * Get all loaded extensions
   */
  listExtensions() {
    return Array.from(this.extensions.entries()).map(([name, state]) => ({
      name,
      version: state.extension.version,
      status: state.status,
      loadedAt: state.loadedAt
    }));
  }
  /**
   * Get tools from all active extensions
   */
  getTools() {
    const tools = [];
    for (const state of Array.from(this.extensions.values())) {
      if (state.status === "active" && state.extension.tools) {
        tools.push(...state.extension.tools);
      }
    }
    return tools;
  }
  /**
   * Get providers from all active extensions
   */
  getProviders() {
    const providers = [];
    for (const state of Array.from(this.extensions.values())) {
      if (state.status === "active" && state.extension.providers) {
        providers.push(...state.extension.providers);
      }
    }
    return providers;
  }
  /**
   * Emit event to all extensions
   */
  emit(event, data) {
    this.eventBus.emit(event, data);
  }
  /**
   * Shutdown the runtime
   */
  async shutdown() {
    logger.info("Shutting down SandboxRuntime");
    const names = Array.from(this.extensions.keys());
    for (const name of names) {
      try {
        await this.unloadExtension(name);
      } catch (error) {
        logger.warn("Failed to unload extension during shutdown", {
          name,
          error: error instanceof Error ? error.message : String(error)
        });
      }
    }
    this.eventBus.removeAllListeners();
    logger.info("SandboxRuntime shutdown complete");
  }
  // ============================================================================
  // Private Methods
  // ============================================================================
  /**
   * Resolve extension from source
   */
  async resolveExtension(source) {
    switch (source.type) {
      case "inline":
        return this.loadInlineExtension(source.code, source.manifest);
      case "file":
        return this.loadFileExtension(source.path);
      case "url":
        return this.loadUrlExtension(source.url);
      case "npm":
        return this.loadNpmExtension(source.package, source.version);
      default:
        throw new ValidationError(
          "Invalid extension source type",
          ErrorCode.VALIDATION_FAILED,
          { source }
        );
    }
  }
  /**
   * Load inline extension
   */
  async loadInlineExtension(code, manifest) {
    const AsyncFunction = Object.getPrototypeOf(
      async function() {
      }
    ).constructor;
    try {
      const factory = new AsyncFunction(
        "manifest",
        `
        ${code}
        return { ...exports, manifest };
      `
      );
      const extension = await factory(manifest);
      return extension;
    } catch (error) {
      throw new SystemError(
        `Failed to load inline extension: ${error instanceof Error ? error.message : String(error)}`,
        ErrorCode.INITIALIZATION_ERROR,
        { manifest: manifest.name }
      );
    }
  }
  /**
   * Load extension from file
   */
  async loadFileExtension(filePath) {
    try {
      const module = await import(filePath);
      const extension = module.default || module;
      if (!extension.name || !extension.version || !extension.manifest) {
        throw new Error("Invalid extension export: missing required fields");
      }
      return extension;
    } catch (error) {
      throw new SystemError(
        `Failed to load extension from file: ${error instanceof Error ? error.message : String(error)}`,
        ErrorCode.FILE_NOT_FOUND,
        { filePath }
      );
    }
  }
  /**
   * Load extension from URL
   */
  async loadUrlExtension(url) {
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      const code = await response.text();
      const manifestMatch = code.match(
        /export\s+const\s+manifest\s*=\s*({[\s\S]*?});/
      );
      if (!manifestMatch) {
        throw new Error("No manifest found in extension code");
      }
      const manifest = JSON.parse(
        manifestMatch[1].replace(/'/g, '"')
      );
      return this.loadInlineExtension(code, manifest);
    } catch (error) {
      throw new SystemError(
        `Failed to load extension from URL: ${error instanceof Error ? error.message : String(error)}`,
        ErrorCode.NETWORK_ERROR,
        { url }
      );
    }
  }
  /**
   * Load extension from npm package
   */
  async loadNpmExtension(packageName, version) {
    try {
      const packagePath = version ? `${packageName}@${version}` : packageName;
      const module = await import(packagePath);
      const extension = module.default || module;
      if (!extension.name || !extension.version || !extension.manifest) {
        throw new Error("Invalid extension export: missing required fields");
      }
      return extension;
    } catch (error) {
      throw new SystemError(
        `Failed to load extension from npm: ${error instanceof Error ? error.message : String(error)}`,
        ErrorCode.INITIALIZATION_ERROR,
        { packageName, version }
      );
    }
  }
  /**
   * Validate permission manifest
   */
  validateManifest(manifest) {
    const result = PermissionManifestSchema.safeParse(manifest);
    if (!result.success) {
      throw new ValidationError(
        `Invalid extension manifest: ${result.error.errors.map((e) => e.message).join(", ")}`,
        ErrorCode.VALIDATION_FAILED,
        { errors: result.error.errors }
      );
    }
    const networkPerms = manifest.permissions.filter(
      (p) => p.type === "network"
    );
    for (const perm of networkPerms) {
      for (const domain of perm.domains) {
        if (!this.isDomainAllowed(domain)) {
          throw new ValidationError(
            `Domain "${domain}" is not in the global allowlist`,
            ErrorCode.PERMISSION_VIOLATION,
            { domain, allowlist: this.config.networkAllowlist }
          );
        }
      }
    }
  }
  /**
   * Check if a domain is allowed
   */
  isDomainAllowed(domain) {
    if (!this.config.networkAllowlist || this.config.networkAllowlist.length === 0) {
      return true;
    }
    return this.config.networkAllowlist.some((allowed) => {
      if (allowed === domain) return true;
      if (allowed.startsWith("*.")) {
        const suffix = allowed.slice(1);
        return domain.endsWith(suffix) || domain === allowed.slice(2);
      }
      return false;
    });
  }
  /**
   * Create sandboxed extension context
   */
  createExtensionContext(manifest) {
    const extensionId = `${manifest.name}@${manifest.version}`;
    const sandboxedFetch = this.createSandboxedFetch(manifest);
    const sandboxedStorage = this.createSandboxedStorage(manifest);
    const framesAPI = this.createFramesAPI(manifest);
    const stateAPI = this.createStateAPI(manifest);
    const handlers = /* @__PURE__ */ new Map();
    const context = {
      extensionId,
      extensionName: manifest.name,
      permissions: manifest.permissions,
      frames: framesAPI,
      state: stateAPI,
      fetch: sandboxedFetch,
      storage: sandboxedStorage,
      emit: (event, data) => {
        if (!this.hasPermission(manifest, "events:emit", event)) {
          logger.warn("Extension attempted to emit without permission", {
            extension: manifest.name,
            event
          });
          return;
        }
        this.eventBus.emit(event, data);
      },
      on: (event, handler) => {
        if (!this.hasPermission(manifest, "events:subscribe", event)) {
          logger.warn("Extension attempted to subscribe without permission", {
            extension: manifest.name,
            event
          });
          return () => {
          };
        }
        if (!handlers.has(event)) {
          handlers.set(event, /* @__PURE__ */ new Set());
        }
        handlers.get(event).add(handler);
        this.eventBus.on(event, handler);
        return () => {
          handlers.get(event)?.delete(handler);
          this.eventBus.off(event, handler);
        };
      },
      off: (event, handler) => {
        handlers.get(event)?.delete(handler);
        this.eventBus.off(event, handler);
      }
    };
    return context;
  }
  /**
   * Create sandboxed fetch function
   */
  createSandboxedFetch(manifest) {
    const networkPerms = manifest.permissions.filter(
      (p) => p.type === "network"
    );
    const allowedDomains = networkPerms.flatMap((p) => p.domains);
    return async (input, init) => {
      const url = typeof input === "string" ? new URL(input) : input instanceof URL ? input : new URL(input.url);
      const isAllowed = allowedDomains.some((domain) => {
        if (domain === url.hostname) return true;
        if (domain.startsWith("*.")) {
          const suffix = domain.slice(1);
          return url.hostname.endsWith(suffix);
        }
        return false;
      });
      if (!isAllowed) {
        throw new ValidationError(
          `Network access to "${url.hostname}" is not permitted`,
          ErrorCode.PERMISSION_VIOLATION,
          { hostname: url.hostname, allowed: allowedDomains }
        );
      }
      return globalThis.fetch(input, init);
    };
  }
  /**
   * Create sandboxed storage
   */
  createSandboxedStorage(manifest) {
    const hasLocalStorage = manifest.permissions.some(
      (p) => p.type === "storage:local"
    );
    if (!hasLocalStorage) {
      return {
        getItem: () => null,
        setItem: () => {
        },
        removeItem: () => {
        },
        clear: () => {
        },
        key: () => null,
        length: 0
      };
    }
    const storageKey = `ext:${manifest.name}:`;
    const storage = /* @__PURE__ */ new Map();
    return {
      getItem: (key) => storage.get(storageKey + key) ?? null,
      setItem: (key, value) => {
        storage.set(storageKey + key, value);
      },
      removeItem: (key) => {
        storage.delete(storageKey + key);
      },
      clear: () => {
        for (const key of Array.from(storage.keys())) {
          if (key.startsWith(storageKey)) {
            storage.delete(key);
          }
        }
      },
      key: (index) => {
        const keys = Array.from(storage.keys()).filter(
          (k) => k.startsWith(storageKey)
        );
        return keys[index]?.slice(storageKey.length) ?? null;
      },
      get length() {
        return Array.from(storage.keys()).filter(
          (k) => k.startsWith(storageKey)
        ).length;
      }
    };
  }
  /**
   * Create frames API with permission checks
   */
  createFramesAPI(manifest) {
    const canRead = manifest.permissions.some((p) => p.type === "frames:read");
    const canWrite = manifest.permissions.some(
      (p) => p.type === "frames:write"
    );
    const noOpFrameManager = {
      getCurrentFrame: async () => null,
      getFrame: async () => null,
      listFrames: async () => []
    };
    if (!canRead && !this.frameManager) {
      return noOpFrameManager;
    }
    const fm = this.frameManager;
    const api = {
      getCurrentFrame: async () => {
        if (!canRead || !fm) return null;
        return fm.getCurrentFrame();
      },
      getFrame: async (frameId) => {
        if (!canRead || !fm) return null;
        return fm.getFrame(frameId);
      },
      listFrames: async (filter) => {
        if (!canRead || !fm) return [];
        return fm.listFrames(filter);
      }
    };
    if (canWrite && fm?.createFrame) {
      api.createFrame = async (options) => {
        return fm.createFrame(options);
      };
    }
    if (canWrite && fm?.closeFrame) {
      api.closeFrame = async (frameId) => {
        return fm.closeFrame(frameId);
      };
    }
    return api;
  }
  /**
   * Create state API with permission checks
   */
  createStateAPI(manifest) {
    const canRead = manifest.permissions.some((p) => p.type === "frames:read");
    const canWrite = manifest.permissions.some(
      (p) => p.type === "frames:write"
    );
    const prefix = `ext:${manifest.name}:`;
    const ss = this.stateSerializer;
    return {
      get: async (key) => {
        if (!canRead || !ss) return null;
        return ss.get(prefix + key);
      },
      set: async (key, value) => {
        if (!canWrite || !ss) return;
        await ss.set(prefix + key, value);
      },
      delete: async (key) => {
        if (!canWrite || !ss) return false;
        return ss.delete(prefix + key);
      },
      getDocument: async (key) => {
        if (!canRead || !ss) return null;
        return ss.getDocument(prefix + key);
      },
      setDocument: async (key, content) => {
        if (!canWrite || !ss) return;
        await ss.setDocument(prefix + key, content);
      },
      deleteDocument: async (key) => {
        if (!canWrite || !ss) return false;
        return ss.deleteDocument(prefix + key);
      }
    };
  }
  /**
   * Check if extension has specific permission
   */
  hasPermission(manifest, type, target) {
    const perm = manifest.permissions.find((p) => p.type === type);
    if (!perm) return false;
    if ((type === "events:emit" || type === "events:subscribe") && target) {
      const eventPerm = perm;
      if (!eventPerm.patterns || eventPerm.patterns.length === 0) {
        return true;
      }
      return eventPerm.patterns.some(
        (pattern) => this.matchPattern(pattern, target)
      );
    }
    return true;
  }
  /**
   * Match glob-style pattern
   */
  matchPattern(pattern, value) {
    const regex = new RegExp(
      "^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
    );
    return regex.test(value);
  }
  /**
   * Execute with timeout
   */
  async withTimeout(promise, timeoutMs, message) {
    const timeoutPromise = new Promise((_, reject) => {
      setTimeout(() => reject(new Error(message)), timeoutMs);
    });
    return Promise.race([promise, timeoutPromise]);
  }
}
function createSandboxRuntime(config) {
  return new SandboxRuntime(config);
}
function createManifest(name, version, permissions, options) {
  return {
    name,
    version,
    permissions,
    ...options
  };
}
var sandbox_runtime_default = SandboxRuntime;
export {
  SandboxRuntime,
  createManifest,
  createSandboxRuntime,
  sandbox_runtime_default as default
};
//# sourceMappingURL=sandbox-runtime.js.map
