// PRE Web GUI — Slot-based inference queue
// Replaces the binary waitForIdle() gate with a concurrency-aware slot manager.
// User requests get priority; background work runs in secondary slots when VRAM permits.

const fs = require('fs');
const path = require('path');
const os = require('os');

const PRE_DIR = path.join(os.homedir(), '.pre');

// Priority levels (lower = higher priority)
const PRIORITY = {
  user: 0,
  cron: 1,
  background: 2,
  housekeeping: 3, // title gen, memory extract, etc.
};

/**
 * Determine how many inference slots the system can support.
 * Based on VRAM headroom: total RAM minus model size minus OS overhead.
 * Returns 1 on constrained systems, 2 on systems with ample memory.
 */
function _detectSlotCount() {
  try {
    // Read model quantization to estimate model size
    const contextFile = path.join(PRE_DIR, 'context');
    const ctx = parseInt(fs.readFileSync(contextFile, 'utf-8').trim(), 10) || 131072;

    // On macOS, check unified memory
    const totalMem = os.totalmem();
    const totalGB = totalMem / (1024 * 1024 * 1024);

    // Heuristic: q8_0 ~28GB, q4_K_M ~15GB. Assume q8_0 if total > 48GB.
    const modelGB = totalGB >= 48 ? 28 : 15;
    const osOverhead = 8;
    const headroom = totalGB - modelGB - osOverhead;

    // Need at least 16GB headroom for a second slot with reasonable context
    if (headroom >= 16) return 2;
    return 1;
  } catch {
    return 1;
  }
}

class SlotManager {
  constructor() {
    this.numSlots = _detectSlotCount();
    this._slots = new Array(this.numSlots).fill(null); // null = free, string = holder ID
    this._queue = []; // { priority, resolve, reject, holderId }
    this._holderId = 0;
    console.log(`[inference-queue] Initialized with ${this.numSlots} slot(s)`);
  }

  /**
   * Acquire an inference slot. Returns a handle with { slotId, release() }.
   * If no slot is available, queues the request by priority.
   * User requests can preempt background work if all slots are occupied.
   *
   * @param {string} priority - 'user', 'cron', 'background', 'housekeeping'
   * @param {AbortSignal} [signal] - Optional abort signal to cancel waiting
   * @returns {Promise<{slotId: number, release: Function}>}
   */
  acquireSlot(priority = 'background', signal) {
    const holderId = `${priority}_${++this._holderId}`;

    // Try to find a free slot
    const freeIdx = this._slots.indexOf(null);
    if (freeIdx !== -1) {
      this._slots[freeIdx] = holderId;
      return Promise.resolve({
        slotId: freeIdx,
        release: () => this._releaseSlot(freeIdx),
      });
    }

    // No free slot — queue the request
    return new Promise((resolve, reject) => {
      const entry = {
        priority: PRIORITY[priority] ?? PRIORITY.background,
        holderId,
        resolve,
        reject,
        signal,
      };

      // Insert in priority order (lower number = higher priority)
      let inserted = false;
      for (let i = 0; i < this._queue.length; i++) {
        if (entry.priority < this._queue[i].priority) {
          this._queue.splice(i, 0, entry);
          inserted = true;
          break;
        }
      }
      if (!inserted) this._queue.push(entry);

      // If aborted before slot is free, remove from queue
      if (signal) {
        const onAbort = () => {
          const idx = this._queue.indexOf(entry);
          if (idx !== -1) this._queue.splice(idx, 1);
          reject(new Error('Slot acquisition aborted'));
        };
        if (signal.aborted) {
          onAbort();
        } else {
          signal.addEventListener('abort', onAbort, { once: true });
        }
      }
    });
  }

  /**
   * Release a slot and drain the queue.
   */
  _releaseSlot(slotIdx) {
    this._slots[slotIdx] = null;

    // Drain: give the slot to the highest-priority waiter
    if (this._queue.length > 0) {
      const next = this._queue.shift();
      this._slots[slotIdx] = next.holderId;
      next.resolve({
        slotId: slotIdx,
        release: () => this._releaseSlot(slotIdx),
      });
    }
  }

  /**
   * Check if any user loops are active (backward compat with isUserLoopActive).
   */
  isUserActive() {
    return this._slots.some(s => s && s.startsWith('user_'));
  }

  /**
   * Returns a promise that resolves when no user slots are active.
   * Backward-compatible with the old waitForIdle() API.
   */
  waitForIdle() {
    if (!this.isUserActive()) return Promise.resolve();
    // Poll-free: queue a background request, it will resolve when a slot frees up
    return this.acquireSlot('background').then(handle => {
      handle.release(); // Immediately release — we just needed to wait
    });
  }

  /**
   * Get current status for debugging.
   */
  getStatus() {
    return {
      numSlots: this.numSlots,
      slots: this._slots.map(s => s || 'free'),
      queueLength: this._queue.length,
      queuePriorities: this._queue.map(e => e.holderId),
    };
  }
}

// Singleton instance
const slotManager = new SlotManager();

module.exports = { slotManager, PRIORITY };
