// Mattermost REST API v4 adapter.
//
// Mirrors Hermes `plugins/platforms/mattermost/adapter.py`: posts go through
// `POST /api/v4/posts` with mentions disabled, replies use `root_id` in thread
// mode, and inbound traffic arrives through an outgoing-webhook POST.

///|
/// API root (`{url}/api/v4`), overridable with `[mattermost.extra] api_base`.
pub fn mattermost_api_base(cfg : PlatformConfig) -> String {
  let api_override = cfg.extra_str("api_base", "")
  if !api_override.is_empty() {
    return if api_override.has_suffix("/") {
      api_override[:api_override.length() - 1].to_owned()
    } else {
      api_override
    }
  }
  let url = cfg.extra_str("url", "")
  let trimmed = if url.has_suffix("/") {
    url[:url.length() - 1].to_owned()
  } else {
    url
  }
  "\{trimmed}/api/v4"
}

///|
/// `POST /posts` payload (mentions disabled, optional thread root).
pub fn mattermost_send_payload(
  channel_id : String,
  message : String,
  reply_to : String?,
) -> Json {
  let props : Map[String, Json] = Map([])
  props.set("disable_mentions", Json::boolean(true))
  let payload = json_obj([
    ("channel_id", Json::string(channel_id)),
    ("message", Json::string(message)),
    ("props", Json::object(props)),
  ])
  obj_with_opt(payload, "root_id", reply_to)
}

///|
/// Call the Mattermost API.
async fn mattermost_call(
  cfg : PlatformConfig,
  token : String,
  meth : String,
  path : String,
  payload : Json,
) -> SendResult {
  let url = "\{mattermost_api_base(cfg)}\{path}"
  let headers = bearer_headers(token)
  let res = match meth {
    "GET" => get_json(url, headers)
    _ => post_json(url, payload, headers)
  }
  match res {
    Err(e) => send_failure("mattermost", e)
    Ok(j) =>
      match json_field_string(j, "message") {
        Some(m) =>
          if json_field_string(j, "id") is None &&
            json_field_int(j, "status_code") is Some(_) {
            send_body_failure("mattermost", m)
          } else {
            send_ok(json_field_string(j, "id"), j)
          }
        None => send_ok(json_field_string(j, "id"), j)
      }
  }
}

///|
/// Send ``content``, chunked at Mattermost's 16383-char limit.
pub async fn mattermost_send(
  cfg : PlatformConfig,
  chat_id : String,
  content : String,
  reply_to? : String,
) -> SendResult {
  match cfg.token_of() {
    None => missing_config(Mattermost, "MATTERMOST_TOKEN")
    Some(token) =>
      if mattermost_api_base(cfg) == "/api/v4" {
        missing_config(Mattermost, "MATTERMOST_URL")
      } else if content.is_empty() {
        SendResult::ok()
      } else {
        let mut first = true
        let mut last = SendResult::ok()
        let mut first_id : String? = None
        for
          chunk in chunk_text(content, Platform::max_message_length(Mattermost)) {
          let reply = if first { reply_to } else { None }
          let r = send_with_retry(() => {
            mattermost_call(
              cfg,
              token,
              "POST",
              "/posts",
              mattermost_send_payload(chat_id, chunk, reply),
            )
          })
          first = false
          if !r.success {
            return r
          }
          if first_id is None {
            first_id = r.message_id
          }
          last = r
        }
        { ..last, message_id: first_id, }
      }
  }
}

///|
/// Channel metadata (`GET /channels/{id}`).
pub async fn mattermost_get_chat(
  cfg : PlatformConfig,
  chat_id : String,
) -> ChatInfo? {
  match cfg.token_of() {
    None => None
    Some(token) => {
      let url = "\{mattermost_api_base(cfg)}/channels/\{chat_id}"
      match get_json(url, bearer_headers(token)) {
        Err(_) => None
        Ok(j) =>
          Some({
            name: or_opt(
              json_field_string(j, "display_name"),
              json_field_string(j, "name"),
            ).unwrap_or(chat_id),
            chat_type: json_field_string(j, "type").unwrap_or("O"),
            chat_id,
          })
      }
    }
  }
}

///|
/// The `token` carried by a Mattermost outgoing-webhook body (configured on
/// the outgoing webhook in Mattermost; compared with `[mattermost.extra]
/// webhook_token` when set).
pub fn mattermost_webhook_token(body : String) -> String? {
  let j = @json.parse(body) catch { _ => return None }
  json_field_string(j, "token")
}

///|
/// Parse a Mattermost outgoing-webhook body into a `MessageEvent`.
pub fn mattermost_parse_webhook(body : String) -> Array[MessageEvent] {
  let out : Array[MessageEvent] = []
  let j = @json.parse(body) catch { _ => return out }
  let text = json_field_string(j, "text").unwrap_or("")
  let channel_id = json_field_string(j, "channel_id").unwrap_or("")
  if channel_id.is_empty() || text.is_empty() {
    return out
  }
  let mtype = if text.has_prefix("/") { Command } else { Text }
  out.push(
    MessageEvent::new(
      text,
      mtype,
      Mattermost,
      channel_id,
      user_id?=json_field_string(j, "user_id"),
      user_name?=json_field_string(j, "user_name"),
      message_id?=json_field_string(j, "post_id"),
      media=[],
      timestamp=now_ms(),
    ),
  )
  out
}