"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.WasmWorkerManager = void 0;
var _wasmLoader = require("../wasmLoader.js");
/**
 * WasmWorkerManager — generic worker lifecycle + message bridge
 * for offloading WASM inference to a Web Worker.
 *
 * All managers share a single Worker (and therefore a single WASM
 * instance).  The worker routes messages by `feature` field, so
 * multiple features can coexist concurrently.  The shared Worker
 * is ref-counted and terminated when the last manager is released.
 */

// ---------------------------------------------------------------------------
// Module-level shared Worker (singleton, ref-counted)
// ---------------------------------------------------------------------------

let sharedWorker = null;
let sharedRefCount = 0;
let sharedWasmBasePath = null;
const featureListeners = new Map();
function acquireWorker(wasmBasePath) {
  if (!sharedWorker) {
    // Always use a bootstrap blob worker. This avoids cross-origin restrictions
    // because: (1) blob: Workers are same-origin by construction, and
    // (2) importScripts() inside Workers is exempt from same-origin policy,
    // so it can load the real worker script from any CDN.
    const workerUrl = JSON.stringify(wasmBasePath + 'sherpa-worker.js');
    const bootstrap = `importScripts(${workerUrl});`;
    const blob = new Blob([bootstrap], {
      type: 'application/javascript'
    });
    sharedWorker = new Worker(URL.createObjectURL(blob));
    sharedWasmBasePath = wasmBasePath;
    sharedWorker.addEventListener('message', e => {
      const feature = e.data?.feature;
      if (feature) featureListeners.get(feature)?.(e.data);
    });
    sharedWorker.addEventListener('error', err => {
      const errorMsg = {
        type: 'worker-error',
        error: err.message || 'Worker error'
      };
      for (const fn of featureListeners.values()) {
        fn(errorMsg);
      }
    });
  } else if (sharedWasmBasePath && wasmBasePath !== sharedWasmBasePath) {
    console.warn(`[WasmWorkerManager] wasmBasePath mismatch: existing="${sharedWasmBasePath}", requested="${wasmBasePath}". Reusing existing worker.`);
  }
  sharedRefCount++;
  return sharedWorker;
}
function releaseWorker(feature) {
  featureListeners.delete(feature);
  sharedRefCount--;
  if (sharedRefCount <= 0) {
    sharedWorker?.terminate();
    sharedWorker = null;
    sharedRefCount = 0;
    sharedWasmBasePath = null;
  }
}

// ---------------------------------------------------------------------------
// WasmWorkerManager
// ---------------------------------------------------------------------------

class WasmWorkerManager {
  pending = new Map();
  nextId = 0;
  initialized = false;
  _cleaned = false;
  _initResolve = null;
  _initReject = null;
  _releaseResolve = null;
  constructor(feature) {
    this.feature = feature;
  }

  /** True if the browser supports Web Workers. */
  static isAvailable() {
    return typeof Worker !== 'undefined';
  }

  /** True if workers are available AND not disabled via config. */
  static shouldUse() {
    return (0, _wasmLoader.getSherpaOnnxConfig)().useWorker !== false && typeof Worker !== 'undefined';
  }
  async init(wasmBasePath, config) {
    const worker = acquireWorker(wasmBasePath);
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        this._initResolve = null;
        this._initReject = null;
        this._cleanup();
        reject(new Error(`Worker init timeout for ${this.feature} (60s)`));
      }, 60_000);
      this._initResolve = result => {
        clearTimeout(timeout);
        this._initResolve = null;
        this._initReject = null;
        this.initialized = true;
        this._cleaned = false;
        resolve(result);
      };
      this._initReject = error => {
        clearTimeout(timeout);
        this._initResolve = null;
        this._initReject = null;
        this._cleanup();
        reject(error);
      };
      featureListeners.set(this.feature, this._onMessage);
      worker.postMessage({
        type: 'init',
        feature: this.feature,
        wasmBasePath,
        config
      });
    });
  }
  async process(payload, transferables, timeoutMs = 120_000) {
    if (!sharedWorker || !this.initialized) {
      throw new Error(`Worker not initialized for ${this.feature}`);
    }
    const requestId = String(this.nextId++);
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        const pending = this.pending.get(requestId);
        if (pending) {
          this.pending.delete(requestId);
          pending.reject(new Error(`Worker process timeout for ${this.feature} (${timeoutMs}ms)`));
        }
      }, timeoutMs);
      this.pending.set(requestId, {
        resolve: value => {
          clearTimeout(timer);
          resolve(value);
        },
        reject: reason => {
          clearTimeout(timer);
          reject(reason);
        }
      });
      sharedWorker.postMessage({
        type: 'process',
        feature: this.feature,
        requestId,
        payload
      }, transferables ?? []);
    });
  }
  async release() {
    if (!sharedWorker || !this.initialized) return;
    return new Promise(resolve => {
      const timeout = setTimeout(() => {
        this._releaseResolve = null;
        this._cleanup();
        resolve();
      }, 5_000);
      this._releaseResolve = () => {
        clearTimeout(timeout);
        this._releaseResolve = null;
        this._cleanup();
        resolve();
      };
      sharedWorker.postMessage({
        type: 'release',
        feature: this.feature
      });
    });
  }
  dispose() {
    this._cleanup();
  }
  _cleanup() {
    if (this._cleaned) return;
    this._cleaned = true;
    for (const [, {
      reject
    }] of this.pending) {
      reject(new Error('Worker disposed'));
    }
    this.pending.clear();
    this.initialized = false;
    releaseWorker(this.feature);
  }
  _onMessage = msg => {
    switch (msg.type) {
      case 'init-complete':
        this._initResolve?.(msg.result);
        break;
      case 'init-error':
        this._initReject?.(new Error(msg.error));
        break;
      case 'worker-error':
        if (this._initReject) {
          this._initReject(new Error(msg.error || 'Worker error'));
        }
        for (const [, {
          reject
        }] of this.pending) {
          reject(new Error(msg.error || 'Worker error'));
        }
        this.pending.clear();
        break;
      case 'result':
        if (msg.requestId != null) {
          const pending = this.pending.get(msg.requestId);
          if (pending) {
            this.pending.delete(msg.requestId);
            pending.resolve(msg.result);
          }
        }
        break;
      case 'process-error':
        if (msg.requestId != null) {
          const pending = this.pending.get(msg.requestId);
          if (pending) {
            this.pending.delete(msg.requestId);
            pending.reject(new Error(msg.error));
          }
        }
        break;
      case 'released':
        this._releaseResolve?.();
        break;
    }
  };
}
exports.WasmWorkerManager = WasmWorkerManager;
//# sourceMappingURL=WasmWorkerManager.js.map