///|
fn normalize_line_ending(line : String) -> String {
  if line.has_suffix("\r\n") {
    line[:line.length() - 2].to_owned()
  } else if line.has_suffix("\n") {
    line[:line.length() - 1].to_owned()
  } else {
    line
  }
}

///|
fn decode_line(bytes : Bytes) -> String raise ReadlineError {
  let line = @utf8.decode(bytes) catch { _ => raise InvalidUtf8 }
  normalize_line_ending(line)
}

///|
fn EditorOwner::raise_session_operation(
  self : EditorOwner,
  status : Int,
) -> Unit raise ReadlineError {
  match status {
    0 => ()
    1 => raise Busy
    2 => raise EmbeddedNul
    5 => raise CleanupFailed(self.session_errno())
    _ => raise ReadFailed(self.session_errno())
  }
}

///|
priv struct StdinInputBuffer {
  mut remaining : Bytes?
}

///|
let stdin_input_buffer : StdinInputBuffer = { remaining: None }

///|
fn StdinInputBuffer::take(self : StdinInputBuffer) -> Bytes? {
  let remaining = self.remaining
  self.remaining = None
  remaining
}

///|
fn StdinInputBuffer::save(self : StdinInputBuffer, remaining : Bytes) -> Unit {
  if remaining.length() > 0 {
    self.remaining = Some(remaining)
  }
}

///|
async fn EditorOwner::drive_session(
  self : EditorOwner,
  generation : Int64,
  completer : ((CompletionContext) -> Array[String])?,
) -> Int {
  for ;; {
    match self.session_drive(generation) {
      0 => {
        let input = match stdin_input_buffer.take() {
          Some(bytes) => Some(bytes)
          None =>
            @stdio.stdin.read_some(max_len=256) catch {
              error if @async.is_being_cancelled() => raise error
              @os_error.OSError(error_code, ..) => raise ReadFailed(error_code)
              _ => raise ReadFailed(0)
            }
        }
        let operation = match input {
          Some(bytes) => self.session_feed(generation, bytes, false)
          None => self.session_feed(generation, Bytes::new(0), true)
        }
        self.raise_session_operation(operation)
      }
      1 => continue
      2..=5 as status => return status
      7 => {
        guard completer is Some(completer) else { raise ReadFailed(0) }
        let context = self.session_completion_context(generation)
        let response = make_session_completion_response(completer, context)
        self.session_apply_completion(generation, response)
      }
      _ => raise ReadFailed(self.session_errno())
    }
  }
}

///|
fn EditorOwner::finish_session(
  self : EditorOwner,
  generation : Int64,
  drive_status : Int,
) -> String? raise {
  let error_code = self.session_errno()
  let operation_status = self.session_operation_status()
  let bytes = self.session_finish(generation)
  guard self.session_operation_status() == 0 else {
    raise CleanupFailed(self.session_errno())
  }
  match drive_status {
    2 => Some(decode_line(bytes))
    3 => None
    4 => raise Interrupted
    5 if operation_status == 2 => raise EmbeddedNul
    _ => raise ReadFailed(error_code)
  }
}

///|
fn EditorOwner::finish_failed_session(
  self : EditorOwner,
  generation : Int64,
) -> Unit raise ReadlineError {
  self.session_finish(generation) |> ignore
  guard self.session_operation_status() == 0 else {
    raise CleanupFailed(self.session_errno())
  }
}

///|
/// 异步读取并编辑一行文本。
///
/// 返回 `Some(line)` 表示用户提交了一行,返回 `None` 只表示正常 EOF。直接回车返回
/// `Some("")`,不会与 EOF 混淆。返回文本由 MoonBit 独立持有,不包含末尾换行符。
/// 此方法不会自动将返回值加入 History。
///
/// **Parameters:**
///
/// - `prompt`:本次读取期间显示的提示符,默认为空字符串,不会成为长期配置。
///
/// **Errors:**
///
/// - `prompt` 包含 NUL,或底层行数据包含 NUL 时抛出 `EmbeddedNul`。
/// - completer 返回的任一候选包含 NUL 时抛出 `EmbeddedNul`;当前输入会被丢弃,editor
///   与原 completer 均可继续使用。
/// - 底层行数据不是合法 UTF-8 时抛出 `InvalidUtf8`。
/// - 同一进程已有活跃读取,或所连接的 History 正在使用时抛出 `Busy`。
/// - 用户按下 Ctrl-C 中断本次输入时抛出 `Interrupted`;未提交输入会被丢弃,editor
///   可以继续读取。
/// - 发生读取错误时抛出 `ReadFailed`,并携带 C 边界立即保存的 OS error code;零表示
///   没有可靠错误码。
/// - 无法完整恢复终端状态时抛出 `CleanupFailed`;当前 editor 此后不可继续使用。
/// - 调用 task 被取消时先恢复读取状态,再继续传播 async runtime 的原取消,不会转换成
///   `Interrupted` 或 `ReadFailed`。
///
/// **Lifecycle:**
///
/// prompt 及底层返回缓冲区只在本次调用期间使用。方法返回或抛错前会复制结果,并恢复
/// editor、History、locale、完整终端状态和进程级读取 guard。
///
/// **Side effects:**
///
/// 此方法等待标准输入时会挂起当前 task,并暂时接管标准终端的行编辑状态。
///
/// **Thread safety:**
///
/// 多个 `LineEditor` 可以共存和顺序读取,但整个进程同一时刻只允许一个活跃读取;冲突
/// 不等待,直接抛出 `Busy`。
///
/// **Examples:**
///
/// ```mbt nocheck
/// let editor = LineEditor::open()
/// match editor.read_line(prompt=">> ") {
///   Some(line) => println(line)
///   None => println("EOF")
/// }
/// ```
pub async fn LineEditor::read_line(
  self : LineEditor,
  prompt? : StringView = "",
) -> String? {
  self.ensure_usable()
  let prompt = @utf8.encode(prompt)
  let completer = self.completion.get()
  let generation = self.owner.session_begin(prompt, completer is Some(_))
  guard generation != 0L else {
    self.owner.raise_session_operation(self.owner.session_operation_status())
    raise ReadFailed(self.owner.session_errno())
  }
  let active = Ref(true)
  defer (if active.val { self.owner.session_cancel(generation) |> ignore })
  let drive_status = self.owner.drive_session(generation, completer) catch {
    error if @async.is_being_cancelled() => {
      @async.protect_from_cancel(() => {
        self.owner.session_cancel(generation) |> ignore
      })
      active.val = false
      raise error
    }
    error => {
      self.owner.finish_failed_session(generation)
      active.val = false
      raise error
    }
  }
  stdin_input_buffer.save(self.owner.session_take_remaining_input(generation))
  let result = self.owner.finish_session(generation, drive_status)
  active.val = false
  result
}