#[cfg(test)]
use crate::session::SessionIdentity;

#[cfg(all(target_family = "wasm", target_os = "unknown"))]
pub const AUTH_BROADCAST_CHANNEL: &str = "polymodel-auth";
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
pub const SIGN_OUT_MESSAGE: &str = "sign-out";

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Toast {
    pub id: u64,
    pub message: String,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ToastState {
    next_id: u64,
    items: Vec<Toast>,
}

impl ToastState {
    pub fn push(&mut self, message: impl Into<String>) -> u64 {
        let id = self.next_id;
        self.next_id += 1;
        self.items.push(Toast {
            id,
            message: message.into(),
        });
        id
    }

    pub fn dismiss(&mut self, id: u64) {
        self.items.retain(|toast| toast.id != id);
    }

    pub fn items(&self) -> &[Toast] {
        &self.items
    }
}

#[cfg(test)]
pub fn handle_remote_sign_out(session: &mut SessionIdentity, toasts: &mut ToastState) {
    *session = SessionIdentity::Anonymous;
    toasts.push("Signed out in another tab.");
}

pub fn login_url(identifier: &str) -> String {
    let identifier = identifier.trim();
    if identifier.is_empty() {
        "/oauth/login".to_string()
    } else {
        format!(
            "/oauth/start?identifier={}",
            encode_query_component(identifier)
        )
    }
}

pub fn logout_url() -> &'static str {
    "/oauth/logout"
}

fn encode_query_component(value: &str) -> String {
    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
    {
        js_sys::encode_uri_component(value)
            .as_string()
            .unwrap_or_default()
    }

    #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
    {
        value
            .bytes()
            .flat_map(|byte| match byte {
                b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
                    vec![byte as char]
                }
                _ => format!("%{byte:02X}").chars().collect(),
            })
            .collect()
    }
}

#[cfg(all(target_family = "wasm", target_os = "unknown"))]
pub fn navigate_to(url: &str) {
    if let Some(window) = web_sys::window() {
        let _ = window.location().set_href(url);
    }
}

#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
pub fn navigate_to(_url: &str) {}

#[cfg(all(target_family = "wasm", target_os = "unknown"))]
pub fn broadcast_sign_out() {
    if let Ok(channel) = web_sys::BroadcastChannel::new(AUTH_BROADCAST_CHANNEL) {
        let _ = channel.post_message(&wasm_bindgen::JsValue::from_str(SIGN_OUT_MESSAGE));
    }
}

#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
pub fn broadcast_sign_out() {}

#[cfg(test)]
mod tests {
    use super::*;
    use jacquard::types::string::Handle;
    use jacquard_common::types::did::Did;

    #[test]
    fn login_url_uses_oauth_login_when_identifier_empty() {
        assert_eq!(login_url("  "), "/oauth/login");
    }

    #[test]
    fn login_url_uses_oauth_start_with_encoded_identifier() {
        assert_eq!(
            login_url("alice model.example"),
            "/oauth/start?identifier=alice%20model.example"
        );
    }

    #[test]
    fn remote_sign_out_clears_session_and_adds_toast() {
        let mut session = SessionIdentity::Authenticated(crate::session::AuthenticatedIdentity {
            did: Did::new_owned("did:plc:alice").unwrap(),
            handle: Handle::new_owned("alice.test").unwrap(),
            display_name: None,
        });
        let mut toasts = ToastState::default();

        handle_remote_sign_out(&mut session, &mut toasts);

        assert_eq!(session, SessionIdentity::Anonymous);
        assert_eq!(toasts.items()[0].message, "Signed out in another tab.");
    }

    #[test]
    fn toast_state_dismisses_by_id() {
        let mut toasts = ToastState::default();
        let first = toasts.push("one");
        let _second = toasts.push("two");

        toasts.dismiss(first);

        assert_eq!(toasts.items().len(), 1);
        assert_eq!(toasts.items()[0].message, "two");
    }
}
