import * as fsp from 'fs/promises';
import * as path from 'path';
import type { PerfDiagEntry } from './diagnostics.models';
import {
  formatPerfDiagLine,
  pushRingBuffer,
  resolveDiagnosticsFilePath
} from './diagnostics.rules';

const DEFAULT_RING_CAPACITY = 120;
const FLUSH_DEBOUNCE_MS = 250;

export interface PerfDiagWriterOptions {
  userDataPath: string;
  sessionId: string;
  ringCapacity?: number;
}

export class PerfDiagWriter {
  private readonly filePath: string;
  private readonly ringCapacity: number;
  private readonly pendingLines: string[] = [];
  private ring: PerfDiagEntry[] = [];
  private flushTimer: NodeJS.Timeout | null = null;
  private flushInFlight: Promise<void> | null = null;
  private disabled = false;

  constructor(options: PerfDiagWriterOptions) {
    this.filePath = resolveDiagnosticsFilePath(options.userDataPath, options.sessionId);
    this.ringCapacity = options.ringCapacity ?? DEFAULT_RING_CAPACITY;
  }

  get snapshotFilePath(): string {
    return this.filePath;
  }

  get bufferedEntries(): readonly PerfDiagEntry[] {
    return this.ring;
  }

  append(entry: PerfDiagEntry): void {
    if (this.disabled) {
      return;
    }

    try {
      this.ring = pushRingBuffer(this.ring, entry, this.ringCapacity);
      this.pendingLines.push(`${formatPerfDiagLine(entry)}\n`);
      this.scheduleFlush();
    } catch {
      this.disabled = true;
    }
  }

  async flush(): Promise<void> {
    if (this.disabled || this.pendingLines.length === 0) {
      return;
    }

    if (this.flushInFlight) {
      await this.flushInFlight;
      return;
    }

    const lines = this.pendingLines.splice(0, this.pendingLines.length);

    this.flushInFlight = this.writeLines(lines)
      .catch(() => {
        this.disabled = true;
      })
      .finally(() => {
        this.flushInFlight = null;
      });

    await this.flushInFlight;
  }

  async flushSnapshot(label: string): Promise<void> {
    this.append({
      collectedAt: Date.now(),
      source: 'main',
      type: 'session',
      payload: {
        event: label,
        filePath: this.filePath,
        entries: this.ring
      }
    });

    await this.flush();
  }

  private scheduleFlush(): void {
    if (this.flushTimer) {
      return;
    }

    this.flushTimer = setTimeout(() => {
      this.flushTimer = null;
      void this.flush();
    }, FLUSH_DEBOUNCE_MS);
  }

  private async writeLines(lines: string[]): Promise<void> {
    await fsp.mkdir(path.dirname(this.filePath), { recursive: true });
    await fsp.appendFile(this.filePath, lines.join(''), 'utf8');
  }
}
