import chokidar from 'chokidar';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { OFFSETS_PATH, ensureDirs } from './config';
import { Parser, ParsedEvent, PricingTable } from './parsers/types';
import { claudeParser } from './parsers/claude';
import { codexParser } from './parsers/codex';

interface Offset {
  inode: number;
  size: number;
}

type Offsets = Record<string, Offset>;

const PARSERS: Parser[] = [claudeParser, codexParser];

const TURN_COUNTER_MAX = 1000;

export class JsonlWatcher {
  private offsets: Offsets = {};
  private timer: NodeJS.Timeout | null = null;
  private pending: ParsedEvent[] = [];
  private flushHandler: ((events: ParsedEvent[]) => void) | null = null;
  private pricing: PricingTable | null = null;
  private watcher: chokidar.FSWatcher | null = null;
  private offsetsDirty = false;
  private offsetsTimer: NodeJS.Timeout | null = null;
  // session 内 turn 计数（process 生命周期内累计；进程重启会重置，但 rawMessageId 唯一可去重）
  private turnCounters: Map<string, number> = new Map();

  constructor(private readonly batchIntervalMs: number) {
    this.loadOffsets();
  }

  setPricing(pricing: PricingTable | null) {
    this.pricing = pricing;
  }

  onFlush(handler: (events: ParsedEvent[]) => void) {
    this.flushHandler = handler;
  }

  start() {
    const home = os.homedir();
    const paths = [
      path.join(home, '.claude', 'projects', '**', '*.jsonl'),
      path.join(home, '.codex', 'sessions', '**', '*.jsonl'),
      path.join(home, '.codex', 'logs', '**', '*.jsonl'),
    ];
    this.watcher = chokidar.watch(paths, {
      persistent: true,
      ignoreInitial: false,
      awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 100 },
    });
    this.watcher.on('add', (p) => this.scan(p));
    this.watcher.on('change', (p) => this.scan(p));
    this.timer = setInterval(() => this.flush(), this.batchIntervalMs);
  }

  stop() {
    if (this.timer) clearInterval(this.timer);
    if (this.offsetsTimer) clearTimeout(this.offsetsTimer);
    if (this.watcher) this.watcher.close();
    this.flush();
    // 退出前一次性 flush offsets（debounce 可能还有未持久化的）
    if (this.offsetsDirty) this.persistOffsets();
  }

  private scan(filePath: string) {
    try {
      const stat = fs.statSync(filePath);
      const prev = this.offsets[filePath];
      let startOffset = 0;
      if (prev && prev.inode === stat.ino && prev.size <= stat.size) {
        startOffset = prev.size;
      }
      if (startOffset >= stat.size) return;

      const fd = fs.openSync(filePath, 'r');
      const buf = Buffer.alloc(stat.size - startOffset);
      fs.readSync(fd, buf, 0, buf.length, startOffset);
      fs.closeSync(fd);
      const text = buf.toString('utf8');

      // 行可能不完整：只取最后一个换行符之前的内容
      const lastNewline = text.lastIndexOf('\n');
      if (lastNewline < 0) return;
      const completePart = text.slice(0, lastNewline);
      const consumedSize = startOffset + Buffer.byteLength(completePart, 'utf8') + 1; // +1 for \n

      const tool = inferTool(filePath);
      const parser = PARSERS.find((p) => p.tool === tool);
      if (!parser) return;

      for (const line of completePart.split('\n')) {
        if (!line.trim()) continue;
        const ev = parser.parseLine(line, filePath, this.pricing);
        if (ev) {
          const next = (this.turnCounters.get(ev.sessionId) ?? 0) + 1;
          // LRU 风格：set 前 delete 让最近使用的 key 保持在 Map 尾部；超过 cap 时驱逐最早的
          this.turnCounters.delete(ev.sessionId);
          this.turnCounters.set(ev.sessionId, next);
          if (this.turnCounters.size > TURN_COUNTER_MAX) {
            const oldest = this.turnCounters.keys().next().value;
            if (oldest !== undefined) this.turnCounters.delete(oldest);
          }
          ev.turnIndex = next;
          this.pending.push(ev);
        }
      }
      this.offsets[filePath] = { inode: stat.ino, size: consumedSize };
      this.scheduleSaveOffsets();
    } catch (err: any) {
      console.warn(`[ffctk] scan failed for ${filePath}: ${err.message}`);
    }
  }

  /** 1s debounce：高频文件改动场景下，offsets.json 不会被反复 fsync */
  private scheduleSaveOffsets() {
    this.offsetsDirty = true;
    if (this.offsetsTimer) return;
    this.offsetsTimer = setTimeout(() => {
      this.offsetsTimer = null;
      if (this.offsetsDirty) this.persistOffsets();
    }, 1000);
  }

  private persistOffsets() {
    fs.writeFileSync(OFFSETS_PATH, JSON.stringify(this.offsets), { mode: 0o600 });
    this.offsetsDirty = false;
  }

  private flush() {
    if (this.pending.length === 0 || !this.flushHandler) return;
    const events = this.pending.splice(0);
    this.flushHandler(events);
  }

  private loadOffsets() {
    ensureDirs();
    if (!fs.existsSync(OFFSETS_PATH)) return;
    try {
      this.offsets = JSON.parse(fs.readFileSync(OFFSETS_PATH, 'utf8'));
    } catch {
      this.offsets = {};
    }
  }
}

function inferTool(filePath: string): 'claude-code' | 'codex-cli' {
  if (filePath.includes('.claude')) return 'claude-code';
  return 'codex-cli';
}
