import { gpiod, GPIOD_EDGE_EVENT_RISING_EDGE } from "./ffi"
import { requestInputLine } from "./gpio-helpers"
import type { PullMode, EdgeMode } from "./types"

type WorkerConfig = {
  chipPath: string
  offset: number
  pull: PullMode
  debounce: number
  edge: EdgeMode
}

type WorkerMessage =
  | { type: "ready"; initialValue: 0 | 1 }
  | { type: "event"; value: 0 | 1; timestamp: bigint }
  | { type: "error"; message: string }

const postMessage = (message: WorkerMessage) => {
  self.postMessage(message)
}

const cleanup = (message: string): never => {
  postMessage({ type: "error", message })
  self.close()
  throw new Error(message)
}

const mapEdgeToValue = (edgeType: number, pull: PullMode): 0 | 1 => {
  // Pull-up: rising edge = released (1), falling edge = pressed (0)
  // Pull-down: rising edge = pressed (1), falling edge = released (0)
  if (pull === "up") {
    return edgeType === GPIOD_EDGE_EVENT_RISING_EDGE ? 1 : 0
  }
  return edgeType === GPIOD_EDGE_EVENT_RISING_EDGE ? 1 : 0
}

const run = (config: WorkerConfig) => {
  const { chip, request } = requestInputLine(config)

  const initialValue = gpiod.gpiod_line_request_get_value(request, config.offset)
  if (initialValue === -1) {
    gpiod.gpiod_line_request_release(request)
    gpiod.gpiod_chip_close(chip)
    cleanup("Failed to read initial value")
  }

  postMessage({ type: "ready", initialValue: initialValue as 0 | 1 })

  const buffer = gpiod.gpiod_edge_event_buffer_new(1)
  if (!buffer) {
    gpiod.gpiod_line_request_release(request)
    gpiod.gpiod_chip_close(chip)
    cleanup("Failed to create event buffer")
  }

  while (true) {
    // Block forever (-1 timeout) until edge event occurs
    const waitResult = gpiod.gpiod_line_request_wait_edge_events(request, -1)

    if (waitResult === 1) {
      const numEvents = gpiod.gpiod_line_request_read_edge_events(request, buffer, 1)
      if (numEvents === -1) cleanup("Failed to read edge events")

      const event = gpiod.gpiod_edge_event_buffer_get_event(buffer, 0)
      const edgeType = gpiod.gpiod_edge_event_get_event_type(event)
      const timestamp = gpiod.gpiod_edge_event_get_timestamp_ns(event)

      const value = mapEdgeToValue(edgeType, config.pull)

      postMessage({ type: "event", value, timestamp })
    } else if (waitResult === -1) {
      cleanup("GPIO wait_edge_events failed")
    }
  }

  // Worker terminates - kernel cleans up GPIO resources automatically
}

self.onmessage = (event: MessageEvent<WorkerConfig>) => {
  self.onmessage = () => {
    throw new Error("Worker already initialized")
  }
  run(event.data)
}
