import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { Logger } from "../monitoring/logger.js";
class IncrementalGarbageCollector {
  logger;
  config;
  stats;
  isRunning = false;
  cycleTimer = null;
  frameManager;
  constructor(frameManager, config = {}) {
    this.frameManager = frameManager;
    this.logger = new Logger("IncrementalGC");
    this.config = {
      framesPerCycle: config.framesPerCycle || 100,
      cycleInterval: config.cycleInterval || 6e4,
      // 60 seconds
      maxAge: config.maxAge || 30 * 24 * 60 * 60 * 1e3,
      // 30 days
      generations: {
        young: config.generations?.young || 24 * 60 * 60 * 1e3,
        // 1 day
        mature: config.generations?.mature || 7 * 24 * 60 * 60 * 1e3,
        // 7 days
        old: config.generations?.old || 30 * 24 * 60 * 60 * 1e3
        // 30 days
      }
    };
    this.stats = {
      totalFrames: 0,
      collectedFrames: 0,
      lastRunTime: 0,
      cycleCount: 0,
      avgCycleTime: 0,
      protectedFrames: 0
    };
    this.logger.info("Incremental GC initialized", this.config);
  }
  /**
   * Start the garbage collection cycle
   */
  start() {
    if (this.isRunning) {
      this.logger.warn("GC already running");
      return;
    }
    this.isRunning = true;
    this.logger.info(
      `Starting incremental GC with ${this.config.cycleInterval}ms intervals`
    );
    this.cycleTimer = setInterval(() => {
      this.runCycle().catch((error) => {
        this.logger.error("GC cycle failed", error);
      });
    }, this.config.cycleInterval);
  }
  /**
   * Stop the garbage collection cycle
   */
  stop() {
    if (this.cycleTimer) {
      clearInterval(this.cycleTimer);
      this.cycleTimer = null;
    }
    this.isRunning = false;
    this.logger.info("Incremental GC stopped");
  }
  /**
   * Run a single GC cycle
   */
  async runCycle() {
    const startTime = Date.now();
    this.logger.debug("Starting GC cycle", {
      cycle: this.stats.cycleCount + 1
    });
    try {
      const allFrames = await this.frameManager.getAllFrames();
      this.stats.totalFrames = allFrames.length;
      if (allFrames.length === 0) {
        this.logger.debug("No frames to collect");
        return;
      }
      const categorized = this.categorizeFrames(allFrames);
      const candidates = this.selectCollectionCandidates(categorized);
      const collected = await this.collectFramesIncremental(candidates);
      this.updateStats(startTime, collected.length);
      this.logger.info("GC cycle completed", {
        cycle: this.stats.cycleCount,
        collected: collected.length,
        protected: this.stats.protectedFrames,
        duration: Date.now() - startTime
      });
    } catch (error) {
      this.logger.error("GC cycle error", error);
    }
  }
  /**
   * Categorize frames by generation and protection status
   */
  categorizeFrames(frames) {
    const now = Date.now();
    const categories = { young: [], mature: [], old: [], protected: [] };
    for (const frame of frames) {
      const age = now - frame.created_at;
      if (this.isProtected(frame)) {
        categories.protected.push(frame);
        continue;
      }
      if (age < this.config.generations.young) {
        categories.young.push(frame);
      } else if (age < this.config.generations.mature) {
        categories.mature.push(frame);
      } else {
        categories.old.push(frame);
      }
    }
    this.stats.protectedFrames = categories.protected.length;
    this.logger.debug("Frame categorization", {
      young: categories.young.length,
      mature: categories.mature.length,
      old: categories.old.length,
      protected: categories.protected.length
    });
    return categories;
  }
  /**
   * Check if a frame should be protected from collection
   */
  isProtected(frame) {
    const currentRunId = this.frameManager.getCurrentRunId?.();
    if (frame.run_id === currentRunId) {
      return true;
    }
    if (frame.state === "active") {
      return true;
    }
    const recentThreshold = Date.now() - 60 * 60 * 1e3;
    if (frame.created_at > recentThreshold) {
      return true;
    }
    if (frame.outputs && Object.keys(frame.outputs).length > 0) {
      return true;
    }
    if (frame.depth === 0) {
      return true;
    }
    return false;
  }
  /**
   * Select collection candidates with priority ordering
   */
  selectCollectionCandidates(categorized) {
    const candidates = [];
    const emptyClosedFrames = categorized.old.filter(
      (f) => f.state === "closed" && (!f.outputs || Object.keys(f.outputs).length === 0)
    );
    const orphaned = [...categorized.mature, ...categorized.old].filter(
      (f) => this.isOrphaned(f)
    );
    const duplicates = this.findDuplicateTraces([
      ...categorized.mature,
      ...categorized.old
    ]);
    const oldMature = categorized.mature.filter((f) => {
      const age = Date.now() - f.created_at;
      return age > this.config.generations.mature * 0.8;
    });
    candidates.push(...emptyClosedFrames);
    candidates.push(...orphaned);
    candidates.push(...duplicates);
    candidates.push(...oldMature);
    const uniqueCandidates = Array.from(new Set(candidates));
    return uniqueCandidates.slice(0, this.config.framesPerCycle);
  }
  /**
   * Check if frame is orphaned (no dependencies)
   */
  isOrphaned(frame) {
    return !frame.parent_frame_id && frame.depth > 0 && frame.state === "closed";
  }
  /**
   * Find duplicate trace signatures
   */
  findDuplicateTraces(frames) {
    const signatureMap = /* @__PURE__ */ new Map();
    for (const frame of frames) {
      const signature = this.createTraceSignature(frame);
      if (!signatureMap.has(signature)) {
        signatureMap.set(signature, []);
      }
      signatureMap.get(signature).push(frame);
    }
    const duplicates = [];
    for (const [signature, frameList] of signatureMap) {
      if (frameList.length > 1) {
        frameList.sort((a, b) => b.timestamp - a.timestamp);
        duplicates.push(...frameList.slice(1));
      }
    }
    return duplicates;
  }
  /**
   * Create a signature for duplicate detection
   */
  createTraceSignature(frame) {
    const type = frame.type;
    const name = frame.name;
    const outputs = JSON.stringify(frame.outputs || {});
    const digestText = frame.digest_text || "";
    return `${type}:${name}:${outputs}:${digestText}`.toLowerCase();
  }
  /**
   * Collect frames incrementally to avoid blocking
   */
  async collectFramesIncremental(candidates) {
    const collected = [];
    const chunkSize = Math.min(10, Math.ceil(candidates.length / 10));
    for (let i = 0; i < candidates.length; i += chunkSize) {
      const chunk = candidates.slice(i, i + chunkSize);
      for (const frame of chunk) {
        try {
          await this.frameManager.deleteFrame(frame.frame_id);
          collected.push(frame);
          this.logger.debug(`Collected frame ${frame.frame_id}`, {
            age: Date.now() - frame.created_at,
            type: frame.type,
            reason: this.getCollectionReason(frame)
          });
        } catch (error) {
          this.logger.warn(`Failed to collect frame ${frame.frame_id}`, error);
        }
      }
      await new Promise((resolve) => setImmediate(resolve));
    }
    return collected;
  }
  /**
   * Get human-readable collection reason
   */
  getCollectionReason(frame) {
    const age = Date.now() - frame.created_at;
    const ageHours = Math.floor(age / (60 * 60 * 1e3));
    if (frame.state === "closed" && (!frame.outputs || Object.keys(frame.outputs).length === 0)) {
      return "empty-closed";
    }
    if (this.isOrphaned(frame)) return "orphaned";
    if (ageHours > 24 * 30) return `old (${ageHours}h)`;
    return "duplicate";
  }
  /**
   * Update GC statistics
   */
  updateStats(startTime, collectedCount) {
    const cycleTime = Date.now() - startTime;
    this.stats.cycleCount++;
    this.stats.collectedFrames += collectedCount;
    this.stats.lastRunTime = startTime;
    this.stats.avgCycleTime = (this.stats.avgCycleTime * (this.stats.cycleCount - 1) + cycleTime) / this.stats.cycleCount;
  }
  /**
   * Get GC statistics
   */
  getStats() {
    return { ...this.stats };
  }
  /**
   * Force a manual GC cycle
   */
  async forceCollection() {
    this.logger.info("Forcing manual GC cycle");
    await this.runCycle();
  }
  /**
   * Update GC configuration
   */
  updateConfig(newConfig) {
    this.config = { ...this.config, ...newConfig };
    this.logger.info("GC configuration updated", this.config);
    if (this.isRunning && newConfig.cycleInterval) {
      this.stop();
      this.start();
    }
  }
}
export {
  IncrementalGarbageCollector
};
//# sourceMappingURL=incremental-gc.js.map
