// Shared types for the integrated chat-gateway SDK.
//
// The shapes mirror Hermes Agent's gateway (`gateway/platforms/base.py`,
// `gateway/platforms/event.py` and the per-platform plugin adapters), so code
// written against this SDK maps onto Hermes' adapter vocabulary:
// `SendResult` / `MessageEvent` / `HomeChannel` / the `Platform` enum.

///|
/// The messaging platforms this SDK can talk to. Values follow Hermes'
/// `Platform` enum (`gateway/config.py`) and the plugin directory names.
pub(all) enum Platform {
  Telegram
  Discord
  Slack
  Whatsapp
  Signal
  Email
  Mattermost
  Matrix
  Dingtalk
  Feishu
  Weixin
  Wecom
  Bluebubbles
  HomeAssistant
} derive(Eq, Hash, Debug)

///|
/// Hermes canonical value (the `Platform(...)` enum value / env-prefix base).
pub fn Platform::value(self : Platform) -> String {
  match self {
    Telegram => "telegram"
    Discord => "discord"
    Slack => "slack"
    Whatsapp => "whatsapp"
    Signal => "signal"
    Email => "email"
    Mattermost => "mattermost"
    Matrix => "matrix"
    Dingtalk => "dingtalk"
    Feishu => "feishu"
    Weixin => "weixin"
    Wecom => "wecom"
    Bluebubbles => "bluebubbles"
    HomeAssistant => "homeassistant"
  }
}

///|
pub fn Platform::of_value(s : String) -> Platform? {
  match s {
    "telegram" => Some(Telegram)
    "discord" => Some(Discord)
    "slack" => Some(Slack)
    "whatsapp" | "whatsapp_cloud" => Some(Whatsapp)
    "signal" => Some(Signal)
    "email" => Some(Email)
    "mattermost" => Some(Mattermost)
    "matrix" => Some(Matrix)
    "dingtalk" => Some(Dingtalk)
    "feishu" | "lark" => Some(Feishu)
    "weixin" | "wechat" => Some(Weixin)
    "wecom" => Some(Wecom)
    "bluebubbles" => Some(Bluebubbles)
    "homeassistant" => Some(HomeAssistant)
    _ => None
  }
}

///|
/// Human-readable name (Hermes `Platform` display names).
pub fn Platform::name(self : Platform) -> String {
  match self {
    Telegram => "Telegram"
    Discord => "Discord"
    Slack => "Slack"
    Whatsapp => "WhatsApp"
    Signal => "Signal"
    Email => "Email"
    Mattermost => "Mattermost"
    Matrix => "Matrix"
    Dingtalk => "DingTalk"
    Feishu => "Feishu/Lark"
    Weixin => "WeChat"
    Wecom => "WeCom (Enterprise WeChat)"
    Bluebubbles => "BlueBubbles"
    HomeAssistant => "Home Assistant"
  }
}

///|
/// Maximum characters the platform accepts in one message; sends are chunked
/// at this boundary (Hermes' per-adapter `MAX_MESSAGE_LENGTH`).
pub fn Platform::max_message_length(self : Platform) -> Int32 {
  match self {
    Telegram => 4096
    Discord => 2000
    Slack => 39000
    Whatsapp => 4096
    Signal => 8000
    Email => 100000
    Mattermost => 16383
    Matrix => 60000
    Dingtalk => 20000
    Feishu => 30000
    Weixin => 4000
    Wecom => 4096
    Bluebubbles => 4000
    HomeAssistant => 4000
  }
}

///|
/// Inbound message kinds, mirroring `gateway/platforms/event.py::MessageType`.
pub(all) enum MessageType {
  Text
  Location
  Photo
  Video
  Audio
  Voice
  Document
  Sticker
  Command
} derive(Eq, Debug)

///|
pub fn MessageType::value(self : MessageType) -> String {
  match self {
    Text => "text"
    Location => "location"
    Photo => "photo"
    Video => "video"
    Audio => "audio"
    Voice => "voice"
    Document => "document"
    Sticker => "sticker"
    Command => "command"
  }
}

///|
/// Platform-neutral send-failure kinds, mirroring Hermes' `SEND_ERROR_KINDS`
/// (`gateway/platforms/base.py`). Consumers branch on these instead of
/// substring-matching `error`.
pub(all) enum SendErrorKind {
  TooLong
  BadFormat
  Forbidden
  NotFound
  RateLimited
  Transient
  Unknown
} derive(Eq, Debug)

///|
pub fn SendErrorKind::value(self : SendErrorKind) -> String {
  match self {
    TooLong => "too_long"
    BadFormat => "bad_format"
    Forbidden => "forbidden"
    NotFound => "not_found"
    RateLimited => "rate_limited"
    Transient => "transient"
    Unknown => "unknown"
  }
}

///|
/// Result of a send, mirroring `gateway/platforms/base.py::SendResult`.
pub(all) struct SendResult {
  success : Bool
  message_id : String?
  error : String?
  retryable : Bool
  retry_after : Double?
  error_kind : SendErrorKind?
  /// Raw response payload (JSON text), when the platform returned one.
  raw : String?
} derive(Debug)

///|
pub fn SendResult::ok(message_id? : String, raw? : String) -> SendResult {
  {
    success: true,
    message_id,
    error: None,
    retryable: false,
    retry_after: None,
    error_kind: None,
    raw,
  }
}

///|
pub fn SendResult::failed(
  error : String,
  retryable~ : Bool = false,
  retry_after? : Double,
  kind? : SendErrorKind,
  raw? : String,
) -> SendResult {
  {
    success: false,
    message_id: None,
    error: Some(error),
    retryable,
    retry_after,
    error_kind: kind,
    raw,
  }
}

///|
/// Raise on failure, return `message_id` otherwise. Handy for call sites
/// that treat a refused send as an error.
pub fn SendResult::unwrap(self : SendResult) -> String raise {
  if self.success {
    self.message_id.unwrap_or("")
  } else {
    raise UtilsError::SendRefused(self.error.unwrap_or("unknown send error"))
  }
}

///|
/// Default delivery target for a platform (Hermes `HomeChannel`): used by
/// cron/notification delivery when no explicit chat is named.
pub(all) struct HomeChannel {
  platform : Platform
  chat_id : String
  name : String
  thread_id : String?
} derive(Debug)

///|
pub fn HomeChannel::new(
  platform : Platform,
  chat_id : String,
  name~ : String = "Home",
  thread_id? : String,
) -> HomeChannel {
  { platform, chat_id, name, thread_id, }
}

///|
/// Static info about a chat, mirroring `get_chat_info` results.
pub(all) struct ChatInfo {
  name : String
  chat_type : String
  chat_id : String
} derive(Debug)

///|
/// A media attachment carried by an inbound `MessageEvent`.
pub(all) struct MediaAttachment {
  url : String
  kind : String // "photo" | "video" | "audio" | "voice" | "document"
} derive(Debug)

///|
/// Normalized inbound message (Hermes `MessageEvent`). Adapters on the
/// receive side produce one of these; this SDK consumes them for send-side
/// routing (e.g. replying in the same thread).
pub(all) struct MessageEvent {
  text : String
  message_type : MessageType
  platform : Platform
  chat_id : String
  user_id : String?
  user_name : String?
  message_id : String?
  reply_to_message_id : String?
  reply_to_text : String?
  media : Array[MediaAttachment]
  timestamp : Int64
} derive(Debug)

///|
pub fn MessageEvent::new(
  text : String,
  message_type : MessageType,
  platform : Platform,
  chat_id : String,
  user_id? : String,
  user_name? : String,
  message_id? : String,
  reply_to_message_id? : String,
  reply_to_text? : String,
  media~ : Array[MediaAttachment] = [],
  timestamp~ : Int64 = 0L,
) -> MessageEvent {
  {
    text,
    message_type,
    platform,
    chat_id,
    user_id,
    user_name,
    message_id,
    reply_to_message_id,
    reply_to_text,
    media,
    timestamp,
  }
}

///|
/// `true` for `/command`-style inbound text.
pub fn MessageEvent::is_command(self : MessageEvent) -> Bool {
  self.message_type == Command || self.text.has_prefix("/")
}

///|
/// The command token (without the leading slash or `@bot` suffix), if any.
pub fn MessageEvent::get_command(self : MessageEvent) -> String? {
  if !self.is_command() {
    return None
  }
  let token = self.text.before(" ").unwrap_or(self.text[:]).to_owned()
  if token.length() < 2 {
    return None
  }
  let name = token[1:].to_owned()
  match name.split_once("@") {
    Some((head, _)) => Some(head.to_owned())
    None => Some(name)
  }
}