/// Window API simulation.
///
/// Handles: location (href, hostname, pathname, search, hash, assign, replace),
/// open(), history (pushState, replaceState, back, forward),
/// postMessage(), alert/confirm/prompt.
///
use jsdet_core::observation::Value;

/// Simulated window.location state.
#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct Location {
    pub href: String,
    pub protocol: String,
    pub hostname: String,
    pub port: String,
    pub pathname: String,
    pub search: String,
    pub hash: String,
    pub origin: String,
}

impl Location {
    pub fn from_url(url: &str) -> Self {
        let (protocol, rest) = url.split_once("://").unwrap_or(("https", url));
        let (authority, path_and_query) = rest.split_once('/').unwrap_or((rest, ""));
        let (host, port) = if authority.contains(':') {
            let (h, p) = authority.rsplit_once(':').unwrap();
            (h.to_string(), p.to_string())
        } else {
            (authority.to_string(), String::new())
        };
        let (path_with_query, hash) = path_and_query
            .split_once('#')
            .unwrap_or((path_and_query, ""));
        let (pathname, search) = path_with_query
            .split_once('?')
            .unwrap_or((path_with_query, ""));

        let origin = if port.is_empty() {
            format!("{protocol}://{host}")
        } else {
            format!("{protocol}://{host}:{port}")
        };

        Self {
            href: url.to_string(),
            protocol: format!("{protocol}:"),
            hostname: host,
            port,
            pathname: format!("/{pathname}"),
            search: if search.is_empty() {
                String::new()
            } else {
                format!("?{search}")
            },
            hash: if hash.is_empty() {
                String::new()
            } else {
                format!("#{hash}")
            },
            origin,
        }
    }

    pub fn get_property(&self, prop: &str) -> Value {
        match prop {
            "href" => Value::string(self.href.clone()),
            "protocol" => Value::string(self.protocol.clone()),
            "hostname" | "host" => Value::string(self.hostname.clone()),
            "port" => Value::string(self.port.clone()),
            "pathname" => Value::string(self.pathname.clone()),
            "search" => Value::string(self.search.clone()),
            "hash" => Value::string(self.hash.clone()),
            "origin" => Value::string(self.origin.clone()),
            _ => Value::Undefined,
        }
    }
}

/// Simulated history state.
#[derive(Debug, Clone, Default)]
pub struct History {
    entries: Vec<String>,
    index: usize,
}

impl History {
    pub fn new(initial_url: &str) -> Self {
        Self {
            entries: vec![initial_url.to_string()],
            index: 0,
        }
    }

    pub fn push_state(&mut self, url: &str) {
        self.entries.truncate(self.index + 1);
        self.entries.push(url.to_string());
        self.index = self.entries.len() - 1;
    }

    pub fn replace_state(&mut self, url: &str) {
        if let Some(entry) = self.entries.get_mut(self.index) {
            *entry = url.to_string();
        }
    }

    pub fn back(&mut self) -> Option<&str> {
        if self.index > 0 {
            self.index -= 1;
            self.entries.get(self.index).map(|s| s.as_str())
        } else {
            None
        }
    }

    pub fn forward(&mut self) -> Option<&str> {
        if self.index + 1 < self.entries.len() {
            self.index += 1;
            self.entries.get(self.index).map(|s| s.as_str())
        } else {
            None
        }
    }

    pub fn length(&self) -> usize {
        self.entries.len()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn location_parses_full_url() {
        let loc = Location::from_url("https://example.com:8080/path?q=1#frag");
        assert_eq!(loc.protocol, "https:");
        assert_eq!(loc.hostname, "example.com");
        assert_eq!(loc.port, "8080");
        assert_eq!(loc.pathname, "/path");
        assert_eq!(loc.search, "?q=1");
        assert_eq!(loc.hash, "#frag");
    }

    #[test]
    fn history_push_and_back() {
        let mut h = History::new("https://a.com");
        h.push_state("https://b.com");
        h.push_state("https://c.com");
        assert_eq!(h.length(), 3);
        assert_eq!(h.back(), Some("https://b.com"));
        assert_eq!(h.back(), Some("https://a.com"));
        assert_eq!(h.forward(), Some("https://b.com"));
    }
}
