// Flush ACK ciblé (Attachment.flushAck) : relance un PTY mis en pause au retour-visible d'une cellule.
// On teste la logique d'ACK sans WebSocket réel (sendControl est public → stubbable).
import { describe, it, expect, vi } from 'vitest';
import { WsClient, Attachment, type TerminalSink } from '../src/lib/ws-client';

const sink: TerminalSink = {
  write: () => {},
  reset: () => {},
  onDetached: () => {},
  onControlChanged: () => {},
};

describe('ws-client · flush ACK ciblé (Attachment.flushAck)', () => {
  it('envoie un unique ACK cumulatif du reliquat traité, puis est idempotent', () => {
    const client = new WsClient();
    const send = vi.spyOn(client, 'sendControl').mockImplementation(() => {});
    const att = new Attachment(client, 'sid', 'interactive', sink, 80, 24);
    att.channel = 2;
    att.processedBytes = 1000;

    att.flushAck();
    expect(send).toHaveBeenCalledTimes(1);
    expect(send).toHaveBeenCalledWith({ type: 'ack', channel: 2, bytes: 1000 });
    expect(att.lastAckBytes).toBe(1000);

    // rien de neuf traité → aucun ACK supplémentaire (n'ACK QUE du réellement traité)
    att.flushAck();
    expect(send).toHaveBeenCalledTimes(1);

    // nouveau reliquat traité → ACK cumulatif mis à jour
    att.processedBytes = 1500;
    att.flushAck();
    expect(send).toHaveBeenCalledTimes(2);
    expect(send).toHaveBeenLastCalledWith({ type: 'ack', channel: 2, bytes: 1500 });
  });

  it('no-op si le canal n’est pas attaché (-1) ou si l’attachment est fermé', () => {
    const client = new WsClient();
    const send = vi.spyOn(client, 'sendControl').mockImplementation(() => {});

    const notAttached = new Attachment(client, 'a', 'interactive', sink, 80, 24); // channel = -1
    notAttached.processedBytes = 500;
    notAttached.flushAck();

    const closed = new Attachment(client, 'b', 'interactive', sink, 80, 24);
    closed.channel = 3;
    closed.processedBytes = 500;
    closed.closed = true;
    closed.flushAck();

    expect(send).not.toHaveBeenCalled();
  });
});
