// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use self::ffi::ICU4XError;
use core::fmt;
#[cfg(feature = "icu_decimal")]
use fixed_decimal::FixedDecimalError;
#[cfg(any(
    feature = "icu_datetime",
    feature = "icu_timezone",
    feature = "icu_calendar"
))]
use icu_calendar::CalendarError;
#[cfg(feature = "icu_collator")]
use icu_collator::CollatorError;
#[cfg(feature = "icu_datetime")]
use icu_datetime::DateTimeError;
#[cfg(any(feature = "icu_decimal", feature = "icu_datetime"))]
use icu_decimal::DecimalError;
#[cfg(feature = "experimental_components")]
use icu_experimental::units::ConversionError;
#[cfg(feature = "icu_list")]
use icu_list::ListError;
use icu_locid::ParserError;
#[cfg(feature = "icu_locid_transform")]
use icu_locid_transform::LocaleTransformError;
#[cfg(feature = "icu_normalizer")]
use icu_normalizer::NormalizerError;
#[cfg(any(feature = "icu_plurals", feature = "icu_datetime"))]
use icu_plurals::PluralsError;
#[cfg(feature = "icu_properties")]
use icu_properties::PropertiesError;
use icu_provider::{DataError, DataErrorKind};
#[cfg(feature = "icu_segmenter")]
use icu_segmenter::SegmenterError;
#[cfg(any(feature = "icu_timezone", feature = "icu_datetime"))]
use icu_timezone::TimeZoneError;

#[diplomat::bridge]
pub mod ffi {
    use alloc::boxed::Box;

    #[derive(Debug, PartialEq, Eq)]
    #[repr(C)]
    /// A common enum for errors that ICU4X may return, organized by API
    ///
    /// The error names are stable and can be checked against as strings in the JS API
    #[diplomat::rust_link(fixed_decimal::FixedDecimalError, Enum, compact)]
    #[diplomat::rust_link(icu::calendar::CalendarError, Enum, compact)]
    #[diplomat::rust_link(icu::collator::CollatorError, Enum, compact)]
    #[diplomat::rust_link(icu::datetime::DateTimeError, Enum, compact)]
    #[diplomat::rust_link(icu::datetime::MismatchedCalendarError, Struct, hidden)]
    #[diplomat::rust_link(icu::decimal::DecimalError, Enum, compact)]
    #[diplomat::rust_link(icu::list::ListError, Enum, compact)]
    #[diplomat::rust_link(icu::locid::ParserError, Enum, compact)]
    #[diplomat::rust_link(icu::locid_transform::LocaleTransformError, Enum, compact)]
    #[diplomat::rust_link(icu::normalizer::NormalizerError, Enum, compact)]
    #[diplomat::rust_link(icu::plurals::PluralsError, Enum, compact)]
    #[diplomat::rust_link(icu::properties::PropertiesError, Enum, compact)]
    #[diplomat::rust_link(icu::provider::DataError, Struct, compact)]
    #[diplomat::rust_link(icu::provider::DataErrorKind, Enum, compact)]
    #[diplomat::rust_link(icu::segmenter::SegmenterError, Enum, compact)]
    #[diplomat::rust_link(icu::timezone::TimeZoneError, Enum, compact)]
    #[diplomat::rust_link(icu_experimental::units::ConversionError, Enum, compact)]
    pub enum ICU4XError {
        // general errors
        /// The error is not currently categorized as ICU4XError.
        /// Please file a bug
        UnknownError = 0x00,
        /// An error arising from writing to a string
        /// Typically found when not enough space is allocated
        /// Most APIs that return a string may return this error
        WriteableError = 0x01,
        /// Some input was out of bounds
        OutOfBoundsError = 0x02,
        /// Input expected to be UTF-8 was ill-formed
        Utf8Error = 0x03,

        // general data errors
        // See DataError
        DataMissingDataKeyError = 0x1_00,
        DataMissingVariantError = 0x1_01,
        DataMissingLocaleError = 0x1_02,
        DataNeedsVariantError = 0x1_03,
        DataNeedsLocaleError = 0x1_04,
        DataExtraneousLocaleError = 0x1_05,
        DataFilteredResourceError = 0x1_06,
        DataMismatchedTypeError = 0x1_07,
        DataMissingPayloadError = 0x1_08,
        DataInvalidStateError = 0x1_09,
        DataCustomError = 0x1_0A,
        DataIoError = 0x1_0B,
        DataUnavailableBufferFormatError = 0x1_0C,
        DataMismatchedAnyBufferError = 0x1_0D,

        // locale errors
        /// The subtag being requested was not set
        LocaleUndefinedSubtagError = 0x2_00,
        /// The locale or subtag string failed to parse
        LocaleParserLanguageError = 0x2_01,
        LocaleParserSubtagError = 0x2_02,
        LocaleParserExtensionError = 0x2_03,

        // data struct errors
        /// Attempted to construct an invalid data struct
        DataStructValidityError = 0x3_00,

        // property errors
        PropertyUnknownScriptIdError = 0x4_00,
        PropertyUnknownGeneralCategoryGroupError = 0x4_01,
        PropertyUnexpectedPropertyNameError = 0x4_02,

        // fixed_decimal errors
        FixedDecimalLimitError = 0x5_00,
        FixedDecimalSyntaxError = 0x5_01,

        // plural errors
        PluralsParserError = 0x6_00,

        // datetime errors
        CalendarParseError = 0x7_00,
        CalendarOverflowError = 0x7_01,
        CalendarUnderflowError = 0x7_02,
        CalendarOutOfRangeError = 0x7_03,
        CalendarUnknownEraError = 0x7_04,
        CalendarUnknownMonthCodeError = 0x7_05,
        CalendarMissingInputError = 0x7_06,
        CalendarUnknownKindError = 0x7_07,
        CalendarMissingError = 0x7_08,

        // datetime format errors
        DateTimePatternError = 0x8_00,
        DateTimeMissingInputFieldError = 0x8_01,
        DateTimeSkeletonError = 0x8_02,
        DateTimeUnsupportedFieldError = 0x8_03,
        DateTimeUnsupportedOptionsError = 0x8_04,
        DateTimeMissingWeekdaySymbolError = 0x8_05,
        DateTimeMissingMonthSymbolError = 0x8_06,
        DateTimeFixedDecimalError = 0x8_07,
        DateTimeMismatchedCalendarError = 0x8_08,

        // dead
        // tinystr errors
        TinyStrTooLargeError = 0x9_00,
        TinyStrContainsNullError = 0x9_01,
        TinyStrNonAsciiError = 0x9_02,

        // timezone errors
        TimeZoneOffsetOutOfBoundsError = 0xA_00,
        TimeZoneInvalidOffsetError = 0xA_01,
        TimeZoneMissingInputError = 0xA_02,
        TimeZoneInvalidIdError = 0xA_03,

        // normalizer errors
        NormalizerFutureExtensionError = 0xB_00,
        NormalizerValidationError = 0xB_01,

        // Units errors
        #[cfg(feature = "experimental_components")]
        InvalidCldrUnitIdentifierError = 0x0C_00,
    }
}

impl ICU4XError {
    #[cfg(feature = "logging")]
    #[inline]
    pub(crate) fn log_original<T: core::fmt::Display + ?Sized>(self, e: &T) -> Self {
        use core::any;
        log::warn!(
            "Returning ICU4XError::{:?} based on original {}: {}",
            self,
            any::type_name::<T>(),
            e
        );
        self
    }

    #[cfg(not(feature = "logging"))]
    #[inline]
    pub(crate) fn log_original<T: core::fmt::Display + ?Sized>(self, _e: &T) -> Self {
        self
    }
}

impl From<fmt::Error> for ICU4XError {
    fn from(e: fmt::Error) -> Self {
        ICU4XError::WriteableError.log_original(&e)
    }
}

impl From<DataError> for ICU4XError {
    fn from(e: DataError) -> Self {
        match e.kind {
            DataErrorKind::MissingDataKey => ICU4XError::DataMissingDataKeyError,
            DataErrorKind::MissingLocale => ICU4XError::DataMissingLocaleError,
            DataErrorKind::NeedsLocale => ICU4XError::DataNeedsLocaleError,
            DataErrorKind::ExtraneousLocale => ICU4XError::DataExtraneousLocaleError,
            DataErrorKind::FilteredResource => ICU4XError::DataFilteredResourceError,
            DataErrorKind::MismatchedType(..) => ICU4XError::DataMismatchedTypeError,
            DataErrorKind::MissingPayload => ICU4XError::DataMissingPayloadError,
            DataErrorKind::InvalidState => ICU4XError::DataInvalidStateError,
            DataErrorKind::Custom => ICU4XError::DataCustomError,
            #[cfg(all(
                feature = "provider_fs",
                not(any(target_arch = "wasm32", target_os = "none"))
            ))]
            DataErrorKind::Io(..) => ICU4XError::DataIoError,
            // datagen only
            // DataErrorKind::MissingSourceData(..) => ..,
            DataErrorKind::UnavailableBufferFormat(..) => {
                ICU4XError::DataUnavailableBufferFormatError
            }
            _ => ICU4XError::UnknownError,
        }
        .log_original(&e)
    }
}

impl From<core::str::Utf8Error> for ICU4XError {
    fn from(_: core::str::Utf8Error) -> Self {
        ICU4XError::Utf8Error
    }
}

#[cfg(feature = "icu_collator")]
impl From<CollatorError> for ICU4XError {
    fn from(e: CollatorError) -> Self {
        match e {
            CollatorError::NotFound => ICU4XError::DataMissingPayloadError,
            CollatorError::MalformedData => ICU4XError::DataInvalidStateError,
            CollatorError::Data(_) => ICU4XError::DataIoError,
            _ => ICU4XError::DataIoError,
        }
        .log_original(&e)
    }
}

#[cfg(feature = "icu_properties")]
impl From<PropertiesError> for ICU4XError {
    fn from(e: PropertiesError) -> Self {
        match e {
            PropertiesError::PropDataLoad(e) => e.into(),
            PropertiesError::UnknownScriptId(..) => ICU4XError::PropertyUnknownScriptIdError,
            PropertiesError::UnknownGeneralCategoryGroup(..) => {
                ICU4XError::PropertyUnknownGeneralCategoryGroupError
            }
            PropertiesError::UnexpectedPropertyName => {
                ICU4XError::PropertyUnexpectedPropertyNameError
            }
            _ => ICU4XError::UnknownError,
        }
        .log_original(&e)
    }
}

#[cfg(any(
    feature = "icu_datetime",
    feature = "icu_timezone",
    feature = "icu_calendar"
))]
impl From<CalendarError> for ICU4XError {
    fn from(e: CalendarError) -> Self {
        match e {
            CalendarError::Parse => ICU4XError::CalendarParseError,
            CalendarError::Overflow { field: _, max: _ } => ICU4XError::CalendarOverflowError,
            CalendarError::Underflow { field: _, min: _ } => ICU4XError::CalendarUnderflowError,
            CalendarError::OutOfRange => ICU4XError::CalendarOutOfRangeError,
            CalendarError::UnknownEra(..) => ICU4XError::CalendarUnknownEraError,
            CalendarError::UnknownMonthCode(..) => ICU4XError::CalendarUnknownMonthCodeError,
            CalendarError::MissingInput(_) => ICU4XError::CalendarMissingInputError,
            CalendarError::UnknownAnyCalendarKind(_) => ICU4XError::CalendarUnknownKindError,
            CalendarError::MissingCalendar => ICU4XError::CalendarMissingError,
            CalendarError::Data(e) => e.into(),
            _ => ICU4XError::UnknownError,
        }
        .log_original(&e)
    }
}

#[cfg(feature = "icu_datetime")]
impl From<DateTimeError> for ICU4XError {
    fn from(e: DateTimeError) -> Self {
        match e {
            DateTimeError::Pattern(_) => ICU4XError::DateTimePatternError,
            DateTimeError::Format(err) => err.into(),
            DateTimeError::Data(err) => err.into(),
            DateTimeError::MissingInputField(_) => ICU4XError::DateTimeMissingInputFieldError,
            // TODO(#1324): Add back skeleton errors
            // DateTimeFormatterError::Skeleton(_) => ICU4XError::DateTimeFormatSkeletonError,
            DateTimeError::UnsupportedField(_) => ICU4XError::DateTimeUnsupportedFieldError,
            DateTimeError::UnsupportedOptions => ICU4XError::DateTimeUnsupportedOptionsError,
            DateTimeError::PluralRules(err) => err.into(),
            DateTimeError::DateTimeInput(err) => err.into(),
            DateTimeError::MissingWeekdaySymbol(_) => ICU4XError::DateTimeMissingWeekdaySymbolError,
            DateTimeError::MissingMonthSymbol(_) => ICU4XError::DateTimeMissingMonthSymbolError,
            DateTimeError::FixedDecimal => ICU4XError::DateTimeFixedDecimalError,
            DateTimeError::FixedDecimalFormatter(err) => err.into(),
            DateTimeError::MismatchedAnyCalendar(_, _) => {
                ICU4XError::DateTimeMismatchedCalendarError
            }
            _ => ICU4XError::UnknownError,
        }
        .log_original(&e)
    }
}

#[cfg(feature = "icu_decimal")]
impl From<FixedDecimalError> for ICU4XError {
    fn from(e: FixedDecimalError) -> Self {
        match e {
            FixedDecimalError::Limit => ICU4XError::FixedDecimalLimitError,
            FixedDecimalError::Syntax => ICU4XError::FixedDecimalSyntaxError,
            _ => ICU4XError::UnknownError,
        }
        .log_original(&e)
    }
}

#[cfg(any(feature = "icu_plurals", feature = "icu_datetime"))]
impl From<PluralsError> for ICU4XError {
    fn from(e: PluralsError) -> Self {
        match e {
            PluralsError::Data(e) => e.into(),
            _ => ICU4XError::UnknownError,
        }
        .log_original(&e)
    }
}

#[cfg(any(feature = "icu_decimal", feature = "icu_datetime"))]
impl From<DecimalError> for ICU4XError {
    fn from(e: DecimalError) -> Self {
        match e {
            DecimalError::Data(e) => e.into(),
            _ => ICU4XError::UnknownError,
        }
        .log_original(&e)
    }
}

#[cfg(feature = "icu_locid_transform")]
impl From<LocaleTransformError> for ICU4XError {
    fn from(e: LocaleTransformError) -> Self {
        match e {
            LocaleTransformError::Data(e) => e.into(),
            _ => ICU4XError::UnknownError,
        }
        .log_original(&e)
    }
}

#[cfg(feature = "icu_segmenter")]
impl From<SegmenterError> for ICU4XError {
    fn from(e: SegmenterError) -> Self {
        match e {
            SegmenterError::Data(e) => e.into(),
            _ => ICU4XError::UnknownError,
        }
        .log_original(&e)
    }
}

#[cfg(feature = "icu_list")]
impl From<ListError> for ICU4XError {
    fn from(e: ListError) -> Self {
        match e {
            ListError::Data(e) => e.into(),
            _ => ICU4XError::UnknownError,
        }
        .log_original(&e)
    }
}

impl From<ParserError> for ICU4XError {
    fn from(e: ParserError) -> Self {
        match e {
            ParserError::InvalidLanguage => ICU4XError::LocaleParserLanguageError,
            ParserError::InvalidSubtag => ICU4XError::LocaleParserSubtagError,
            ParserError::InvalidExtension => ICU4XError::LocaleParserExtensionError,
            ParserError::DuplicatedExtension => ICU4XError::LocaleParserExtensionError,
            _ => ICU4XError::UnknownError,
        }
        .log_original(&e)
    }
}

#[cfg(any(feature = "icu_timezone", feature = "icu_datetime"))]
impl From<TimeZoneError> for ICU4XError {
    fn from(e: TimeZoneError) -> Self {
        match e {
            TimeZoneError::OffsetOutOfBounds => ICU4XError::TimeZoneOffsetOutOfBoundsError,
            TimeZoneError::InvalidOffset => ICU4XError::TimeZoneInvalidOffsetError,
            TimeZoneError::Data(err) => err.into(),
            _ => ICU4XError::UnknownError,
        }
        .log_original(&e)
    }
}

#[cfg(feature = "icu_normalizer")]
impl From<NormalizerError> for ICU4XError {
    fn from(e: NormalizerError) -> Self {
        match e {
            NormalizerError::FutureExtension => ICU4XError::NormalizerFutureExtensionError,
            NormalizerError::ValidationError => ICU4XError::NormalizerValidationError,
            NormalizerError::Data(err) => err.into(),
            _ => ICU4XError::UnknownError,
        }
        .log_original(&e)
    }
}

#[cfg(feature = "experimental_components")]
impl From<ConversionError> for ICU4XError {
    fn from(value: ConversionError) -> Self {
        match value {
            ConversionError::InvalidUnit => ICU4XError::InvalidCldrUnitIdentifierError,
            _ => ICU4XError::UnknownError,
        }
    }
}
