//// One submission's model input and its durable user-visible event.

import albedo/daemon/events as view
import albedo/daemon/note
import albedo/daemon/operations
import albedo/daemon/session_state
import albedo/daemon/turn.{type Submission}
import albedo/openai_api/types
import gleam/json
import gleam/list
import gleam/option.{None, Some}

pub fn input(submission: Submission) -> types.Input {
  let text = case submission.source {
    turn.Note(origin) -> note.wrap(origin, submission.text)
    _ -> submission.text
  }
  case submission.image {
    Some(image) -> types.UserImage(text, image)
    None -> types.User(text)
  }
}

pub fn event(submission: Submission, timestamp: Int) -> String {
  view.durable_submission(display(submission), timestamp)
}

pub fn display(submission: Submission) -> operations.Display {
  operations.Display(
    submission.display,
    turn.source_name(submission.source),
    submission.client_id,
    submission.operation_id,
    option.map(submission.image, fn(image) {
      let #(mime_type, width, height, bytes) = types.image_meta(image)
      operations.ImageMetadata(mime_type, width, height, bytes)
    }),
  )
}

pub fn emit(
  state: session_state.State(message),
  submissions: List(Submission),
  timestamp: Int,
) -> session_state.State(message) {
  list.fold(submissions, state, fn(state, submission) {
    case submission.source {
      turn.Continue -> state
      _ -> session_state.emit(state, event(submission, timestamp))
    }
  })
}

@external(erlang, "erlang", "term_to_binary")
pub fn encode(submission: Submission) -> BitArray

@external(erlang, "albedo_session", "decode_submission")
pub fn decode(payload: BitArray) -> Submission

@external(erlang, "albedo_session", "fingerprint")
pub fn image_fingerprint(image: types.Image) -> String

pub fn commits(
  submissions: List(Submission),
  offset: Int,
) -> List(operations.Commit) {
  let #(_, reversed_commits) =
    list.fold(submissions, #(offset, []), fn(acc, submission) {
      let position = case submission.source {
        turn.Continue -> None
        _ -> Some(acc.0)
      }
      let next = case position {
        None -> acc.0
        Some(_) -> acc.0 + 1
      }
      #(next, case submission.operation_id {
        None -> acc.1
        Some(id) -> [
          operations.Commit(id, position, display(submission)),
          ..acc.1
        ]
      })
    })
  list.reverse(reversed_commits)
}

pub fn inputs(submissions: List(Submission)) -> List(types.Input) {
  list.filter(submissions, fn(submission) { submission.source != turn.Continue })
  |> list.map(input)
}

pub fn membership(
  state: session_state.State(message),
  id: String,
) -> session_state.State(message) {
  session_state.emit(
    state,
    view.event("turn_membership", [
      #("turnId", json.string(id)),
      #(
        "submissionIds",
        json.array(
          list.filter_map(state.active_submissions, fn(submission) {
            case submission.submission_id {
              Some(id) -> Ok(id)
              None -> Error(Nil)
            }
          }),
          json.string,
        ),
      ),
    ]),
  )
}
