
import { AutomationEventPublisherPort, AutomationEvent } from '../ports/AutomationEventPublisherPort';
import { AutomationLifecycleEmitterPort, LifecycleCallback } from '../ports/AutomationLifecycleEmitterPort';
import { LoggerPort } from '../ports/LoggerPort';
import type { IAsyncApplicationService } from '@core/shared/application';
import { OverlayAction, OverlaySyncPort } from '../ports/OverlaySyncPort';
import { ActionAck } from '../ports/IOverlaySyncPort';

type ConstructorArgs = {
  lifecycleEmitter: AutomationLifecycleEmitterPort
  publisher: AutomationEventPublisherPort
  logger: LoggerPort
  initialPanelWaitMs?: number
  maxPanelRetries?: number
  backoffFactor?: number
  defaultTimeoutMs?: number
}

export class OverlaySyncService
  implements OverlaySyncPort, IAsyncApplicationService<OverlayAction, ActionAck>
{
  private lifecycleEmitter: AutomationLifecycleEmitterPort
  private publisher: AutomationEventPublisherPort
  private logger: LoggerPort
  private initialPanelWaitMs: number
  private maxPanelRetries: number
  private backoffFactor: number
  private defaultTimeoutMs: number

  constructor(args: ConstructorArgs) {
    this.lifecycleEmitter = args.lifecycleEmitter
    this.publisher = args.publisher
    this.logger = args.logger
    this.initialPanelWaitMs = args.initialPanelWaitMs ?? 500
    this.maxPanelRetries = args.maxPanelRetries ?? 3
    this.backoffFactor = args.backoffFactor ?? 2
    this.defaultTimeoutMs = args.defaultTimeoutMs ?? 5000
  }

  async execute(action: OverlayAction): Promise<ActionAck> {
    return this.startAction(action)
  }
 
  async startAction(action: OverlayAction): Promise<ActionAck> {
    const timeoutMs = action.timeoutMs ?? this.defaultTimeoutMs
    const seenEvents: AutomationEvent[] = []
    let settled = false

    const cb: LifecycleCallback = async (ev) => {
      seenEvents.push(ev)
      if (ev.type === 'action-started' && ev.actionId === action.id) {
        if (!settled) {
          settled = true
          cleanup()
          resolveAck({ id: action.id, status: 'confirmed' })
        }
      }
    }

    const cleanup = () => {
      try {
        this.lifecycleEmitter.offLifecycle(cb)
      } catch {
        // ignore
      }
    }

    let resolveAck: (ack: ActionAck) => void = () => {}
    const promise = new Promise<ActionAck>((resolve) => {
      resolveAck = resolve
      try {
        this.lifecycleEmitter.onLifecycle(cb)
      } catch (e) {
        const error = e instanceof Error ? e : new Error(String(e))
        this.logger?.error?.('OverlaySyncService: failed to subscribe to lifecycleEmitter', error, {
          actionId: action.id,
        })
      }
    })

    try {
      void this.publisher.publish({
        type: 'modal-opened',
        timestamp: Date.now(),
        payload: { actionId: action.id, label: action.label },
        actionId: action.id,
      } as AutomationEvent)
    } catch (e) {
      const error = e instanceof Error ? e : new Error(String(e))
      this.logger?.warn?.('OverlaySyncService: publisher.publish failed', {
        actionId: action.id,
        error,
      })
    }

    const timeoutPromise = new Promise<ActionAck>((res) => {
      setTimeout(() => {
        if (!settled) {
          settled = true
          cleanup()
          this.logger?.info?.('OverlaySyncService: timeout waiting for confirmation', {
            actionId: action.id,
            timeoutMs,
          })
          const lastEvents = seenEvents.slice(-10)
          this.logger?.debug?.('OverlaySyncService: recent lifecycle events', {
            actionId: action.id,
            events: lastEvents,
          })
          res({ id: action.id, status: 'tentative', reason: 'timeout' })
        }
      }, timeoutMs)
    })

    return Promise.race([promise, timeoutPromise])
  }

  async cancelAction(actionId: string): Promise<void> {
    try {
      await this.publisher.publish({
        type: 'panel-missing',
        timestamp: Date.now(),
        actionId,
      } as AutomationEvent)
    } catch (e) {
      const error = e instanceof Error ? e : new Error(String(e))
      this.logger?.warn?.('OverlaySyncService: cancelAction publish failed', {
        actionId,
        error,
      })
    }
  }
}