///|
/// Who authored a message. A tool exchange is a role of its own here rather than a
/// part of a user message, because OpenAI, Anthropic, Gemini and Ollama all put it
/// in a distinct place on the wire.
#derive(Eq, Debug)
pub(all) enum Role {
  System
  User
  Assistant
  Tool
}

///|
/// The lowercase name a role goes by on the wire, using the OpenAI spelling.
pub fn Role::code(self : Role) -> String {
  match self {
    System => "system"
    User => "user"
    Assistant => "assistant"
    Tool => "tool"
  }
}

///|
/// One typed piece of a message. A message is an ordered list of these, so text and
/// a tool call can share one assistant turn — which is what every provider returns
/// when a model calls a tool and explains itself in the same breath.
#derive(Eq, Debug)
pub(all) enum Part {
  /// Literal text.
  Text(String)
  /// An image, either a URL or an inline media type plus URL.
  Image(url~ : String, media~ : String?)
  /// The model asks for a tool: its id (for matching the result), its name, and the
  /// parsed arguments.
  Call(id~ : String, name~ : String, args~ : Json)
  /// A tool's answer, linked to the call by `id`. A failed tool is a result with
  /// `error` set, not an exception: the model is supposed to see what went wrong.
  Result(id~ : String, content~ : String, error~ : Bool)
}

///|
/// A text part.
pub fn Part::text(text : String) -> Part {
  Text(text)
}

///|
/// A tool call part.
pub fn Part::call(id : String, name : String, args : Json) -> Part {
  Call(id~, name~, args~)
}

///|
/// A tool result part.
pub fn Part::result(
  id : String,
  content : String,
  error~ : Bool = false,
) -> Part {
  Result(id~, content~, error~)
}

///|
/// The text this part carries: a text part's own text, a result's content, and nothing for a
/// call or an image.
pub fn Part::text_of(self : Part) -> String {
  match self {
    Text(text) => text
    Result(content~, ..) => content
    _ => ""
  }
}

///|
/// A message: a role and its ordered parts.
#derive(Eq, Debug)
pub(all) struct Message {
  role : Role
  parts : Array[Part]
}

///|
/// A message built from a role and its parts.
pub fn Message::new(role : Role, parts : Array[Part]) -> Message {
  { role, parts, }
}

///|
/// A system message carrying one text part.
pub fn Message::system(text : String) -> Message {
  { role: System, parts: [Text(text)], }
}

///|
/// A user message carrying one text part.
pub fn Message::user(text : String) -> Message {
  { role: User, parts: [Text(text)], }
}

///|
/// An assistant message carrying one text part.
pub fn Message::assistant(text : String) -> Message {
  { role: Assistant, parts: [Text(text)], }
}

///|
/// A tool message carrying one result, addressed to the call `id`.
pub fn Message::tool(
  content : String,
  id~ : String,
  error~ : Bool = false,
) -> Message {
  { role: Tool, parts: [Result(id~, content~, error~)], }
}

///|
/// A copy with `part` appended. The tool loop grows the conversation this way.
pub fn Message::push(self : Message, part : Part) -> Message {
  { ..self, parts: [..self.parts, part], }
}

///|
/// The text parts joined by `separator`. What a caller shows a human, and what a
/// provider without structured content receives.
pub fn Message::text_content(
  self : Message,
  separator~ : String = "\n",
) -> String {
  let texts = []
  for part in self.parts {
    if part is Text(text) {
      texts.push(text)
    }
  }
  texts.join(separator)
}

///|
/// The tool calls this message requests, in order. Empty on a plain text reply.
pub fn Message::calls(self : Message) -> Array[Part] {
  self.parts.filter(part => part is Call(..))
}

///|
/// The tool results this message carries, in order.
pub fn Message::results(self : Message) -> Array[Part] {
  self.parts.filter(part => part is Result(..))
}

///|
/// Whether this message asks for a tool.
pub fn Message::is_call(self : Message) -> Bool {
  for part in self.parts {
    if part is Call(..) {
      return true
    }
  }
  false
}

///|
pub extend Role with Eq::{not_equal, equal}

///|
pub extend Role with Debug::{to_repr}

///|
pub extend Part with Eq::{not_equal, equal}

///|
pub extend Part with Debug::{to_repr}

///|
pub extend Message with Eq::{not_equal, equal}

///|
pub extend Message with Debug::{to_repr}