///|
priv enum Backend {
  Polling(PollingState)
  FSEvents(FSEventsState)
  Inotify(InotifyState)
  RDCW(RDCWState)
}

///|
/// A filesystem watcher. Calls to `next` block until at least one change is
/// observed, then return the batch of events seen during that scan.
///
/// The watcher transparently picks the fastest available backend at start:
/// FSEvents on macOS, inotify on Linux, ReadDirectoryChangesW on Windows,
/// polling fallback elsewhere. The public surface is identical across
/// backends.
pub struct Watcher {
  priv backend : Backend
  priv mut closed : Bool
}

///|
fn default_exclude(_ : String) -> Bool {
  false
}

///|
/// Start a watcher rooted at `roots`. Each entry must be a directory; paths
/// are tracked recursively. `interval_ms` controls how often `next` re-checks
/// for events (default 500 ms for polling, 50 ms for FSEvents/inotify).
/// `exclude` is applied per path; returning `true` drops both the entry and
/// (for directories) its descendants from tracking.
pub async fn start(
  roots : Array[String],
  interval_ms? : Int,
  exclude? : (String) -> Bool,
) -> Watcher {
  let ex = exclude.unwrap_or(default_exclude)
  let fast_interval = interval_ms.unwrap_or(50)
  let slow_interval = interval_ms.unwrap_or(500)
  match fsevents_start(roots, fast_interval, ex, fast_interval.to_double()) {
    Some(state) => Watcher::{ backend: FSEvents(state), closed: false }
    None =>
      match inotify_start(roots, fast_interval, ex) {
        Some(state) => Watcher::{ backend: Inotify(state), closed: false }
        None =>
          match rdcw_start(roots, fast_interval, ex) {
            Some(state) => Watcher::{ backend: RDCW(state), closed: false }
            None => {
              let state = polling_start(roots, slow_interval, ex)
              Watcher::{ backend: Polling(state), closed: false }
            }
          }
      }
  }
}

///|
/// Force the polling backend, ignoring FSEvents/inotify/etc. Useful for tests
/// that need deterministic snapshot semantics, or for environments where the
/// native backend misbehaves.
pub async fn start_polling(
  roots : Array[String],
  interval_ms? : Int,
  exclude? : (String) -> Bool,
) -> Watcher {
  let ex = exclude.unwrap_or(default_exclude)
  let interval = interval_ms.unwrap_or(500)
  let state = polling_start(roots, interval, ex)
  Watcher::{ backend: Polling(state), closed: false }
}

///|
/// Block until at least one event is observed (or the watcher is closed),
/// then return all events from that scan. Returns `[]` only on close.
pub async fn Watcher::next(self : Watcher) -> Array[Event] {
  for ;; {
    if self.closed {
      break []
    }
    let events = match self.backend {
      Polling(s) => polling_next(s)
      FSEvents(s) => fsevents_next(s)
      Inotify(s) => inotify_next(s)
      RDCW(s) => rdcw_next(s)
    }
    if self.closed {
      break []
    }
    if events.length() > 0 {
      break normalize(events)
    }
  }
}

///|
/// Mark the watcher closed and release backend resources. Idempotent.
pub fn Watcher::close(self : Watcher) -> Unit {
  if self.closed {
    return
  }
  self.closed = true
  match self.backend {
    Polling(_) => ()
    FSEvents(s) => fsevents_close(s)
    Inotify(s) => inotify_close(s)
    RDCW(s) => rdcw_close(s)
  }
}

///|
/// Dedupe `(path, kind)` pairs while preserving first-seen order. FSEvents
/// often emits the same Modified event twice in a single batch; polling
/// won't, but it's cheap to apply uniformly.
fn normalize(events : Array[Event]) -> Array[Event] {
  let seen : Map[String, Bool] = Map([])
  let out : Array[Event] = []
  for e in events {
    let key = e.path + "\u{00}" + e.kind.to_key()
    if !seen.contains(key) {
      seen[key] = true
      out.push(e)
    }
  }
  out
}

///|
fn EventKind::to_key(self : EventKind) -> String {
  match self {
    Created => "C"
    Modified => "M"
    Removed => "R"
  }
}