// Direct port of https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts

///| A completed agent turn.

///|
/// Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L9-L14
pub(all) struct Turn {
  items : ReadOnlyArray[ThreadItem]
  final_response : String
  usage : Usage?
} derive(Eq)

///| Alias describing the result returned by `Thread::run`.

///|
/// Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L16-L17
pub type RunResult = Turn

///| The result type returned after a streamed callback completes.

///| Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L19-L25

///|
/// Async type difference: MoonBit has no AsyncGenerator, so streamed events are consumed by the callback and the result is Unit.
pub type StreamedTurn = Unit

///| Alias describing the result returned by `Thread::run_streamed`.

///|
/// Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L24-L25
pub type RunStreamedResult = StreamedTurn

///| One structured input entry sent to the agent.

///| Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L27-L36

///|
/// Type difference: MoonBit uses an enum instead of a discriminated object union, and Path replaces filesystem strings.
pub(all) enum UserInput {
  Text(String)
  LocalImage(@path.Path)
} derive(Eq)

///| Input accepted by a thread turn.

///| Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L38

///|
/// Type difference: MoonBit uses an enum instead of the TypeScript string-or-array union.
pub(all) enum Input {
  Prompt(String)
  UserInputs(Array[UserInput])
} derive(Eq)

///| Represents one persisted conversation with the Codex agent.

///|
/// Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L40-L45
pub struct Thread {
  priv exec : CodexExec
  priv options : CodexOptions
  priv mut id_value : String?
  priv thread_options : ThreadOptions
}

///| Returns the persisted thread identifier after the first turn starts.

///|
/// Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L47-L50
pub fn Thread::id(self : Thread) -> String? {
  self.id_value
}

///| Constructs the same internal thread state as the upstream constructor.

///|
/// Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L52-L63
fn Thread::Thread(
  exec : CodexExec,
  options : CodexOptions,
  thread_options : ThreadOptions,
  id? : String,
) -> Thread {
  { exec, options, id_value: id, thread_options }
}

///| Runs one turn and forwards each structured event as it is produced.
///
/// The callback is the MoonBit equivalent of consuming the TypeScript SDK's async event generator.

///| Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L65-L68

///|
/// Async type difference: the callback parameter replaces StreamedTurn.events because MoonBit has no AsyncGenerator.
pub async fn Thread::run_streamed(
  self : Thread,
  input : Input,
  on_event : async (ThreadEvent) -> Unit,
  turn_options? : TurnOptions = TurnOptions::TurnOptions(),
) -> Unit {
  self.run_streamed_internal(input, turn_options, async fn(event) {
    on_event(event)
    true
  })
}

///| Implements the upstream runStreamedInternal process and cleanup order.

///| Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L70-L114

///|
/// Async type difference: event delivery uses a callback returning whether iteration should continue; event JSON is decoded through the standard FromJson implementation.
async fn Thread::run_streamed_internal(
  self : Thread,
  input : Input,
  turn_options : TurnOptions,
  on_event : async (ThreadEvent) -> Bool,
) -> Unit {
  let schema_file = create_output_schema_file(turn_options.output_schema)
  let normalized = normalize_input(input)
  let options = self.thread_options
  let stop_requested = Ref(false)
  let result = Ok(
    self.exec.run(
      {
        input: normalized.prompt,
        base_url: self.options.base_url,
        api_key: self.options.api_key,
        thread_id: self.id_value,
        images: normalized.images,
        model: options.model,
        sandbox_mode: options.sandbox_mode,
        working_directory: options.working_directory,
        additional_directories: options.additional_directories,
        skip_git_repo_check: options.skip_git_repo_check,
        output_schema_file: schema_file.schema_path,
        model_reasoning_effort: options.model_reasoning_effort,
        network_access_enabled: options.network_access_enabled,
        web_search_mode: options.web_search_mode,
        web_search_enabled: options.web_search_enabled,
        approval_policy: options.approval_policy,
      },
      async fn(event) {
        if event is ThreadStarted(started) {
          self.id_value = Some(started.thread_id)
        }
        let should_continue = on_event(event)
        if !should_continue {
          stop_requested.val = true
        }
        should_continue
      },
    ),
  ) catch {
    error => Err(error)
  }
  @async.protect_from_cancel(async fn() { schema_file.cleanup() })
  match result {
    Ok(_) => ()
    Err(error) =>
      // Async type difference: cancelling the MoonBit process task surfaces as cancellation after cleanup; the upstream generator closes normally when its consumer breaks.
      // Upstream generator cleanup: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/exec.ts#L235-L242
      if !stop_requested.val || !@async.is_cancellation_error(error) {
        raise error
      }
  }
}

///| Runs one turn and buffers completed items, the final response, and token usage.

///|
/// Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L116-L140
pub async fn Thread::run(
  self : Thread,
  input : Input,
  turn_options? : TurnOptions = TurnOptions::TurnOptions(),
) -> Turn {
  let items : Array[ThreadItem] = []
  let mut final_response = ""
  let mut usage : Usage? = None
  let mut turn_failure : ThreadError? = None
  self.run_streamed_internal(input, turn_options, fn(event) {
    match event {
      ItemCompleted(completed) => {
        if completed.item is AgentMessage(message) {
          final_response = message.text
        }
        items.push(completed.item)
        true
      }
      TurnCompleted(completed) => {
        usage = Some(completed.usage)
        true
      }
      TurnFailed(failed) => {
        turn_failure = Some(failed.error)
        // Async type difference: returning false is the MoonBit callback equivalent of the upstream consumer's `break`, which closes the generator and terminates the CLI.
        // Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L133-L135
        false
      }
      ThreadStarted(_)
      | TurnStarted(_)
      | ItemStarted(_)
      | ItemUpdated(_)
      | StreamError(_) => true
    }
  })
  if turn_failure is Some(error) {
    raise CodexSdkError::TurnFailed(message=error.message)
  }
  { items: ReadOnlyArray::from_array(items), final_response, usage }
}

// Type difference: MoonBit requires a named type for the upstream normalizeInput return object.
// Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L143-L157

///|
priv struct MoonBitInternalNormalizedInput {
  prompt : String
  images : Array[@path.Path]
}

///| Separates prompt text and image paths with the same order and joining behavior as upstream normalizeInput.

///|
/// Upstream: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L143-L157
fn normalize_input(input : Input) -> MoonBitInternalNormalizedInput {
  match input {
    Prompt(prompt) => { prompt, images: [] }
    UserInputs(items) => {
      let prompt = items
        .iter()
        .filter_map(item => {
          match item {
            Text(text) => Some(text)
            LocalImage(_) => None
          }
        })
        .join("\n\n")
      let images = items
        .iter()
        .filter_map(item => {
          match item {
            Text(_) => None
            LocalImage(path) => Some(path)
          }
        })
        .collect()
      { prompt, images }
    }
  }
}