defmodule ExPhoneNumber.Extraction do
  @moduledoc false

  import ExPhoneNumber.Normalization
  import ExPhoneNumber.Validation
  import ExPhoneNumber.Utilities
  alias ExPhoneNumber.Constants.CountryCodeSource
  alias ExPhoneNumber.Constants.ErrorMessages
  alias ExPhoneNumber.Constants.Patterns
  alias ExPhoneNumber.Constants.ValidationResults
  alias ExPhoneNumber.Constants.Values
  alias ExPhoneNumber.Metadata
  alias ExPhoneNumber.Metadata.PhoneMetadata
  alias ExPhoneNumber.Model.PhoneNumber

  def extract_country_code(full_number) when is_binary(full_number) do
    if String.length(full_number) == 0 or String.at(full_number, 0) == "0" do
      {0, ""}
    else
      extract_country_code(1, full_number)
    end
  end

  def extract_country_code(index, full_number) when is_number(index) and is_binary(full_number) do
    if not (index <= Values.max_length_country_code() and index <= String.length(full_number)) do
      {0, ""}
    else
      split = String.split_at(full_number, index)

      case Integer.parse(elem(split, 0)) do
        {int, _bin} ->
          if Metadata.is_valid_country_code?(int) do
            {int, elem(split, 1)}
          else
            extract_country_code(index + 1, full_number)
          end

        :error ->
          {0, ""}
      end
    end
  end

  def extract_possible_number(number_to_parse) do
    case Regex.run(Patterns.valid_start_char_pattern(), number_to_parse, return: :index) do
      [{index, _length} | _tail] ->
        {_head, tail} = String.split_at(number_to_parse, index)

        tail
        |> split_at_match_and_return_head(Patterns.unwanted_end_char_pattern())
        |> split_at_match_and_return_head(Patterns.second_number_start_pattern())

      nil ->
        ""
    end
  end

  def maybe_extract_country_code(number, _metadata, _keep_raw_input) when number == [],
    do: {false, number, %{country_code: 0}}

  def maybe_extract_country_code(number, metadata, keep_raw_input)
      when is_binary(number) and is_boolean(keep_raw_input) do
    possible_country_idd_prefix = if not is_nil(metadata), do: metadata.international_prefix

    possible_country_idd_prefix = if is_nil(possible_country_idd_prefix), do: "NonMatch", else: possible_country_idd_prefix

    {country_code_source, full_number} = maybe_strip_international_prefix_and_normalize(number, possible_country_idd_prefix)

    phone_number =
      if keep_raw_input,
        do: %PhoneNumber{country_code_source: country_code_source},
        else: %PhoneNumber{}

    if country_code_source != CountryCodeSource.from_default_country() do
      if String.length(full_number) <= Values.min_length_for_nsn() do
        {false, ErrorMessages.too_short_after_idd()}
      else
        {country_code, national_number} = extract_country_code(full_number)

        if country_code != 0 do
          {true, national_number, %{phone_number | country_code: country_code}}
        else
          {false, ErrorMessages.invalid_country_code()}
        end
      end
    else
      if not is_nil(metadata) do
        country_code_string = Integer.to_string(metadata.country_code)

        if String.starts_with?(full_number, country_code_string) do
          {_, potential_national_number} = String.split_at(full_number, String.length(country_code_string))

          {result, _, possible_national_number} = maybe_strip_national_prefix_and_carrier_code(potential_national_number, metadata)

          potential_national_number = if result, do: possible_national_number, else: potential_national_number

          if (not matches_entirely?(metadata.general.national_number_pattern, full_number) and
                matches_entirely?(
                  metadata.general.national_number_pattern,
                  potential_national_number
                )) or
               test_number_length(full_number, metadata) == ValidationResults.too_long() do
            phone_number =
              if keep_raw_input,
                do: %{
                  phone_number
                  | country_code: metadata.country_code,
                    country_code_source: CountryCodeSource.from_number_without_plus_sign()
                },
                else: phone_number

            {true, potential_national_number, %{phone_number | country_code: metadata.country_code}}
          else
            {true, full_number, %{phone_number | country_code: 0}}
          end
        else
          {true, full_number, %{phone_number | country_code: 0}}
        end
      else
        {true, full_number, %{phone_number | country_code: 0}}
      end
    end
  end

  def maybe_strip_extension(number) do
    case Regex.run(Patterns.extn_pattern(), number, return: :index) do
      [{index, _} | tail] ->
        {phone_number_head, _} = String.split_at(number, index)

        if is_viable_phone_number?(phone_number_head) do
          {match_index, match_length} =
            Enum.find(tail, fn {match_index, match_length} ->
              if match_index > 0 do
                match = Kernel.binary_part(number, match_index, match_length)
                match != ""
              else
                false
              end
            end)

          ext = Kernel.binary_part(number, match_index, match_length)
          {ext, phone_number_head}
        else
          {"", number}
        end

      nil ->
        {"", number}
    end
  end

  def maybe_strip_international_prefix_and_normalize(number, _possible_country_idd_prefix)
      when number == [],
      do: {CountryCodeSource.from_default_country(), ""}

  def maybe_strip_international_prefix_and_normalize(number, possible_country_idd_prefix) do
    if Regex.match?(Patterns.leading_plus_chars_pattern(), number) do
      stripped_plus_sign = Regex.replace(Patterns.leading_plus_chars_pattern(), number, "")
      {CountryCodeSource.from_number_with_plus_sign(), normalize(stripped_plus_sign)}
    else
      case Regex.compile(possible_country_idd_prefix) do
        {:error, _} ->
          {CountryCodeSource.from_default_country(), ""}

        {:ok, regex} ->
          normalized_number = normalize(number)

          case parse_prefix_as_idd(regex, normalized_number) do
            {false, _} -> {CountryCodeSource.from_default_country(), normalized_number}
            {true, stripped_idd} -> {CountryCodeSource.from_number_with_idd(), stripped_idd}
          end
      end
    end
  end

  def maybe_strip_national_prefix_and_carrier_code(number, %PhoneMetadata{} = metadata) do
    maybe_strip_national_prefix_and_carrier_code(
      number,
      metadata.national_prefix_for_parsing,
      metadata.national_prefix_transform_rule,
      metadata.general.national_number_pattern
    )
  end

  def maybe_strip_national_prefix_and_carrier_code(
        number,
        national_prefix_for_parsing,
        _national_prefix_transform_rule,
        _general_national_number_pattern
      )
      when is_nil(number) or is_nil(national_prefix_for_parsing),
      do: {false, "", number}

  def maybe_strip_national_prefix_and_carrier_code(
        number,
        national_prefix_for_parsing,
        national_prefix_transform_rule,
        general_national_number_pattern
      )
      when is_binary(number) and is_binary(national_prefix_for_parsing) do
    prefix_pattern = ~r/^(?:#{national_prefix_for_parsing})/

    case Regex.run(prefix_pattern, number) do
      nil ->
        {false, "", number}

      matches ->
        is_viable_original_number = matches_entirely?(general_national_number_pattern, number)
        number_of_groups = if length(matches) == 1, do: 1, else: length(matches) - 1
        match = Enum.at(matches, number_of_groups)
        number_stripped = elem(String.split_at(number, String.length(Enum.at(matches, 0))), 1)
        is_nil_transform_rule = is_nil_or_empty?(national_prefix_transform_rule)
        is_nil_match = is_nil_or_empty?(match)
        no_transform = is_nil_transform_rule or is_nil_match

        if no_transform do
          if is_viable_original_number and
               not matches_entirely?(general_national_number_pattern, number_stripped) do
            {false, "", number}
          else
            carrier_code =
              if length(matches) > 0 and not is_nil(Enum.at(matches, number_of_groups)),
                do: Enum.at(matches, 1)

            {true, carrier_code, number_stripped}
          end
        else
          transformed_number = Regex.replace(prefix_pattern, number, national_prefix_transform_rule)

          if is_viable_original_number and
               not matches_entirely?(general_national_number_pattern, transformed_number) do
            {false, "", number}
          else
            carrier_code = if length(matches) > 0, do: Enum.at(matches, 1)
            {true, carrier_code, transformed_number}
          end
        end
    end
  end

  def parse_prefix_as_idd(pattern, number) do
    case Regex.run(pattern, number, return: :index) do
      [{index, match_length} | _tail] ->
        if index == 0 do
          {_, number_tail} = String.split_at(number, match_length)
          matches = Regex.run(Patterns.capturing_digit_pattern(), number_tail)
          match_is_nil = is_nil(matches)
          capture = if match_is_nil, do: "", else: Enum.at(matches, 1)
          normalized_group = normalize_digits_only(capture)

          if normalized_group == "0" do
            {false, number}
          else
            {true, number_tail}
          end
        else
          {false, number}
        end

      nil ->
        {false, number}
    end
  end
end
