// What travels inside a prompt and inside a tool call's result: text, media, resources and diffs.

///|
/// Who a piece of content is for.
#derive(Eq)
pub(all) enum Role {
  Assistant
  User
}

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

///|
/// The name the role travels as.
pub fn Role::to_string(self : Role) -> String {
  match self {
    Assistant => "assistant"
    User => "user"
  }
}

///|
/// The role with that name, or `None`.
pub fn Role::of(name : String) -> Role? {
  match name {
    "assistant" => Some(Assistant)
    "user" => Some(User)
    _ => None
  }
}

///|
pub extend Role with @json.ToJson::{to_json}

///|
pub impl ToJson for Role with fn to_json(self) {
  Json::string(self.to_string())
}

///|
pub extend Role with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for Role with fn from_json(json, path) {
  match Role::of(text_of(json, path)) {
    Some(role) => role
    None => raise want(path, "a role", json)
  }
}

///|
/// Hints for how a client shows a piece of content.
#derive(Eq)
pub(all) struct Annotations {
  audience : Array[Role]?
  last_modified : String?
  priority : Double?
  meta : Json?
}

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

///|
pub extend Annotations with @json.ToJson::{to_json}

///|
pub impl ToJson for Annotations with fn to_json(self) {
  json_of([
    ("audience", json_list_of(self.audience)),
    ("lastModified", json_string(self.last_modified)),
    ("priority", json_float(self.priority)),
    ("_meta", self.meta),
  ])
}

///|
pub extend Annotations with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for Annotations with fn from_json(json, path) {
  let fields = expect_object(json, path)
  {
    audience: list_member(fields, "audience", path, (value, at) => {
      read(value, at)
    }),
    last_modified: string_member(fields, "lastModified", path),
    priority: float_member(fields, "priority", path),
    meta: meta_of(fields, path),
  }
}

///|
/// Text the model reads or writes.
#derive(Eq)
pub(all) struct TextContent {
  text : String
  annotations : Annotations?
  meta : Json?
}

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

///|
pub extend TextContent with @json.ToJson::{to_json}

///|
pub impl ToJson for TextContent with fn to_json(self) {
  json_of([
    ("text", Some(Json::string(self.text))),
    ("annotations", self.annotations.map(write)),
    ("_meta", self.meta),
  ])
}

///|
pub extend TextContent with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for TextContent with fn from_json(json, path) {
  let fields = expect_object(json, path)
  {
    text: required_string(fields, "text", path),
    annotations: object_member(fields, "annotations", path),
    meta: meta_of(fields, path),
  }
}

///|
/// An image, base64 in `data`.
#derive(Eq)
pub(all) struct ImageContent {
  data : String
  mime_type : String
  uri : String?
  annotations : Annotations?
  meta : Json?
}

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

///|
pub extend ImageContent with @json.ToJson::{to_json}

///|
pub impl ToJson for ImageContent with fn to_json(self) {
  json_of([
    ("data", Some(Json::string(self.data))),
    ("mimeType", Some(Json::string(self.mime_type))),
    ("uri", json_string(self.uri)),
    ("annotations", self.annotations.map(write)),
    ("_meta", self.meta),
  ])
}

///|
pub extend ImageContent with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for ImageContent with fn from_json(json, path) {
  let fields = expect_object(json, path)
  {
    data: required_string(fields, "data", path),
    mime_type: required_string(fields, "mimeType", path),
    uri: string_member(fields, "uri", path),
    annotations: object_member(fields, "annotations", path),
    meta: meta_of(fields, path),
  }
}

///|
/// Audio, base64 in `data`.
#derive(Eq)
pub(all) struct AudioContent {
  data : String
  mime_type : String
  annotations : Annotations?
  meta : Json?
}

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

///|
pub extend AudioContent with @json.ToJson::{to_json}

///|
pub impl ToJson for AudioContent with fn to_json(self) {
  json_of([
    ("data", Some(Json::string(self.data))),
    ("mimeType", Some(Json::string(self.mime_type))),
    ("annotations", self.annotations.map(write)),
    ("_meta", self.meta),
  ])
}

///|
pub extend AudioContent with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for AudioContent with fn from_json(json, path) {
  let fields = expect_object(json, path)
  {
    data: required_string(fields, "data", path),
    mime_type: required_string(fields, "mimeType", path),
    annotations: object_member(fields, "annotations", path),
    meta: meta_of(fields, path),
  }
}

///|
/// A resource the client can read, named by URI rather than by value.
#derive(Eq)
pub(all) struct ResourceLink {
  uri : String
  name : String
  title : String?
  description : String?
  mime_type : String?
  size : Int64?
  annotations : Annotations?
  meta : Json?
}

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

///|
pub extend ResourceLink with @json.ToJson::{to_json}

///|
pub impl ToJson for ResourceLink with fn to_json(self) {
  json_of([
    ("uri", Some(Json::string(self.uri))),
    ("name", Some(Json::string(self.name))),
    ("title", json_string(self.title)),
    ("description", json_string(self.description)),
    ("mimeType", json_string(self.mime_type)),
    ("size", json_int(self.size)),
    ("annotations", self.annotations.map(write)),
    ("_meta", self.meta),
  ])
}

///|
pub extend ResourceLink with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for ResourceLink with fn from_json(json, path) {
  let fields = expect_object(json, path)
  {
    uri: required_string(fields, "uri", path),
    name: required_string(fields, "name", path),
    title: string_member(fields, "title", path),
    description: string_member(fields, "description", path),
    mime_type: string_member(fields, "mimeType", path),
    size: int_member(fields, "size", path),
    annotations: object_member(fields, "annotations", path),
    meta: meta_of(fields, path),
  }
}

///|
/// Resource contents carried as text.
#derive(Eq)
pub(all) struct TextResource {
  uri : String
  text : String
  mime_type : String?
  meta : Json?
}

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

///|
pub extend TextResource with @json.ToJson::{to_json}

///|
pub impl ToJson for TextResource with fn to_json(self) {
  json_of([
    ("uri", Some(Json::string(self.uri))),
    ("text", Some(Json::string(self.text))),
    ("mimeType", json_string(self.mime_type)),
    ("_meta", self.meta),
  ])
}

///|
pub extend TextResource with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for TextResource with fn from_json(json, path) {
  let fields = expect_object(json, path)
  {
    uri: required_string(fields, "uri", path),
    text: required_string(fields, "text", path),
    mime_type: string_member(fields, "mimeType", path),
    meta: meta_of(fields, path),
  }
}

///|
/// Resource contents carried as base64 in `blob`.
#derive(Eq)
pub(all) struct BlobResource {
  uri : String
  blob : String
  mime_type : String?
  meta : Json?
}

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

///|
pub extend BlobResource with @json.ToJson::{to_json}

///|
pub impl ToJson for BlobResource with fn to_json(self) {
  json_of([
    ("uri", Some(Json::string(self.uri))),
    ("blob", Some(Json::string(self.blob))),
    ("mimeType", json_string(self.mime_type)),
    ("_meta", self.meta),
  ])
}

///|
pub extend BlobResource with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for BlobResource with fn from_json(json, path) {
  let fields = expect_object(json, path)
  {
    uri: required_string(fields, "uri", path),
    blob: required_string(fields, "blob", path),
    mime_type: string_member(fields, "mimeType", path),
    meta: meta_of(fields, path),
  }
}

///|
/// Either kind of embedded resource contents, told apart by `text` against `blob`.
#derive(Eq)
pub(all) enum Resource {
  Text(TextResource)
  Blob(BlobResource)
}

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

///|
pub extend Resource with @json.ToJson::{to_json}

///|
pub impl ToJson for Resource with fn to_json(self) {
  match self {
    Text(resource) => write(resource)
    Blob(resource) => write(resource)
  }
}

///|
pub extend Resource with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for Resource with fn from_json(json, path) {
  let fields = expect_object(json, path)
  if fields.contains("text") {
    Text(read(json, path))
  } else if fields.contains("blob") {
    Blob(read(json, path))
  } else {
    raise @json.JsonDecodeError((path, "want a resource with text or blob"))
  }
}

///|
/// A resource carried by value inside a message.
#derive(Eq)
pub(all) struct EmbeddedResource {
  resource : Resource
  annotations : Annotations?
  meta : Json?
}

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

///|
pub extend EmbeddedResource with @json.ToJson::{to_json}

///|
pub impl ToJson for EmbeddedResource with fn to_json(self) {
  json_of([
    ("resource", Some(write(self.resource))),
    ("annotations", self.annotations.map(write)),
    ("_meta", self.meta),
  ])
}

///|
pub extend EmbeddedResource with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for EmbeddedResource with fn from_json(json, path) {
  let fields = expect_object(json, path)
  {
    resource: required_object(fields, "resource", path),
    annotations: object_member(fields, "annotations", path),
    meta: meta_of(fields, path),
  }
}

///|
/// One displayable piece of a prompt or of a tool's output, tagged by `type` on the wire.
#derive(Eq)
pub(all) enum ContentBlock {
  Text(TextContent)
  Image(ImageContent)
  Audio(AudioContent)
  ResourceLink(ResourceLink)
  Resource(EmbeddedResource)
}

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

///|
pub extend ContentBlock with @json.ToJson::{to_json}

///|
pub impl ToJson for ContentBlock with fn to_json(self) {
  match self {
    Text(content) => json_tagged(write(content), "type", "text")
    Image(content) => json_tagged(write(content), "type", "image")
    Audio(content) => json_tagged(write(content), "type", "audio")
    ResourceLink(content) =>
      json_tagged(write(content), "type", "resource_link")
    Resource(content) => json_tagged(write(content), "type", "resource")
  }
}

///|
pub extend ContentBlock with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for ContentBlock with fn from_json(json, path) {
  let fields = expect_object(json, path)
  match required_string(fields, "type", path) {
    "text" => Text(read(json, path))
    "image" => Image(read(json, path))
    "audio" => Audio(read(json, path))
    "resource_link" => ResourceLink(read(json, path))
    "resource" => Resource(read(json, path))
    other =>
      raise @json.JsonDecodeError(
        (path.add_key("type"), "unknown content block \{other}"),
      )
  }
}

///|
/// A standard content block wrapped for a tool call's output.
#derive(Eq)
pub(all) struct Content {
  content : ContentBlock
  meta : Json?
}

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

///|
pub extend Content with @json.ToJson::{to_json}

///|
pub impl ToJson for Content with fn to_json(self) {
  json_of([("content", Some(write(self.content))), ("_meta", self.meta)])
}

///|
pub extend Content with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for Content with fn from_json(json, path) {
  let fields = expect_object(json, path)
  {
    content: required_object(fields, "content", path),
    meta: meta_of(fields, path),
  }
}

///|
/// A file edit, given as the whole file before and after.
#derive(Eq)
pub(all) struct Diff {
  path : String
  new_text : String
  old_text : String?
  meta : Json?
}

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

///|
pub extend Diff with @json.ToJson::{to_json}

///|
pub impl ToJson for Diff with fn to_json(self) {
  json_of([
    ("path", Some(Json::string(self.path))),
    ("newText", Some(Json::string(self.new_text))),
    ("oldText", json_string(self.old_text)),
    ("_meta", self.meta),
  ])
}

///|
pub extend Diff with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for Diff with fn from_json(json, path) {
  let fields = expect_object(json, path)
  {
    path: required_string(fields, "path", path),
    new_text: required_string(fields, "newText", path),
    old_text: string_member(fields, "oldText", path),
    meta: meta_of(fields, path),
  }
}

///|
/// A reference to a terminal the client opened, embedded in a tool call.
#derive(Eq)
pub(all) struct TerminalRef {
  terminal_id : String
  meta : Json?
}

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

///|
/// A reference to a terminal, as a tool call embeds it.
pub fn TerminalRef::new(terminal_id : String) -> TerminalRef {
  { terminal_id, meta: None, }
}

///|
pub extend TerminalRef with @json.ToJson::{to_json}

///|
pub impl ToJson for TerminalRef with fn to_json(self) {
  json_of([
    ("terminalId", Some(Json::string(self.terminal_id))),
    ("_meta", self.meta),
  ])
}

///|
pub extend TerminalRef with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for TerminalRef with fn from_json(json, path) {
  let fields = expect_object(json, path)
  {
    terminal_id: required_string(fields, "terminalId", path),
    meta: meta_of(fields, path),
  }
}

///|
/// What a tool call produced, tagged by `type` on the wire.
#derive(Eq)
pub(all) enum ToolCallContent {
  Content(Content)
  Diff(Diff)
  Terminal(TerminalRef)
}

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

///|
pub extend ToolCallContent with @json.ToJson::{to_json}

///|
pub impl ToJson for ToolCallContent with fn to_json(self) {
  match self {
    Content(content) => json_tagged(write(content), "type", "content")
    Diff(diff) => json_tagged(write(diff), "type", "diff")
    Terminal(terminal) => json_tagged(write(terminal), "type", "terminal")
  }
}

///|
pub extend ToolCallContent with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for ToolCallContent with fn from_json(json, path) {
  let fields = expect_object(json, path)
  match required_string(fields, "type", path) {
    "content" => Content(read(json, path))
    "diff" => Diff(read(json, path))
    "terminal" => Terminal(read(json, path))
    other =>
      raise @json.JsonDecodeError(
        (path.add_key("type"), "unknown tool call content \{other}"),
      )
  }
}

///|
/// A place in the workspace a tool touched.
#derive(Eq)
pub(all) struct ToolCallLocation {
  path : String
  line : Int64?
  meta : Json?
}

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

///|
pub extend ToolCallLocation with @json.ToJson::{to_json}

///|
pub impl ToJson for ToolCallLocation with fn to_json(self) {
  json_of([
    ("path", Some(Json::string(self.path))),
    ("line", json_int(self.line)),
    ("_meta", self.meta),
  ])
}

///|
pub extend ToolCallLocation with @json.FromJson::{from_json}

///|
pub impl @json.FromJson for ToolCallLocation with fn from_json(json, path) {
  let fields = expect_object(json, path)
  {
    path: required_string(fields, "path", path),
    line: int_member(fields, "line", path),
    meta: meta_of(fields, path),
  }
}