import { describe, it, expect } from 'vitest';
import { OverlaySyncService } from 'apps/companion/main/automation/application/services/OverlaySyncService';
import type { AutomationEvent } from 'apps/companion/main/automation/application/ports/IAutomationEventPublisher';
import type {
  IAutomationLifecycleEmitter,
  LifecycleCallback,
} from 'core/automation/infrastructure//IAutomationLifecycleEmitter';
import type {
  OverlayAction,
  ActionAck,
} from 'apps/companion/main/automation/application/ports/IOverlaySyncPort';

class TestLifecycleEmitter implements IAutomationLifecycleEmitter {
  private callbacks: Set<LifecycleCallback> = new Set();

  onLifecycle(cb: LifecycleCallback): void {
    this.callbacks.add(cb);
  }

  offLifecycle(cb: LifecycleCallback): void {
    this.callbacks.delete(cb);
  }

  async emit(event: AutomationEvent): Promise<void> {
    for (const cb of Array.from(this.callbacks)) {
      await cb(event);
    }
  }
}

class RecordingPublisher {
  public events: AutomationEvent[] = [];

  async publish(event: AutomationEvent): Promise<void> {
    this.events.push(event);
  }
}

describe('Overlay lifecycle (integration)', () => {
  it('emits modal-opened and confirms after action-started in sane order', async () => {
    const lifecycleEmitter = new TestLifecycleEmitter();
    const publisher = new RecordingPublisher();
    type LoggerLike = {
      debug: (...args: unknown[]) => void;
      info: (...args: unknown[]) => void;
      warn: (...args: unknown[]) => void;
      error: (...args: unknown[]) => void;
      fatal: (...args: unknown[]) => void;
      child: (...args: unknown[]) => LoggerLike;
      flush: (...args: unknown[]) => Promise<void>;
    };
    const logger = console as unknown as LoggerLike;

    const service = new OverlaySyncService({
      lifecycleEmitter,
      publisher,
      logger,
      defaultTimeoutMs: 1_000,
    });

    const action: OverlayAction = {
      id: 'hosted-session',
      label: 'Starting hosted session',
    };

    const ackPromise: Promise<ActionAck> = service.startAction(action);

    expect(publisher.events.length).toBe(1);
    const first = publisher.events[0]!;
    expect(first.type).toBe('modal-opened');
    expect(first.actionId).toBe('hosted-session');

    await lifecycleEmitter.emit({
      type: 'panel-attached',
      actionId: 'hosted-session',
      timestamp: Date.now(),
      payload: { selector: '#gridpilot-overlay' },
    });

    await lifecycleEmitter.emit({
      type: 'action-started',
      actionId: 'hosted-session',
      timestamp: Date.now(),
    });

    const ack = await ackPromise;
    expect(ack.id).toBe('hosted-session');
    expect(ack.status).toBe('confirmed');

    expect(publisher.events[0]!.type).toBe('modal-opened');
    expect(publisher.events[0]!.actionId).toBe('hosted-session');
  });

  it('emits panel-missing when cancelAction is called', async () => {
    const lifecycleEmitter = new TestLifecycleEmitter();
    const publisher = new RecordingPublisher();
    type LoggerLike = {
      debug: (...args: unknown[]) => void;
      info: (...args: unknown[]) => void;
      warn: (...args: unknown[]) => void;
      error: (...args: unknown[]) => void;
      fatal: (...args: unknown[]) => void;
      child: (...args: unknown[]) => LoggerLike;
      flush: (...args: unknown[]) => Promise<void>;
    };
    const logger = console as unknown as LoggerLike;

    const service = new OverlaySyncService({
      lifecycleEmitter,
      publisher,
      logger,
    });

    await service.cancelAction('hosted-session-cancel');

    expect(publisher.events.length).toBe(1);
    const ev = publisher.events[0]!;
    expect(ev.type).toBe('panel-missing');
    expect(ev.actionId).toBe('hosted-session-cancel');
  });
});