// claude-adapter : déduit l'état fin d'une session (busy / waiting / idle) + le dialogue typé.
// Source PRIMAIRE = le registre ~/.claude/sessions (status stable inter-versions) ; l'écran reconstruit
// (@xterm/headless) sert uniquement à TYPER le dialogue et à couvrir le cas Trust (qui précède le
// registre). Un tracker par session vive claude, alimenté par le flux PTY (pas de re-replay du ring).
import type { SessionActivity, SessionDialog } from '@arboretum/shared';
import { ScreenReader } from './screen-reader.js';
import { classifyDialog } from './dialog-classifier.js';
import { findByPid } from './session-registry.js';

const DEBOUNCE_MS = 200; // coalescence des rafales d'output avant ré-évaluation
const POLL_MS = 700; // capte les transitions busy↔idle qui n'émettent pas d'output discriminant

export interface AdapterState {
  activity: SessionActivity | null;
  waitingFor: string | null;
  dialog: SessionDialog | null;
}

const EMPTY: AdapterState = { activity: null, waitingFor: null, dialog: null };

function sameState(a: AdapterState, b: AdapterState): boolean {
  return a.activity === b.activity && a.waitingFor === b.waitingFor && JSON.stringify(a.dialog) === JSON.stringify(b.dialog);
}

export class SessionActivityTracker {
  private readonly reader = new ScreenReader(120, 32);
  private state: AdapterState = EMPTY;
  private debounce: NodeJS.Timeout | null = null;
  private readonly poll: NodeJS.Timeout;
  private disposed = false;

  constructor(
    private readonly pid: number,
    private readonly sessionsDir: string,
    private readonly onChange: () => void,
  ) {
    this.poll = setInterval(() => this.evaluate(), POLL_MS);
    this.poll.unref();
  }

  feed(chunk: Uint8Array): void {
    if (this.disposed) return;
    void this.reader.feed(chunk);
    if (this.debounce) return;
    this.debounce = setTimeout(() => {
      this.debounce = null;
      this.evaluate();
    }, DEBOUNCE_MS);
    this.debounce.unref();
  }

  resize(cols: number, rows: number): void {
    if (!this.disposed) this.reader.resize(cols, rows);
  }

  snapshot(): AdapterState {
    return this.state;
  }

  /** Évalue l'état courant (debounce → snapshot fiable). Public pour les tests déterministes. */
  evaluate(): void {
    if (this.disposed) return;
    const reg = findByPid(this.sessionsDir, this.pid);
    const next = this.derive(reg?.status ?? null, reg?.waitingFor ?? null);
    if (!sameState(this.state, next)) {
      this.state = next;
      this.onChange();
    }
  }

  private derive(status: 'busy' | 'idle' | 'waiting' | null, waitingFor: string | null): AdapterState {
    if (status === 'busy') return { activity: 'busy', waitingFor: null, dialog: null };
    if (status === 'idle') return { activity: 'idle', waitingFor: null, dialog: null };
    if (status === 'waiting') {
      const c = classifyDialog(this.reader.snapshotLines());
      return { activity: 'waiting', waitingFor, dialog: c ? { kind: c.kind, waitingFor, options: c.options } : null };
    }
    // Pas (encore) de registre : seul le dialogue Trust le précède (spike S1).
    const c = classifyDialog(this.reader.snapshotLines());
    if (c?.kind === 'trust') return { activity: 'waiting', waitingFor: 'trust', dialog: { kind: 'trust', waitingFor: 'trust', options: c.options } };
    return EMPTY;
  }

  dispose(): void {
    this.disposed = true;
    if (this.debounce) clearTimeout(this.debounce);
    clearInterval(this.poll);
    this.reader.dispose();
  }
}
