import { createHash } from 'node:crypto';
import { EventEmitter } from 'node:events';
import type { CmuxClient } from './cmux-client.js';
import { computeDiff } from '../utils/text-differ.js';
import type { DiffPatch } from '../protocol/messages.js';

interface PollTarget {
  workspace: string;
  surface: string;
  type: 'terminal' | 'browser';
  subscribers: Set<string>;
  lastHash: string;
  lastLines: string[];
  idleCount: number;
  timer: ReturnType<typeof setTimeout> | null;
}

interface ScreenUpdate {
  surface: string;
  workspace: string;
  full: boolean;
  lines: string[];
  patches?: DiffPatch[];
}

export class ScreenPoller extends EventEmitter {
  private client: CmuxClient;
  private targets = new Map<string, PollTarget>();
  private basePollRate: number;
  private fullSyncInterval = 30000;
  private lastFullSync = new Map<string, number>();

  static readonly IDLE_THRESHOLD = 3;
  static readonly DEEP_IDLE_MS = 10000;

  constructor(client: CmuxClient, pollRate = 200) {
    super();
    this.client = client;
    this.basePollRate = pollRate;
  }

  subscribe(clientId: string, workspace: string, surface: string): void {
    const key = `${workspace}:${surface}`;
    let target = this.targets.get(key);

    if (!target) {
      target = {
        workspace,
        surface,
        type: 'terminal',
        subscribers: new Set(),
        lastHash: '',
        lastLines: [],
        idleCount: 0,
        timer: null,
      };
      this.targets.set(key, target);
    }

    target.subscribers.add(clientId);

    if (!target.timer) {
      this.schedulePoll(key, target, 0);
    }
  }

  unsubscribe(clientId: string, surface?: string): void {
    for (const [key, target] of this.targets) {
      if (surface && !key.endsWith(`:${surface}`)) continue;

      target.subscribers.delete(clientId);

      if (target.subscribers.size === 0) {
        if (target.timer) clearTimeout(target.timer);
        target.timer = null;
        this.targets.delete(key);
        this.lastFullSync.delete(key);
      }
    }
  }

  resetToFast(surface: string): void {
    for (const [key, target] of this.targets) {
      if (key.endsWith(`:${surface}`)) {
        target.idleCount = 0;
        // Reschedule with fast rate
        if (target.timer) clearTimeout(target.timer);
        this.schedulePoll(key, target, this.basePollRate);
      }
    }
  }

  private getInterval(target: PollTarget): number {
    if (target.idleCount >= ScreenPoller.IDLE_THRESHOLD) {
      const idleTimeMs = target.idleCount * this.basePollRate;
      if (idleTimeMs > ScreenPoller.DEEP_IDLE_MS) return 2000;
      return 1000;
    }
    return this.basePollRate;
  }

  private schedulePoll(key: string, target: PollTarget, delay: number): void {
    target.timer = setTimeout(() => this.poll(key, target), delay);
  }

  private async poll(key: string, target: PollTarget): Promise<void> {
    if (target.subscribers.size === 0) return;

    if (target.type === 'browser') {
      await this.pollBrowser(key, target);
    } else {
      await this.pollTerminal(key, target);
    }

    if (target.subscribers.size > 0) {
      const delay = target.type === 'browser' ? 2000 : this.getInterval(target);
      this.schedulePoll(key, target, delay);
    }
  }

  private async pollTerminal(key: string, target: PollTarget): Promise<void> {
    try {
      const content = await this.client.readScreen(target.workspace, target.surface);
      const hash = createHash('sha256').update(content).digest('hex');

      const now = Date.now();
      const needsFullSync =
        !this.lastFullSync.has(key) ||
        now - this.lastFullSync.get(key)! > this.fullSyncInterval;

      if (hash !== target.lastHash) {
        target.idleCount = 0;
        const newLines = content.split('\n');

        const update: ScreenUpdate = {
          surface: target.surface,
          workspace: target.workspace,
          full: target.lastLines.length === 0 || needsFullSync,
          lines: newLines,
        };

        if (!update.full && target.lastLines.length > 0) {
          update.patches = computeDiff(target.lastLines, newLines);
        }

        if (needsFullSync) {
          this.lastFullSync.set(key, now);
        }

        target.lastHash = hash;
        target.lastLines = newLines;

        this.emit('update', update);
      } else {
        target.idleCount++;
      }
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      if (msg.includes('not a terminal') || msg.includes('invalid_params')) {
        // Switch to browser screenshot mode
        target.type = 'browser';
        target.lastHash = '';
      } else {
        console.error(`[poller] ${key}:`, msg);
      }
    }
  }

  private async pollBrowser(key: string, target: PollTarget): Promise<void> {
    try {
      const { data, mime } = await this.client.browserScreenshot(target.workspace, target.surface);
      const hash = createHash('sha256').update(data).digest('hex');
      if (hash !== target.lastHash) {
        target.lastHash = hash;
        target.idleCount = 0;
        this.emit('browser-update', { surface: target.surface, workspace: target.workspace, data, mime });
      } else {
        target.idleCount++;
      }
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      console.error(`[poller] browser ${key}:`, msg);
    }
  }

  getLastLines(workspace: string, surface: string): string[] | null {
    const key = `${workspace}:${surface}`;
    const target = this.targets.get(key);
    return target && target.lastLines.length > 0 ? target.lastLines : null;
  }

  getSubscriberCount(surface: string): number {
    for (const [key, target] of this.targets) {
      if (key.endsWith(`:${surface}`)) return target.subscribers.size;
    }
    return 0;
  }

  destroy(): void {
    for (const target of this.targets.values()) {
      if (target.timer) clearTimeout(target.timer);
    }
    this.targets.clear();
    this.removeAllListeners();
  }
}
