// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{borrow::Cow, collections::HashSet};

use rustc_hash::FxHashMap;
use strum::IntoEnumIterator;
use zeroes_itoa::LeadingZeroBuffer;

use crate::{
    enums::{NumberLengthType, PhoneNumberFormat, PhoneNumberType},
    errors::{InvalidRegexError, ValidationError},
    generated::{
        proto::PhoneNumber, uniprops_digits::uniprops::get_digit_value, uniprops_without_nl,
    },
    interfaces::MatcherApi,
    phonenumberutil::{
        helper_types::PrefixParts,
        regex_wrapper_types::{PhoneMetadataWrapper, PhoneNumberDescWrapper},
    },
};

#[cfg(feature = "builtin_metadata")]
use crate::generated::{metadata::METADATA, proto::PhoneMetadataCollection};
#[cfg(feature = "builtin_metadata")]
use prost::{DecodeError, Message};

use super::helper_constants::{
    OPTIONAL_EXT_SUFFIX, PLUS_SIGN, POSSIBLE_CHARS_AFTER_EXT_LABEL,
    POSSIBLE_SEPARATORS_BETWEEN_NUMBER_AND_EXT_LABEL, RFC3966_EXTN_PREFIX, RFC3966_PREFIX,
};

/// Loads metadata from helper constants METADATA array
#[cfg(feature = "builtin_metadata")]
pub fn load_compiled_metadata() -> Result<PhoneMetadataCollection, DecodeError> {
    PhoneMetadataCollection::decode(METADATA)
}

/// Returns a pointer to the description inside the metadata of the appropriate
/// type.
pub fn get_number_desc_by_type(
    metadata: &PhoneMetadataWrapper,
    phone_number_type: PhoneNumberType,
) -> &PhoneNumberDescWrapper {
    match phone_number_type {
        PhoneNumberType::PremiumRate => &metadata.premium_rate,
        PhoneNumberType::TollFree => &metadata.toll_free,
        PhoneNumberType::Mobile => &metadata.mobile,
        PhoneNumberType::FixedLine | PhoneNumberType::FixedLineOrMobile => &metadata.fixed_line,
        PhoneNumberType::SharedCost => &metadata.shared_cost,
        PhoneNumberType::VoIP => &metadata.voip,
        PhoneNumberType::PersonalNumber => &metadata.personal_number,
        PhoneNumberType::Pager => &metadata.pager,
        PhoneNumberType::UAN => &metadata.uan,
        PhoneNumberType::VoiceMail => &metadata.voicemail,
        // Instead of the default case, we only match `Unknown`
        PhoneNumberType::Unknown => &metadata.general_desc,
    }
}

/// A helper function that is used by Format and FormatByPattern.
pub fn get_number_prefix_by_format_and_calling_code(
    country_calling_code: &'_ str,
    number_format: PhoneNumberFormat,
) -> PrefixParts<'_> {
    match number_format {
        PhoneNumberFormat::E164 => PrefixParts([PLUS_SIGN, country_calling_code, "", ""], 2),
        PhoneNumberFormat::International => {
            PrefixParts([PLUS_SIGN, country_calling_code, " ", ""], 3)
        }
        PhoneNumberFormat::RFC3966 => {
            PrefixParts([RFC3966_PREFIX, PLUS_SIGN, country_calling_code, "-"], 4)
        }
        // here code is already returned
        PhoneNumberFormat::National => PrefixParts([""; 4], 0),
    }
}

// Returns true when one national number is the suffix of the other or both are
// the same.
pub fn is_national_number_suffix_of_the_other(
    first_number: &PhoneNumber,
    second_number: &PhoneNumber,
) -> bool {
    let mut buf = itoa::Buffer::new();
    let first_number_national_number = buf.format(first_number.national_number);
    let mut buf = itoa::Buffer::new();
    let second_number_national_number = buf.format(second_number.national_number);
    // Note that HasSuffixString returns true if the numbers are equal.
    first_number_national_number.ends_with(second_number_national_number)
        || second_number_national_number.ends_with(first_number_national_number)
}

/// Helper method for constructing regular expressions for parsing. Creates an
/// expression that captures up to max_length digits.
pub fn extn_digits(max_length: u32) -> String {
    let mut buf = itoa::Buffer::new();
    let max_length_str = buf.format(max_length);
    const HELPER_STR_LEN: usize = 2 + 4 + 2;

    let mut expr = String::with_capacity(
        HELPER_STR_LEN + super::helper_constants::DIGITS.len() + max_length_str.len(),
    );

    expr.push_str("([");
    // Fully qualify DIGITS const as its common name
    expr.push_str(super::helper_constants::DIGITS);
    expr.push_str("]{1,");
    expr.push_str(max_length_str);
    expr.push_str("})");

    expr
}

pub fn get_national_significant_number<'b>(
    phone_number: &PhoneNumber,
    buf: &'b mut zeroes_itoa::LeadingZeroBuffer,
) -> Cow<'b, str> {
    buf.format(
        phone_number.national_number,
        if phone_number.italian_leading_zero() {
            phone_number
                .number_of_leading_zeros()
                .try_into()
                .unwrap_or(0)
        } else {
            0
        },
    )
}

pub fn get_national_significant_number_owned(phone_number: &PhoneNumber) -> String {
    let mut buf = LeadingZeroBuffer::new();
    get_national_significant_number(phone_number, &mut buf).to_string()
}

pub fn normalize_digits(string: &str) -> String {
    string
        .chars()
        .map(|char| {
            get_digit_value(char)
                .map(|digit| (digit + b'0') as char)
                .unwrap_or(char)
        })
        .collect()
}

// Helper initialiser method to create the regular-expression pattern to match
// extensions. Note that:
// - There are currently six capturing groups for the extension itself. If this
// number is changed, MaybeStripExtension needs to be updated.
// - The only capturing groups should be around the digits that you want to
// capture as part of the extension, or else parsing will fail!
pub fn create_extn_pattern(for_parsing: bool) -> String {
    // We cap the maximum length of an extension based on the ambiguity of the
    // way the extension is prefixed. As per ITU, the officially allowed
    // length for extensions is actually 40, but we don't support this since we
    // haven't seen real examples and this introduces many false interpretations
    // as the extension labels are not standardized.
    const EXT_LIMIT_AFTER_EXPLICIT_LABEL: u32 = 20;
    const EXT_LIMIT_AFTER_LIKELY_LABEL: u32 = 15;
    const EXT_LIMIT_AFTER_AMBIGUOUS_CHAR: u32 = 9;
    const EXT_LIMIT_WHEN_NOT_SURE: u32 = 6;

    #[cfg(all(feature = "lite", not(feature = "regex")))]
    const EXPLICIT_EXT_LABELS: &str = concat!(
        "(?:",
        "[eE]?[xX][tT]",
        "(?:[eE][nN][sS][iI](?:[oO\u{00D3}]\u{0301}?|[\u{00F3}\u{00D3}]))?[nN]?",
        "|",
        "(?:[\u{FF45}\u{FF25}])?[\u{FF58}\u{FF38}][\u{FF54}\u{FF34}](?:[\u{FF4E}\u{FF2E}])?",
        "|",
        "[\u{0434}\u{0414}][\u{043E}\u{041E}][\u{0431}\u{0411}]",
        "|",
        "[aA][nN][eE][xX][oO]",
        ")"
    );

    #[cfg(all(feature = "lite", not(feature = "regex")))]
    const AMBIGUOUS_EXT_LABELS: &str = concat!(
        "(?:",
        "[xX\u{FF58}\u{FF38}#\u{FF03}~\u{FF5E}]",
        "|",
        "[iI][nN][tT]",
        "|",
        "[\u{FF49}\u{FF29}][\u{FF4E}\u{FF2E}][\u{FF54}\u{FF34}]",
        ")"
    );

    #[cfg(feature = "regex")]
    const EXPLICIT_EXT_LABELS: &str = concat!(
        "(?:",
        "e?xt(?:ensi(?:o\u{0301}?|\u{00F3}))?n?",
        "|",
        "(?:\u{FF45})?\u{FF58}\u{FF54}(?:\u{FF4E})?",
        "|",
        "\u{0434}\u{043E}\u{0431}",
        "|",
        "anexo",
        ")"
    );

    #[cfg(feature = "regex")]
    const AMBIGUOUS_EXT_LABELS: &str = concat!(
        "(?:",
        "[x\u{FF58}#\u{FF03}~\u{FF5E}]",
        "|",
        "int",
        "|",
        "\u{FF49}\u{FF4E}\u{FF54}",
        ")"
    );

    const AMBIGUOUS_SEPARATOR: &str = "[- ]+";

    let rfc_extn = fast_cat::concat_str!(
        RFC3966_EXTN_PREFIX,
        &extn_digits(EXT_LIMIT_AFTER_EXPLICIT_LABEL)
    );
    let explicit_extn = fast_cat::concat_str!(
        POSSIBLE_SEPARATORS_BETWEEN_NUMBER_AND_EXT_LABEL,
        EXPLICIT_EXT_LABELS,
        POSSIBLE_CHARS_AFTER_EXT_LABEL,
        &extn_digits(EXT_LIMIT_AFTER_EXPLICIT_LABEL),
        OPTIONAL_EXT_SUFFIX
    );
    let ambiguous_extn = fast_cat::concat_str!(
        POSSIBLE_SEPARATORS_BETWEEN_NUMBER_AND_EXT_LABEL,
        AMBIGUOUS_EXT_LABELS,
        POSSIBLE_CHARS_AFTER_EXT_LABEL,
        &extn_digits(EXT_LIMIT_AFTER_AMBIGUOUS_CHAR),
        OPTIONAL_EXT_SUFFIX
    );

    let american_style_extn_with_suffix = fast_cat::concat_str!(
        AMBIGUOUS_SEPARATOR,
        &extn_digits(EXT_LIMIT_WHEN_NOT_SURE),
        "#"
    );

    // The first regular expression covers RFC 3966 format, where the extension is
    // added using ";ext=". The second more generic where extension is mentioned
    // with explicit labels like "ext:". In both the above cases we allow more
    // numbers in extension than any other extension labels. The third one
    // captures when single character extension labels or less commonly used
    // labels are present. In such cases we capture fewer extension digits in
    // order to reduce the chance of falsely interpreting two numbers beside each
    // other as a number + extension. The fourth one covers the special case of
    // American numbers where the extension is written with a hash at the end,
    // such as "- 503#".
    let extension_pattern = fast_cat::concat_str!(
        &rfc_extn,
        "|",
        &explicit_extn,
        "|",
        &ambiguous_extn,
        "|",
        &american_style_extn_with_suffix
    );
    // Additional pattern that is supported when parsing extensions, not when
    // matching.
    if for_parsing {
        // ",," is commonly used for auto dialling the extension when connected.
        // Semi-colon works in Iphone and also in Android to pop up a button with
        // the extension number following.
        let auto_dialling_and_ext_labels_found = "(?:,{2}|;)";
        // This is same as kPossibleSeparatorsBetweenNumberAndExtLabel, but not
        // matching comma as extension label may have it.
        let possible_separators_number_ext_label_no_comma = "[ \u{00A0}\t]*";

        let auto_dialling_extn = fast_cat::concat_str!(
            possible_separators_number_ext_label_no_comma,
            auto_dialling_and_ext_labels_found,
            POSSIBLE_CHARS_AFTER_EXT_LABEL,
            &extn_digits(EXT_LIMIT_AFTER_LIKELY_LABEL),
            OPTIONAL_EXT_SUFFIX
        );
        let only_commas_extn = fast_cat::concat_str!(
            possible_separators_number_ext_label_no_comma,
            "(?:,)+",
            POSSIBLE_CHARS_AFTER_EXT_LABEL,
            &extn_digits(EXT_LIMIT_AFTER_AMBIGUOUS_CHAR),
            OPTIONAL_EXT_SUFFIX
        );
        // Here the first pattern is exclusive for extension autodialling formats
        // which are used when dialling and in this case we accept longer
        // extensions. However, the second pattern is more liberal on number of
        // commas that acts as extension labels, so we have strict cap on number of
        // digits in such extensions.
        return fast_cat::concat_str!(
            &extension_pattern,
            "|",
            &auto_dialling_extn,
            "|",
            &only_commas_extn
        );
    }
    extension_pattern
}

/// Normalizes a string of characters representing a phone number by replacing
/// all characters found in the accompanying map with the values therein, and
/// stripping all other characters if remove_non_matches is true.
///
/// Parameters:
/// * `number` - a pointer to a string of characters representing a phone number to
///   be normalized.
/// * `normalization_replacements` - a mapping of characters to what they should be
///   replaced by in the normalized version of the phone number
/// * `remove_non_matches` - indicates whether characters that are not able to be
///   replaced should be stripped from the number. If this is false, they will be
///   left unchanged in the number.
///
/// Returns: normalized_string
pub fn normalize_helper(
    normalization_replacements: &FxHashMap<char, char>,
    remove_non_matches: bool,
    phone_number: &str,
) -> String {
    let mut normalized_number = String::with_capacity(phone_number.len());
    // Skip UTF checking because strings in rust are valid UTF-8 already
    for phone_char in phone_number.chars() {
        if let Some(replacement) = normalization_replacements.get(&phone_char.to_ascii_uppercase())
        {
            normalized_number.push(*replacement);
        } else if !remove_non_matches {
            normalized_number.push(phone_char);
        }
        // If neither of the above are true, we remove this character.
    }

    normalized_number
}

/// Returns `true` if there is any possible number data set for a particular
/// PhoneNumberDesc.
pub fn desc_has_possible_number_data(desc: &PhoneNumberDescWrapper) -> bool {
    // If this is empty, it means numbers of this type inherit from the "general
    // desc" -> the value "-1" means that no numbers exist for this type.
    desc.original.possible_length.len() != 1
        || desc
            .original
            .possible_length
            .first()
            .map(|l| *l != -1)
            .unwrap_or(false)
}

/// Note: `DescHasData` must account for any of MetadataFilter's
/// excludableChildFields potentially being absent from the metadata. It must
/// check them all. For any changes in `DescHasData`, ensure that all the
/// excludableChildFields are still being checked.
///
/// If your change is safe simply
/// mention why during a review without needing to change MetadataFilter.
///
/// Returns `true` if there is any data set for a particular PhoneNumberDesc.
pub fn desc_has_data(desc: &PhoneNumberDescWrapper) -> bool {
    // Checking most properties since we don't know what's present, since a custom
    // build may have stripped just one of them (e.g. USE_METADATA_LITE strips
    // exampleNumber). We don't bother checking the PossibleLengthsLocalOnly,
    // since if this is the only thing that's present we don't really support the
    // type at all: no type-specific methods will work with only this data.
    desc.original.example_number.is_some()
        || desc_has_possible_number_data(desc)
        || desc.national_number_pattern().pattern_base.is_some()
}

/// Returns the types we have metadata for based on the PhoneMetadata object
/// passed in.
pub fn populate_supported_types_for_metadata(
    metadata: &PhoneMetadataWrapper,
    types: &mut HashSet<PhoneNumberType>,
) {
    PhoneNumberType::iter()
        // Never return FIXED_LINE_OR_MOBILE (it is a convenience type, and
        // represents that a particular number type can't be
        // determined) or UNKNOWN (the non-type).
        .filter(|number_type| {
            !matches!(
                number_type,
                PhoneNumberType::FixedLineOrMobile | PhoneNumberType::Unknown
            )
        })
        .filter(|number_type| desc_has_data(get_number_desc_by_type(metadata, *number_type)))
        .for_each(|number_type| {
            types.insert(number_type);
        });
}

pub fn get_supported_types_for_metadata(
    metadata: &PhoneMetadataWrapper,
) -> HashSet<PhoneNumberType> {
    const EFFECTIVE_NUMBER_TYPES: usize = 11 /* count */ - 2 /* filter type or unknown */;
    let mut types = HashSet::with_capacity(EFFECTIVE_NUMBER_TYPES);
    populate_supported_types_for_metadata(metadata, &mut types);
    types
}

#[inline]
fn build_length_mask(lengths: &[i32]) -> u64 {
    let mut mask = 0u64;
    for &l in lengths {
        if l != -1 {
            mask |= 1 << l;
        } else {
            mask = 0;
        }
    }
    mask
}

/// Helper method to check a number against possible lengths for this number
/// type, and determine whether it matches, or is too short or too long.
/// Metadata REQUIRED to have AT MOST 63 possible lengths
pub(crate) fn test_number_length(
    phone_number: &str,
    phone_metadata: &PhoneMetadataWrapper,
    phone_number_type: PhoneNumberType,
) -> Result<NumberLengthType, ValidationError> {
    let desc_for_type = get_number_desc_by_type(phone_metadata, phone_number_type);
    let mut possible_mask = if desc_for_type.original.possible_length.is_empty() {
        build_length_mask(&phone_metadata.general_desc.original.possible_length)
    } else {
        build_length_mask(&desc_for_type.original.possible_length)
    };

    let mut local_mask = build_length_mask(&desc_for_type.original.possible_length_local_only);

    if phone_number_type == PhoneNumberType::FixedLineOrMobile {
        let fixed_line_desc = get_number_desc_by_type(phone_metadata, PhoneNumberType::FixedLine);
        if !desc_has_possible_number_data(fixed_line_desc) {
            return test_number_length(phone_number, phone_metadata, PhoneNumberType::Mobile);
        } else {
            let mobile_desc = get_number_desc_by_type(phone_metadata, PhoneNumberType::Mobile);
            if desc_has_possible_number_data(mobile_desc) {
                let len_to_append = if mobile_desc.original.possible_length.is_empty() {
                    &phone_metadata.general_desc.original.possible_length
                } else {
                    &mobile_desc.original.possible_length
                };

                possible_mask |= build_length_mask(len_to_append);
                local_mask |= build_length_mask(&mobile_desc.original.possible_length_local_only);
            }
        }
    }

    if possible_mask == 0 {
        return Err(ValidationError::InvalidLength);
    }

    let actual_length = phone_number.len() as u32;
    if actual_length >= 64 {
        return Err(ValidationError::TooLong);
    }

    let actual_length_bit = 1u64 << actual_length;
    if (local_mask & actual_length_bit) != 0 {
        return Ok(NumberLengthType::IsPossibleLocalOnly);
    }

    let minimum_length = possible_mask.trailing_zeros();
    let maximum_length = 63 - possible_mask.leading_zeros();

    if minimum_length == actual_length {
        return Ok(NumberLengthType::IsPossible);
    } else if minimum_length > actual_length {
        return Err(ValidationError::TooShort);
    } else if maximum_length < actual_length {
        return Err(ValidationError::TooLong);
    }

    if (possible_mask & actual_length_bit) != 0 {
        Ok(NumberLengthType::IsPossible)
    } else {
        Err(ValidationError::InvalidLength)
    }
}

/// Helper method to check a number against possible lengths for this region,
/// based on the metadata being passed in, and determine whether it matches, or
/// is too short or too long.
pub fn test_number_length_with_unknown_type(
    phone_number: &str,
    phone_metadata: &PhoneMetadataWrapper,
) -> Result<NumberLengthType, ValidationError> {
    test_number_length(phone_number, phone_metadata, PhoneNumberType::Unknown)
}

/// Returns a new phone number containing only the fields needed to uniquely
/// identify a phone number, rather than any fields that capture the context in
/// which the phone number was created.
/// These fields correspond to those set in `parse()` rather than
/// `parse_and_keep_raw_input()`.
pub(crate) fn copy_core_fields_only(from_number: &PhoneNumber) -> PhoneNumber {
    PhoneNumber {
        country_code: from_number.country_code,
        national_number: from_number.national_number,
        extension: from_number.extension.clone(),
        italian_leading_zero: from_number.italian_leading_zero,
        number_of_leading_zeros: from_number.number_of_leading_zeros,
        ..Default::default()
    }
}

/// Determines whether the given number is a national number match for the given
/// PhoneNumberDesc. Does not check against possible lengths!
pub fn is_match(
    matcher_api: &dyn MatcherApi,
    number: &str,
    number_desc: &PhoneNumberDescWrapper,
) -> Result<bool, InvalidRegexError> {
    matcher_api.match_national_number(number, number_desc, false)
}

pub fn is_unwanted_end_char(c: char) -> bool {
    c != '#' && uniprops_without_nl::uniprops::Category::from_char(c).is_some()
}
