import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { spawn, execSync } from "child_process";
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
import { join, dirname } from "path";
import {
  DEFAULT_SERVER_CONFIG
} from "./types.js";
import { createPredictionClient } from "./prediction-client.js";
import { logger } from "../../core/monitoring/logger.js";
const HOME = process.env["HOME"] || "/tmp";
const PID_FILE = join(HOME, ".stackmemory", "sweep", "server.pid");
const LOG_FILE = join(HOME, ".stackmemory", "sweep", "server.log");
class SweepServerManager {
  config;
  process = null;
  constructor(config = {}) {
    this.config = { ...DEFAULT_SERVER_CONFIG, ...config };
    if (!this.config.modelPath) {
      this.config.modelPath = join(
        HOME,
        ".stackmemory",
        "models",
        "sweep",
        "sweep-next-edit-1.5b.q8_0.v2.gguf"
      );
    }
  }
  /**
   * Find llama-server executable
   */
  findLlamaServer() {
    const candidates = [
      "llama-server",
      "llama.cpp/llama-server",
      "/usr/local/bin/llama-server",
      "/opt/homebrew/bin/llama-server",
      join(HOME, ".local", "bin", "llama-server")
    ];
    for (const cmd of candidates) {
      try {
        execSync(`which ${cmd}`, { stdio: "ignore" });
        return cmd;
      } catch {
        if (existsSync(cmd)) {
          return cmd;
        }
      }
    }
    return null;
  }
  /**
   * Start the llama-server
   */
  async startServer() {
    const status = await this.getStatus();
    if (status.running) {
      return status;
    }
    if (!existsSync(this.config.modelPath)) {
      throw new Error(
        `Model not found: ${this.config.modelPath}
Download with: huggingface-cli download sweepai/sweep-next-edit-1.5B sweep-next-edit-1.5b.q8_0.v2.gguf --local-dir ~/.stackmemory/models/sweep`
      );
    }
    const llamaServer = this.findLlamaServer();
    if (!llamaServer) {
      throw new Error(
        "llama-server not found. Install with:\n  brew install llama.cpp\nor build from source: https://github.com/ggerganov/llama.cpp"
      );
    }
    const logDir = dirname(LOG_FILE);
    if (!existsSync(logDir)) {
      mkdirSync(logDir, { recursive: true });
    }
    const args = [
      "-m",
      this.config.modelPath,
      "--port",
      String(this.config.port),
      "--host",
      this.config.host,
      "-c",
      String(this.config.contextSize)
    ];
    if (this.config.threads) {
      args.push("-t", String(this.config.threads));
    }
    if (this.config.gpuLayers && this.config.gpuLayers > 0) {
      args.push("-ngl", String(this.config.gpuLayers));
    }
    logger.info("Starting Sweep server", { llamaServer, args });
    this.process = spawn(llamaServer, args, {
      detached: true,
      stdio: ["ignore", "pipe", "pipe"]
    });
    if (this.process.pid) {
      const pidDir = dirname(PID_FILE);
      if (!existsSync(pidDir)) {
        mkdirSync(pidDir, { recursive: true });
      }
      writeFileSync(
        PID_FILE,
        JSON.stringify({
          pid: this.process.pid,
          port: this.config.port,
          host: this.config.host,
          modelPath: this.config.modelPath,
          startedAt: Date.now()
        })
      );
    }
    this.process.unref();
    const ready = await this.waitForReady(1e4);
    if (!ready) {
      await this.stopServer();
      throw new Error("Server failed to start within timeout");
    }
    return this.getStatus();
  }
  /**
   * Wait for server to be ready
   */
  async waitForReady(timeoutMs) {
    const client = createPredictionClient({
      port: this.config.port,
      host: this.config.host
    });
    const startTime = Date.now();
    while (Date.now() - startTime < timeoutMs) {
      if (await client.checkHealth()) {
        return true;
      }
      await new Promise((resolve) => setTimeout(resolve, 500));
    }
    return false;
  }
  /**
   * Stop the server
   */
  async stopServer() {
    const status = await this.getStatus();
    if (!status.running || !status.pid) {
      return;
    }
    try {
      process.kill(status.pid, "SIGTERM");
      await new Promise((resolve) => {
        const checkInterval = setInterval(() => {
          try {
            process.kill(status.pid, 0);
          } catch {
            clearInterval(checkInterval);
            resolve();
          }
        }, 100);
        setTimeout(() => {
          clearInterval(checkInterval);
          try {
            process.kill(status.pid, "SIGKILL");
          } catch {
          }
          resolve();
        }, 5e3);
      });
    } catch (error) {
      logger.warn("Error stopping server", { error });
    }
    try {
      if (existsSync(PID_FILE)) {
        const { unlinkSync } = await import("fs");
        unlinkSync(PID_FILE);
      }
    } catch {
    }
  }
  /**
   * Get server status
   */
  async getStatus() {
    if (!existsSync(PID_FILE)) {
      return { running: false };
    }
    try {
      const data = JSON.parse(readFileSync(PID_FILE, "utf-8"));
      const { pid, port, host, modelPath, startedAt } = data;
      try {
        process.kill(pid, 0);
      } catch {
        return { running: false };
      }
      const client = createPredictionClient({ port, host });
      const healthy = await client.checkHealth();
      return {
        running: healthy,
        pid,
        port,
        host,
        modelPath,
        startedAt
      };
    } catch {
      return { running: false };
    }
  }
  /**
   * Check server health
   */
  async checkHealth() {
    const client = createPredictionClient({
      port: this.config.port,
      host: this.config.host
    });
    return client.checkHealth();
  }
}
function createServerManager(config) {
  return new SweepServerManager(config);
}
export {
  SweepServerManager,
  createServerManager
};
//# sourceMappingURL=sweep-server-manager.js.map
