use kordophoned::daemon::signals::Signal;
use std::collections::HashMap;
use std::ffi::CString;
use xpc_connection::Message;

pub type XpcMap = HashMap<CString, Message>;

pub fn cstr(s: &str) -> CString {
    CString::new(s).unwrap_or_else(|_| CString::new("").unwrap())
}

pub fn get_dictionary_field<'a>(
    map: &'a HashMap<CString, Message>,
    key: &str,
) -> Option<&'a HashMap<CString, Message>> {
    let k = CString::new(key).ok()?;
    map.get(&k).and_then(|v| match v {
        Message::Dictionary(d) => Some(d),
        _ => None,
    })
}

pub fn dict_get_str(map: &HashMap<CString, Message>, key: &str) -> Option<String> {
    let k = CString::new(key).ok()?;
    match map.get(&k) {
        Some(Message::String(v)) => Some(v.to_string_lossy().into_owned()),
        _ => None,
    }
}

pub fn dict_get_i64_from_str(map: &HashMap<CString, Message>, key: &str) -> Option<i64> {
    dict_get_str(map, key).and_then(|s| s.parse::<i64>().ok())
}

pub fn dict_put_str(map: &mut XpcMap, key: &str, value: impl AsRef<str>) {
    map.insert(cstr(key), Message::String(cstr(value.as_ref())));
}

pub fn dict_put_i64_as_str(map: &mut XpcMap, key: &str, value: i64) {
    dict_put_str(map, key, value.to_string());
}

pub fn array_from_strs(values: impl IntoIterator<Item = String>) -> Message {
    let arr = values
        .into_iter()
        .map(|s| Message::String(cstr(&s)))
        .collect();
    Message::Array(arr)
}

pub fn make_ok_reply() -> Message {
    let mut reply: XpcMap = HashMap::new();
    dict_put_str(&mut reply, "type", "Ok");
    Message::Dictionary(reply)
}

pub fn make_error_reply(code: &str, message: &str) -> Message {
    let mut reply: HashMap<CString, Message> = HashMap::new();
    reply.insert(cstr("type"), Message::String(cstr("Error")));
    reply.insert(cstr("error"), Message::String(cstr(code)));
    reply.insert(cstr("message"), Message::String(cstr(message)));
    Message::Dictionary(reply)
}

pub fn attach_request_id(mut message: Message, request_id: Option<String>) -> Message {
    if let (Some(id), Message::Dictionary(ref mut m)) = (request_id, &mut message) {
        dict_put_str(m, "request_id", &id);
    }
    message
}

pub fn signal_to_message(signal: Signal) -> Message {
    let mut root: XpcMap = HashMap::new();
    let mut args: XpcMap = HashMap::new();
    match signal {
        Signal::ConversationsUpdated => {
            dict_put_str(&mut root, "name", "ConversationsUpdated");
        }
        Signal::MessagesUpdated(conversation_id) => {
            dict_put_str(&mut root, "name", "MessagesUpdated");
            dict_put_str(&mut args, "conversation_id", &conversation_id);
        }
        Signal::AttachmentDownloaded(attachment_id) => {
            dict_put_str(&mut root, "name", "AttachmentDownloadCompleted");
            dict_put_str(&mut args, "attachment_id", &attachment_id);
        }
        Signal::AttachmentUploaded(upload_guid, attachment_guid) => {
            dict_put_str(&mut root, "name", "AttachmentUploadCompleted");
            dict_put_str(&mut args, "upload_guid", &upload_guid);
            dict_put_str(&mut args, "attachment_guid", &attachment_guid);
        }
        Signal::UpdateStreamReconnected => {
            dict_put_str(&mut root, "name", "UpdateStreamReconnected");
        }
    }
    if !args.is_empty() {
        root.insert(cstr("arguments"), Message::Dictionary(args));
    }
    Message::Dictionary(root)
}
