///|
/// A synchronous iterator owned by the current Node environment.
pub struct NapiIterator[T] {
  priv next : () -> T? raise
  priv close : () -> Unit raise
  priv mut closed : Bool
}

///|
#borrow(result)
extern "c" fn napi_iterator_current_env(result : Ref[NapiEnv]) -> Int32 = "napi_mbt_iterator_current_env"

///|
pub fn[T] NapiIterator::new(
  next : () -> T? raise,
  close~ : () -> Unit raise = fn() { () },
) -> NapiIterator[T] raise NapiError {
  let env = Ref(napi_dummy_env())
  result_from_status(
    env.val,
    napi_iterator_current_env(env),
    b"Reading current N-API environment",
  )
  { next, close, closed: false, }
}

///|
pub fn[T] NapiIterator::next(self : NapiIterator[T]) -> T? raise {
  if self.closed {
    return None
  }
  errdefer {
    self.closed = true
    (self.close)() catch {
      _ => ()
    }
  }
  let result = (self.next)()
  if result is None {
    self.closed = true
    (self.close)()
  }
  result
}

///|
pub fn[T] NapiIterator::close(self : NapiIterator[T]) -> Unit raise {
  if self.closed {
    return
  }
  self.closed = true
  (self.close)()
}

///|
#borrow(result)
extern "c" fn napi_iterator_factory(
  env : NapiEnv,
  input : Bool,
  next : NapiValue,
  close : NapiValue,
  result : Ref[NapiValue],
) -> Int32 = "napi_mbt_iterator_factory"

///|
#warnings("-unused_value")
fn[T] get_NapiIterator(
  env : NapiEnv,
  value : NapiValue,
  convert : (NapiEnv, NapiValue) -> T raise,
) -> NapiIterator[T] raise NapiError {
  let normalized = Ref(moonbit_dummy_napi_value())
  result_from_status(
    env,
    napi_iterator_factory(env, true, value, napi_null(env), normalized),
    b"Reading synchronous iterator",
  )
  let next = NapiCallback::new(
    env,
    napi_field_get(env, normalized.val, "next", true),
  )
  errdefer (next.dispose() catch { _ => () })
  let close = NapiCallback::new(
    env,
    napi_field_get(env, normalized.val, "close", true),
  )
  errdefer (close.dispose() catch { _ => () })
  NapiIterator::new(
    () => {
      let item = next.call(napi_array_new(env, 0))
      napi_require_object(env, item)
      if get_Bool(env, napi_field_get(env, item, "done", true)) {
        None
      } else {
        Some(convert(env, napi_field_get(env, item, "value", true)))
      }
    },
    close=() => {
      errdefer {
        next.dispose() catch {
          _ => ()
        }
        close.dispose() catch {
          _ => ()
        }
      }
      let result = close.call(napi_array_new(env, 0))
      next.dispose() catch {
        _ => ()
      }
      close.dispose() catch {
        _ => ()
      }
      ignore(result)
    },
  )
}

///|
#warnings("-unused_value")
fn[T] set_NapiIterator(
  env : NapiEnv,
  value : NapiIterator[T],
  convert : (NapiEnv, T) -> NapiValue raise,
) -> NapiValue raise NapiError {
  let next = napi_closure_new(env, fn(env, _) {
    try {
      let result = napi_object_new(env)
      let item = value.next()
      napi_field_set(env, result, "done", set_Bool(env, item is None))
      if item is Some(item) {
        napi_field_set(env, result, "value", convert(env, item))
      }
      result
    } catch {
      error => {
        throw_napi_error(env, error)
        moonbit_dummy_napi_value()
      }
    }
  })
  let close = napi_closure_new(env, fn(env, _) {
    try {
      value.close()
      set_Unit(env, ())
    } catch {
      error => {
        throw_napi_error(env, error)
        moonbit_dummy_napi_value()
      }
    }
  })
  let result = Ref(moonbit_dummy_napi_value())
  result_from_status(
    env,
    napi_iterator_factory(env, false, next, close, result),
    b"Writing synchronous iterator",
  )
  result.val
}