///|
/// Provider adapter session backed by a Codex SDK thread.
priv struct CodexCliSession {
  client : @sdk.Client
  thread : @sdk.Thread
  thread_options : @sdk.ThreadOptions
  turn_options : @sdk.TurnOptions
}

///|
impl @cli.CliSession for CodexCliSession with fn id(self) {
  self.thread.id()
}

///|
impl @cli.CliSession for CodexCliSession with fn continuation(self) {
  self.thread
  .id()
  .map(fn(continuation_id) {
    @cli.Continuation::Continuation(fn() {
      codex_session(
        self.client,
        self.thread_options,
        self.turn_options,
        Some(continuation_id),
      )
    })
  })
}

///|
impl @cli.CliSession for CodexCliSession with fn prompt(self, prompt) {
  let turn = self.thread.run(
    codex_input(prompt),
    turn_options=self.turn_options,
  )
  let continuation = @cli.CliSession::continuation(self)
  @cli.FinalResponse::FinalResponse(
    turn.final_response,
    session_id?=self.thread.id(),
    changed_files=codex_changed_files(turn.items),
    continuation?,
  )
}

///|
fn codex_input(prompt : @cli.Prompt) -> @sdk.Input {
  if prompt.context_files.is_empty() {
    return @sdk.Prompt(prompt.text)
  }
  let files = prompt.context_files
    .iter()
    .map(path => path.to_string())
    .join("\n")
  @sdk.Prompt(
    "\{prompt.text}\n\nContext files supplied by the caller:\n\{files}",
  )
}

///|
fn codex_changed_files(
  items : ReadOnlyArray[@sdk.ThreadItem],
) -> ReadOnlyArray[@path.Path] {
  let files : Array[@path.Path] = []
  for item in items {
    match item {
      @sdk.FileChange(change) =>
        match change.status {
          @sdk.PatchCompleted =>
            for update in change.changes {
              files.push(update.path)
            }
          _ => ()
        }
      _ => ()
    }
  }
  ReadOnlyArray::from_array(files)
}

///|
fn codex_session(
  client : @sdk.Client,
  thread_options : @sdk.ThreadOptions,
  turn_options : @sdk.TurnOptions,
  continuation_id : String?,
) -> &@cli.CliSession {
  let thread = match continuation_id {
    Some(id) => client.resume_thread(id, options=thread_options)
    None => client.start_thread(options=thread_options)
  }
  let session = CodexCliSession::{
    client,
    thread,
    thread_options,
    turn_options,
  }
  (session : &@cli.CliSession)
}

///|
/// Creates a Codex-backed provider-neutral CLI adapter.
///
/// Codex-native client, thread, and turn options remain available at this
/// package boundary. Prompt context paths are appended to the prompt text in
/// caller order, and completed Codex patch paths are returned in
/// `FinalResponse.changed_files`.
///
/// ```check
/// let cli = codex_cli()
/// let session = cli.start()
/// assert_eq(session.id(), None)
/// ```
pub fn codex_cli(
  options? : @sdk.ClientOptions = @sdk.ClientOptions::ClientOptions(),
  thread_options? : @sdk.ThreadOptions = @sdk.ThreadOptions::ThreadOptions(),
  turn_options? : @sdk.TurnOptions = @sdk.TurnOptions::TurnOptions(),
  resume_thread_id? : String,
) -> @cli.Cli {
  let client = @sdk.Client::Client(options~)
  @cli.Cli::Cli(fn() {
    codex_session(client, thread_options, turn_options, resume_thread_id)
  })
}