import type { InferenceRequest, InferenceResult, QueuePriority } from "../shared/types";
import { hashText } from "../shared/hash";
import { ResultCache } from "./cache";

type Runner = (requests: InferenceRequest[]) => Promise<InferenceResult[]>;

interface Pending {
  request: InferenceRequest;
  resolve: (result: InferenceResult) => void;
  reject: (error: unknown) => void;
}

export class InferenceQueue {
  private readonly pending: Pending[] = [];
  private scheduled = false;
  private running = false;

  constructor(
    private readonly run: Runner,
    private readonly cache = new ResultCache(),
    private readonly maxSize = 500,
    private readonly batchSize = 8,
    private readonly debounceMs = 20
  ) {}

  enqueue(request: InferenceRequest): Promise<InferenceResult> {
    const cached = this.cache.get(request.text);
    if (cached) {
      const result = { ...cached, requestId: request.requestId, id: request.id, navigationId: request.navigationId, modelDurationMs: 0 };
      this.logCompletion(request, result, true);
      return Promise.resolve(result);
    }

    if (this.pending.length >= this.maxSize) this.dropLowestPriority();
    return new Promise<InferenceResult>((resolve, reject) => {
      this.pending.push({ request, resolve, reject });
      this.pending.sort((a, b) => a.request.priority - b.request.priority);
      this.schedule();
    });
  }

  invalidate(tabId?: number, navigationId?: string): void {
    for (let index = this.pending.length - 1; index >= 0; index -= 1) {
      const request = this.pending[index]?.request;
      if (!request) continue;
      if ((tabId === undefined || request.tabId === tabId) && (navigationId === undefined || request.navigationId === navigationId)) {
        this.pending[index]?.reject(new Error("Inference request invalidated"));
        this.pending.splice(index, 1);
      }
    }
  }

  get pendingCount(): number { return this.pending.length; }
  get cacheSize(): number { return this.cache.size; }

  private schedule(): void {
    if (this.scheduled || this.running) return;
    this.scheduled = true;
    setTimeout(() => { this.scheduled = false; void this.flush(); }, this.debounceMs);
  }

  private async flush(): Promise<void> {
    if (this.running || this.pending.length === 0) return;
    this.running = true;
    const batch = this.pending.splice(0, this.batchSize);
    try {
      const results = await this.run(batch.map((item) => item.request));
      const byRequest = new Map(results.map((result) => [result.requestId, result]));
      for (const item of batch) {
        const result = byRequest.get(item.request.requestId);
        if (!result) {
          const error = new Error("Classifier returned no result");
          this.logFailure(item.request, error);
          item.reject(error);
        } else {
          this.cache.set(item.request.text, result);
          this.logCompletion(item.request, result, false);
          item.resolve(result);
        }
      }
    } catch (error) {
      batch.forEach((item) => {
        this.logFailure(item.request, error);
        item.reject(error);
      });
    } finally {
      this.running = false;
      if (this.pending.length > 0) this.schedule();
    }
  }

  private dropLowestPriority(): void {
    let worstIndex = 0;
    for (let index = 1; index < this.pending.length; index += 1) {
      const current = this.pending[index]?.request.priority ?? 0;
      const worst = this.pending[worstIndex]?.request.priority ?? 0;
      if (current > worst) worstIndex = index;
    }
    this.pending[worstIndex]?.reject(new Error("Inference queue is full"));
    this.pending.splice(worstIndex, 1);
  }

  private logCompletion(request: InferenceRequest, result: InferenceResult, cacheHit: boolean): void {
    console.debug("[VibeGuard] Post analysis completed", {
      id: request.id,
      text: request.text,
      textHash: hashText(request.text),
      result,
      modelDurationMs: result.modelDurationMs ?? 0,
      cacheHit
    });
  }

  private logFailure(request: InferenceRequest, error: unknown): void {
    console.error("[VibeGuard] Post analysis failed", {
      id: request.id,
      text: request.text,
      textHash: hashText(request.text),
      error: String(error)
    });
  }
}

export function requestKey(request: InferenceRequest): string {
  return `${request.tabId ?? "global"}:${request.navigationId}:${request.id}:${hashText(request.text)}`;
}

export function priorityFor(visible: boolean, activeTab: boolean): QueuePriority {
  if (visible && activeTab) return 0;
  if (activeTab) return 1;
  return visible ? 2 : 3;
}
