import type { WorkerDependencies, WorkerService } from './contracts.js'
import type { WorkerEnvironment } from './environment.js'

export function composeWorker(environment: WorkerEnvironment, dependencies: WorkerDependencies): WorkerService {
  const controller = new AbortController()
  let run: Promise<void> | undefined
  let stop: Promise<void> | undefined

  return {
    async start() {
      if (run) return
      if (controller.signal.aborted) throw new Error('Worker cannot restart after shutdown')
      dependencies.health.setStatus('starting')
      dependencies.metrics.increment('worker_starts_total')
      run = dependencies.loop.start({ environment, signal: controller.signal })
      dependencies.health.setStatus('ready')
      run.catch((error: unknown) => {
        dependencies.health.setStatus('failed', error instanceof Error ? error.message : 'Worker loop failed')
        dependencies.metrics.increment('worker_failures_total')
      })
    },

    async stop(reason = 'shutdown') {
      if (stop) return stop
      stop = (async () => {
        dependencies.health.setStatus('stopping', reason)
        dependencies.metrics.increment('worker_shutdowns_total', { reason })
        controller.abort(reason)
        if (run) {
          let timer: ReturnType<typeof setTimeout> | undefined
          const timeout = new Promise<never>((_, reject) => {
            timer = (dependencies.setTimer ?? setTimeout)(
              () => reject(new Error(`Worker shutdown exceeded ${environment.shutdownTimeoutMs}ms`)),
              environment.shutdownTimeoutMs,
            )
          })
          try {
            await Promise.race([run, timeout])
          } finally {
            if (timer) (dependencies.clearTimer ?? clearTimeout)(timer)
          }
        }
        dependencies.health.setStatus('stopped', reason)
      })()
      return stop
    },

    health: () => dependencies.health.snapshot(),
  }
}

export type SignalSource = Pick<NodeJS.Process, 'on' | 'off'>

export function installGracefulShutdown(
  worker: WorkerService,
  source: SignalSource = process,
  onError: (error: unknown) => void = console.error,
): () => void {
  const handlers = new Map<NodeJS.Signals, () => void>()
  for (const signal of ['SIGINT', 'SIGTERM'] as const) {
    const handler = () => void worker.stop(signal).catch(onError)
    handlers.set(signal, handler)
    source.on(signal, handler)
  }
  return () => {
    for (const [signal, handler] of handlers) source.off(signal, handler)
  }
}
