use std::{borrow::Cow, sync::Arc};

use crate::{
    error::{XMLError, XMLErrorDomain, XMLErrorLevel},
    uri::URIStr,
};

#[derive(Debug)]
pub struct SAXParseError {
    pub error: XMLError,
    pub level: XMLErrorLevel,
    pub domain: XMLErrorDomain,
    pub line: usize,
    pub column: usize,
    pub system_id: Arc<URIStr>,
    pub public_id: Option<Arc<str>>,
    pub message: Cow<'static, str>,
}

impl std::fmt::Display for SAXParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let system_id = self
            .system_id
            .as_unescaped_str()
            .unwrap_or(self.system_id.as_escaped_str().into());
        let last = system_id
            .rsplit_once('/')
            .map(|ret| ret.1)
            .unwrap_or(system_id.as_ref());
        write!(
            f,
            "{}[line:{},column:{}][{}] {}",
            last, self.line, self.column, self.level, self.message,
        )
    }
}

impl std::error::Error for SAXParseError {}

macro_rules! generic_error {
    ($method:ident, $handler:expr, $code:expr, $level:expr, $domain:expr, $locator:expr, $message:literal, $( $args:expr ),*) => {
        #[allow(unused)]
        use $crate::error::XMLError::*;
        $handler.$method($crate::sax::error::SAXParseError {
            error: $code,
            level: $level,
            domain: $domain,
            line: $locator.line(),
            column: $locator.column(),
            system_id: $locator.system_id(),
            public_id: $locator.public_id(),
            message: ::std::borrow::Cow::Owned(format!($message, $( $args ),*)),
        })
    };
    ($method:ident, $handler:expr, $code:expr, $level:expr, $domain:expr, $locator:expr, $message:literal) => {
        $handler.$method($crate::sax::error::SAXParseError {
            error: $code,
            level: $level,
            domain: $domain,
            line: $locator.line(),
            column: $locator.column(),
            system_id: $locator.system_id(),
            public_id: $locator.public_id(),
            message: ::std::borrow::Cow::Borrowed($message),
        })
    };
    ($method:ident, $handler:expr, $code:expr, $level:expr, $domain:expr, $locator:expr, $message:expr) => {
        $handler.$method($crate::sax::error::SAXParseError {
            error: $code,
            level: $level,
            domain: $domain,
            line: $locator.line(),
            column: $locator.column(),
            system_id: $locator.system_id(),
            public_id: $locator.public_id(),
            message: ::std::borrow::Cow::Owned($message.into()),
        })
    };
}

macro_rules! fatal_error {
    ($reader:expr, $code:ident, $message:literal, $( $args:expr ),*) => {
        #[allow(unused)]
        use $crate::error::XMLError::*;
        $crate::sax::error::generic_error!(
            fatal_error,
            $reader.handler,
            $code,
            $crate::error::XMLErrorLevel::FatalError,
            $crate::error::XMLErrorDomain::Parser,
            $reader.locator,
            $message,
            $( $args ),*
        );
        $reader.fatal_error_occurred = true;
    };
    ($reader:expr, $code:ident, $message:literal) => {
        $crate::sax::error::fatal_error!($reader, $code, $message, );
    };
    ($reader:expr, $code:ident, $message:expr) => {
        $crate::sax::error::generic_error!(
            fatal_error,
            $reader.handler,
            $crate::error::XMLError::$code,
            $crate::error::XMLErrorLevel::FatalError,
            $crate::error::XMLErrorDomain::Parser,
            $reader.locator,
            $message
        );
        $reader.fatal_error_occurred = true;
    };
}

macro_rules! error {
    ($reader:expr, $code:ident, $message:literal, $( $args:expr ),*) => {
        #[allow(unused)]
        use $crate::error::XMLError::*;
        $crate::sax::error::generic_error!(
            error,
            $reader.handler,
            $code,
            $crate::error::XMLErrorLevel::Error,
            $crate::error::XMLErrorDomain::Parser,
            $reader.locator,
            $message,
            $( $args ),*
        );
    };
    ($reader:expr, $code:ident, $message:literal) => {
        $crate::sax::error::error!($reader, $code, $message, );
    };
    ($reader:expr, $code:ident, $message:expr) => {
        $crate::sax::error::generic_error!(
            error,
            $reader.handler,
            $crate::error::XMLError::$code,
            $crate::error::XMLErrorLevel::Error,
            $crate::error::XMLErrorDomain::Parser,
            $reader.locator,
            $message
        );
    };
}

macro_rules! warning {
    ($reader:expr, $code:ident, $message:literal, $( $args:expr ),*) => {
        $crate::sax::error::generic_error!(
            warning,
            $reader.handler,
            $crate::error::XMLError::$code,
            $crate::error::XMLErrorLevel::Warning,
            $crate::error::XMLErrorDomain::Parser,
            $reader.locator,
            $message,
            $( $args ),*
        );
    };
    ($reader:expr, $code:ident, $message:literal) => {
        $crate::sax::error::warning!($reader, $code, $message, );
    };
    ($reader:expr, $code:ident, $message:expr) => {
        $crate::sax::error::generic_error!(
            warning,
            $reader.handler,
            $crate::error::XMLError::$code,
            $crate::error::XMLErrorLevel::Warning,
            $crate::error::XMLErrorDomain::Parser,
            $reader.locator,
            $message
        );
    };
}

macro_rules! ns_error {
    ($reader:expr, $code:ident, $message:literal, $( $args:expr ),*) => {
        #[allow(unused)]
        use $crate::error::XMLError::*;
        $crate::sax::error::generic_error!(
            error,
            $reader.handler,
            $code,
            $crate::error::XMLErrorLevel::Error,
            $crate::error::XMLErrorDomain::Namespace,
            $reader.locator,
            $message,
            $( $args ),*
        );
    };
    ($reader:expr, $code:ident, $message:literal) => {
        $crate::sax::error::ns_error!($reader, $code, $message, );
    };
    ($reader:expr, $code:ident, $message:expr) => {
        $crate::sax::error::generic_error!(
            error,
            $reader.handler,
            $crate::error::XMLError::$code,
            $crate::error::XMLErrorLevel::Error,
            $crate::error::XMLErrorDomain::Namespace,
            $reader.locator,
            $message
        );
    };
}

macro_rules! validity_error {
    ($reader:expr, $code:ident, $message:literal, $( $args:expr ),*) => {
        $crate::sax::error::generic_error!(
            error,
            $reader.handler,
            $code,
            $crate::error::XMLErrorLevel::Error,
            $crate::error::XMLErrorDomain::DTDValid,
            $reader.locator,
            $message,
            $( $args ),*
        );
    };
    ($reader:expr, $code:ident, $message:literal) => {
        $crate::sax::error::validity_error!($reader, $code, $message, );
    };
    ($reader:expr, $code:ident, $message:expr) => {
        $crate::sax::error::generic_error!(
            error,
            $reader.handler,
            $code,
            $crate::error::XMLErrorLevel::Error,
            $crate::error::XMLErrorDomain::DTDValid,
            $reader.locator,
            $message
        );
    };
}

pub(crate) use {error, fatal_error, generic_error, ns_error, validity_error, warning};
