#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
compile_error!("Unexpected pointer width for target platform");

use alloc::{
    string::{String, ToString},
    vec::Vec,
};
use core::str::FromStr;

use web_sys::window;

use crate::{
    os::{Os, Target},
    CpuArchitecture, DesktopEnvironment, Error, Language, LanguagePreferences,
    OsString, Platform, Result,
};

// Get the user agent
fn user_agent() -> Option<String> {
    window()?.navigator().user_agent().ok()
}

// Get the document domain
fn document_domain() -> Option<String> {
    window()?.document()?.location()?.hostname().ok()
}

// Get the browser name and version for desktop environment
fn browser_info() -> String {
    let orig_string = user_agent().unwrap_or_default();
    let start = if let Some(s) = orig_string.rfind(' ') {
        s
    } else {
        return "Unknown Browser".to_string();
    };
    let string = orig_string
        .get(start + 1..)
        .unwrap_or("Unknown Browser")
        .replace('/', " ");
    let string = if let Some(s) = string.rfind("Safari") {
        if let Some(s) = orig_string.rfind("Chrome") {
            if let Some(e) = orig_string.get(s..).unwrap_or_default().find(' ')
            {
                orig_string
                    .get(s..)
                    .unwrap_or("Chrome")
                    .get(..e)
                    .unwrap_or("Chrome")
                    .replace('/', " ")
            } else {
                "Chrome".to_string()
            }
        } else if orig_string.contains("Linux") {
            "GNOME Web".to_string()
        } else {
            string.get(s..).unwrap_or("Safari").replace('/', " ")
        }
    } else if string.contains("Edg ") {
        string.replace("Edg ", "Edge ")
    } else if string.contains("OPR ") {
        string.replace("OPR ", "Opera ")
    } else {
        string
    };

    string
}

impl Target for Os {
    fn lang_prefs(self) -> Result<LanguagePreferences> {
        if let Some(window) = window() {
            let langs = window
                .navigator()
                .languages()
                .to_vec()
                .into_iter()
                .filter_map(|l| {
                    l.as_string().as_deref().map(Language::from_str)
                })
                .collect::<Result<Vec<_>>>()?;
            Ok(LanguagePreferences {
                fallbacks: langs,
                ..Default::default()
            })
        } else {
            Err(Error::new(
                "Failed to retrieve languages: Window object is missing",
            ))
        }
    }

    fn realname(self) -> Result<OsString> {
        Ok("Anonymous".to_string().into())
    }

    fn username(self) -> Result<OsString> {
        Ok("anonymous".to_string().into())
    }

    fn devicename(self) -> Result<OsString> {
        Ok("Browser".to_string().into())
    }

    fn hostname(self) -> Result<String> {
        document_domain()
            .filter(|x| !x.is_empty())
            .ok_or_else(|| {
                Error::new(
                    "Domain missing, failed to retrieve document domain from window"
                )
            })
    }

    fn distro(self) -> Result<String> {
        let string = user_agent().ok_or_else(Error::permission_denied)?;
        let err = || Error::with_invalid_data("Parsing failed");
        let begin = string.find('(').ok_or_else(err)?;
        let end = string.find(')').ok_or_else(err)?;
        let string = &string[begin + 1..end];

        Ok(if string.contains("Win32") || string.contains("Win64") {
            let begin = if let Some(b) = string.find("NT") {
                b
            } else {
                return Ok("Windows".to_string());
            };
            let end = if let Some(e) = string.find('.') {
                e
            } else {
                return Ok("Windows".to_string());
            };
            let string = &string[begin + 3..end];

            alloc::format!("Windows {string}")
        } else if string.contains("Linux") {
            let string = if string.contains("X11") || string.contains("Wayland")
            {
                let begin = if let Some(b) = string.find(';') {
                    b
                } else {
                    return Ok("Unknown Linux".to_string());
                };
                &string[begin + 2..]
            } else {
                string
            };

            if string.starts_with("Linux") {
                "Unknown Linux".to_string()
            } else {
                let end = if let Some(e) = string.find(';') {
                    e
                } else {
                    return Ok("Unknown Linux".to_string());
                };
                string[..end].to_string()
            }
        } else if let Some(begin) = string.find("Mac OS X") {
            if let Some(end) = string[begin..].find(';') {
                string[begin..begin + end].to_string()
            } else {
                string[begin..].to_string().replace('_', ".")
            }
        } else {
            string.to_string()
        })
    }

    #[inline(always)]
    fn desktop_env(self) -> Option<DesktopEnvironment> {
        Some(DesktopEnvironment::WebBrowser(browser_info()))
    }

    fn platform(self) -> Platform {
        let string = user_agent().unwrap_or_default();
        let begin = if let Some(b) = string.find('(') {
            b
        } else {
            return Platform::Unknown("Unknown".to_string());
        };
        let end = if let Some(e) = string.find(')') {
            e
        } else {
            return Platform::Unknown("Unknown".to_string());
        };
        let string = &string[begin + 1..end];

        if string.contains("Win32") || string.contains("Win64") {
            Platform::Windows
        } else if string.contains("Linux") {
            Platform::Linux
        } else if string.contains("Mac OS X") {
            Platform::Mac
        } else {
            Platform::Unknown(string.to_string())
        }
    }

    #[inline(always)]
    fn arch(self) -> Result<CpuArchitecture> {
        Ok(if cfg!(target_pointer_width = "64") {
            CpuArchitecture::Wasm64
        } else {
            CpuArchitecture::Wasm32
        })
    }
}
