import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { spawn } from "child_process";
import {
  loadModelRouterConfig,
  buildModelEnv
} from "./model-router.js";
const DEFAULT_ERROR_PATTERNS = [
  /rate.?limit/i,
  /429/,
  /too.?many.?requests/i,
  /overloaded/i,
  /capacity/i,
  /temporarily.?unavailable/i,
  /503/,
  /502/,
  /500/,
  /internal.?server.?error/i,
  /timeout/i,
  /ETIMEDOUT/,
  /ESOCKETTIMEDOUT/,
  /ECONNRESET/,
  /ECONNREFUSED/
];
class FallbackMonitor {
  config;
  routerConfig = loadModelRouterConfig();
  currentProvider = "anthropic";
  restartCount = 0;
  inFallback = false;
  errorBuffer = "";
  lastErrorTime = 0;
  errorCount = 0;
  constructor(config = {}) {
    this.config = {
      enabled: true,
      maxRestarts: 3,
      restartDelayMs: 2e3,
      errorPatterns: DEFAULT_ERROR_PATTERNS,
      ...config
    };
  }
  /**
   * Check if text contains error patterns that should trigger fallback
   */
  detectError(text) {
    for (const pattern of this.config.errorPatterns) {
      if (pattern.test(text)) {
        return {
          shouldFallback: true,
          reason: pattern.source
        };
      }
    }
    return { shouldFallback: false, reason: "" };
  }
  /**
   * Get environment variables for fallback provider
   */
  getFallbackEnv() {
    const fallbackProvider = this.routerConfig.fallback?.provider || "qwen";
    const providerConfig = this.routerConfig.providers[fallbackProvider];
    if (!providerConfig) {
      console.error(`[fallback] Provider not configured: ${fallbackProvider}`);
      return {};
    }
    return buildModelEnv(providerConfig);
  }
  /**
   * Check if fallback is available (has API key)
   */
  isFallbackAvailable() {
    const fallbackProvider = this.routerConfig.fallback?.provider || "qwen";
    const providerConfig = this.routerConfig.providers[fallbackProvider];
    if (!providerConfig) return false;
    return !!process.env[providerConfig.apiKeyEnv];
  }
  /**
   * Wrap a Claude process with fallback monitoring
   * Returns a function to spawn the process with automatic restart on failure
   */
  wrapProcess(command, args, options = {}) {
    let currentProcess = null;
    let stopped = false;
    const startProcess = () => {
      const env = { ...process.env, ...options.env };
      if (this.inFallback) {
        const fallbackEnv = this.getFallbackEnv();
        Object.assign(env, fallbackEnv);
        this.currentProvider = this.routerConfig.fallback?.provider || "qwen";
      }
      const isTTY = process.stdout.isTTY;
      currentProcess = spawn(command, args, {
        stdio: isTTY ? "inherit" : ["inherit", "pipe", "pipe"],
        env,
        cwd: options.cwd
      });
      if (!isTTY) {
        currentProcess.stdout?.on("data", (data) => {
          const text = data.toString();
          process.stdout.write(data);
          this.checkForErrors(text);
        });
        currentProcess.stderr?.on("data", (data) => {
          const text = data.toString();
          process.stderr.write(data);
          this.checkForErrors(text);
        });
      }
      currentProcess.on("exit", (code, _signal) => {
        if (stopped) return;
        if (code !== 0 && this.shouldRestart()) {
          console.log(
            `
[fallback] Process exited with code ${code}, restarting with fallback...`
          );
          this.activateFallback("exit_code");
          setTimeout(() => {
            if (!stopped) {
              startProcess();
            }
          }, this.config.restartDelayMs);
        }
      });
      return currentProcess;
    };
    return {
      start: startProcess,
      stop: () => {
        stopped = true;
        currentProcess?.kill();
      },
      isInFallback: () => this.inFallback,
      getCurrentProvider: () => this.currentProvider
    };
  }
  /**
   * Check output text for errors and trigger fallback if needed
   */
  checkForErrors(text) {
    this.errorBuffer += text;
    if (this.errorBuffer.length > 1e4) {
      this.errorBuffer = this.errorBuffer.slice(-5e3);
    }
    const { shouldFallback, reason } = this.detectError(text);
    if (shouldFallback) {
      const now = Date.now();
      if (now - this.lastErrorTime < 1e3) {
        this.errorCount++;
      } else {
        this.errorCount = 1;
      }
      this.lastErrorTime = now;
      if (this.errorCount >= 2 && !this.inFallback && this.isFallbackAvailable()) {
        console.log(`
[fallback] Detected error pattern: ${reason}`);
        this.activateFallback(reason);
      }
    }
  }
  /**
   * Activate fallback mode
   */
  activateFallback(reason) {
    if (this.inFallback) return;
    this.inFallback = true;
    this.restartCount++;
    this.currentProvider = this.routerConfig.fallback?.provider || "qwen";
    console.log(
      `[fallback] Switching to ${this.currentProvider} (reason: ${reason})`
    );
    if (this.config.onFallback) {
      this.config.onFallback(this.currentProvider, reason);
    }
  }
  /**
   * Check if we should restart
   */
  shouldRestart() {
    return this.config.enabled && this.restartCount < this.config.maxRestarts && this.isFallbackAvailable();
  }
  /**
   * Reset fallback state
   */
  reset() {
    this.inFallback = false;
    this.restartCount = 0;
    this.errorCount = 0;
    this.errorBuffer = "";
    this.currentProvider = "anthropic";
  }
  /**
   * Get current status
   */
  getStatus() {
    return {
      inFallback: this.inFallback,
      currentProvider: this.currentProvider,
      restartCount: this.restartCount,
      fallbackAvailable: this.isFallbackAvailable()
    };
  }
}
function spawnWithFallback(command, args, options = {}) {
  const monitor = new FallbackMonitor({
    onFallback: options.onFallback
  });
  const wrapper = monitor.wrapProcess(command, args, options);
  return wrapper.start();
}
function getEnvWithFallback() {
  const config = loadModelRouterConfig();
  const env = {};
  if (config.fallback?.enabled) {
    const fallbackProvider = config.providers[config.fallback.provider];
    if (fallbackProvider) {
      env["STACKMEMORY_FALLBACK_PROVIDER"] = config.fallback.provider;
      env["STACKMEMORY_FALLBACK_MODEL"] = fallbackProvider.model;
      env["STACKMEMORY_FALLBACK_URL"] = fallbackProvider.baseUrl || "";
      env["STACKMEMORY_FALLBACK_KEY_ENV"] = fallbackProvider.apiKeyEnv;
    }
  }
  return env;
}
export {
  FallbackMonitor,
  getEnvWithFallback,
  spawnWithFallback
};
//# sourceMappingURL=fallback-monitor.js.map
