import * as webcontainer from '@tempfix/webcontainer__api';
import type { IExecutionEnvironment, IFileEntry, IFileWatcher, IProcessHandle } from '../interfaces/IExecutionEnvironment.js';

/**
 * WebContainer-based execution environment.
 * Runs Node.js and shell commands in the browser using WebContainer API.
 */
export class WebContainerEnvironment implements IExecutionEnvironment {
  // Static shared state - WebContainer only allows ONE boot per page
  private static sharedContainer: webcontainer.WebContainer | null = null;
  private static bootPromise: Promise<webcontainer.WebContainer> | null = null;

  private _ready: boolean = false;

  public readonly type = 'webcontainer' as const;

  public get ready(): boolean {
    return this._ready;
  }

  private get container(): webcontainer.WebContainer | null {
    return WebContainerEnvironment.sharedContainer;
  }

  // ============ Lifecycle ============

  public async init(): Promise<void> {
    // Already initialized (this instance)
    if (this._ready && WebContainerEnvironment.sharedContainer) {
      return;
    }

    // If boot is in progress (by any instance), wait for it
    if (WebContainerEnvironment.bootPromise) {
      await WebContainerEnvironment.bootPromise;
      this._ready = true;
      return;
    }

    // If already booted by another instance, just mark ready
    if (WebContainerEnvironment.sharedContainer) {
      this._ready = true;
      return;
    }

    // Check if SharedArrayBuffer is available (required for WebContainer)
    if (typeof SharedArrayBuffer === 'undefined') {
      throw new Error(
        'WebContainer requires SharedArrayBuffer which is not available. ' +
        'Ensure your server sends these headers:\n' +
        '  Cross-Origin-Opener-Policy: same-origin\n' +
        '  Cross-Origin-Embedder-Policy: require-corp'
      );
    }

    // Start boot process
    WebContainerEnvironment.bootPromise = webcontainer.WebContainer.boot();

    try {
      WebContainerEnvironment.sharedContainer = await WebContainerEnvironment.bootPromise;
      this._ready = true;
    } catch (error) {
      // Reset promise on failure so retry is possible
      WebContainerEnvironment.bootPromise = null;
      throw error;
    }
  }

  public async destroy(): Promise<void> {
    if (WebContainerEnvironment.sharedContainer) {
      WebContainerEnvironment.sharedContainer.teardown();
      WebContainerEnvironment.sharedContainer = null;
      WebContainerEnvironment.bootPromise = null;
      this._ready = false;
    }
  }

  // ============ Filesystem Operations ============

  public async readFile(path: string): Promise<string> {
    this.ensureReady();
    return await this.container!.fs.readFile(path, 'utf-8');
  }

  public async writeFile(path: string, contents: string): Promise<void> {
    this.ensureReady();
    await this.container!.fs.writeFile(path, contents, 'utf-8');
  }

  public async readDir(path: string): Promise<IFileEntry[]> {
    this.ensureReady();
    const entries = await this.container!.fs.readdir(path, { withFileTypes: true });

    return entries.map((entry) => ({
      type: entry.isDirectory() ? 'directory' as const : 'file' as const,
      name: entry.name,
      path: path === '/' ? `/${entry.name}` : `${path}/${entry.name}`,
    }));
  }

  public async mkdir(path: string): Promise<void> {
    this.ensureReady();
    await this.container!.fs.mkdir(path, { recursive: true });
  }

  public async rm(path: string, options?: { recursive?: boolean }): Promise<void> {
    this.ensureReady();
    await this.container!.fs.rm(path, { recursive: options?.recursive ?? false });
  }

  public async exists(path: string): Promise<boolean> {
    this.ensureReady();
    try {
      await this.container!.fs.readFile(path);
      return true;
    } catch {
      try {
        await this.container!.fs.readdir(path);
        return true;
      } catch {
        return false;
      }
    }
  }

  public watch(
    path: string,
    callback: (event: 'rename' | 'change', filename: string | null) => void,
    options?: { recursive?: boolean }
  ): IFileWatcher {
    this.ensureReady();
    const watcher = this.container!.fs.watch(
      path,
      { recursive: options?.recursive ?? false },
      callback
    );
    return {
      stop: () => watcher.close(),
    };
  }

  // ============ Process Execution ============

  public async spawn(command: string, args: string[] = []): Promise<IProcessHandle> {
    this.ensureReady();

    const process = await this.container!.spawn(command, args);

    return {
      output: process.output as unknown as ReadableStream<string>,
      input: process.input as unknown as { getWriter(): WritableStreamDefaultWriter<string> },
      exit: process.exit,
      kill: () => process.kill(),
    };
  }

  // ============ WebContainer-specific methods ============

  /**
   * Mount files into the virtual filesystem.
   * This is a WebContainer-specific operation.
   * @param files - File tree structure to mount
   */
  public async mount(files: webcontainer.FileSystemTree): Promise<void> {
    this.ensureReady();
    await this.container!.mount(files);
  }

  /**
   * Get the underlying WebContainer instance.
   * Use sparingly - prefer the interface methods.
   */
  public getContainer(): webcontainer.WebContainer {
    this.ensureReady();
    return this.container!;
  }

  // ============ Private Helpers ============

  private ensureReady(): void {
    if (!this._ready || !this.container) {
      throw new Error('WebContainerEnvironment not initialized. Call init() first.');
    }
  }
}
