/*
 * Copyright (c) 2021-2025, Tim Flynn <trflynn89@ladybird.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibJS/Runtime/Intl/AbstractOperations.h>
#include <LibJS/Runtime/Intl/DisplayNames.h>
#include <LibJS/Runtime/VM.h>

namespace JS::Intl {

GC_DEFINE_ALLOCATOR(DisplayNames);

// 12 DisplayNames Objects, https://tc39.es/ecma402/#intl-displaynames-objects
DisplayNames::DisplayNames(Object& prototype)
    : IntlObject(ConstructWithPrototypeTag::Tag, prototype)
{
}

// 12.2.3 Internal slots, https://tc39.es/ecma402/#sec-Intl.DisplayNames-internal-slots
ReadonlySpan<Utf16View> DisplayNames::relevant_extension_keys() const
{
    // The value of the [[RelevantExtensionKeys]] internal slot is « ».
    return {};
}

// 12.2.3 Internal slots, https://tc39.es/ecma402/#sec-Intl.DisplayNames-internal-slots
ReadonlySpan<ResolutionOptionDescriptor> DisplayNames::resolution_option_descriptors(VM&) const
{
    // The value of the [[ResolutionOptionDescriptors]] internal slot is « ».
    return {};
}

void DisplayNames::set_type(Utf16View type)
{
    if (type == "language"sv)
        m_type = Type::Language;
    else if (type == "region"sv)
        m_type = Type::Region;
    else if (type == "script"sv)
        m_type = Type::Script;
    else if (type == "currency"sv)
        m_type = Type::Currency;
    else if (type == "calendar"sv)
        m_type = Type::Calendar;
    else if (type == "dateTimeField"sv)
        m_type = Type::DateTimeField;
    else
        VERIFY_NOT_REACHED();
}

Utf16String DisplayNames::type_string() const
{
    switch (m_type) {
    case Type::Language:
        return "language"_utf16;
    case Type::Region:
        return "region"_utf16;
    case Type::Script:
        return "script"_utf16;
    case Type::Currency:
        return "currency"_utf16;
    case Type::Calendar:
        return "calendar"_utf16;
    case Type::DateTimeField:
        return "dateTimeField"_utf16;
    default:
        VERIFY_NOT_REACHED();
    }
}

void DisplayNames::set_fallback(Utf16View fallback)
{
    if (fallback == "none"sv)
        m_fallback = Fallback::None;
    else if (fallback == "code"sv)
        m_fallback = Fallback::Code;
    else
        VERIFY_NOT_REACHED();
}

Utf16String DisplayNames::fallback_string() const
{
    switch (m_fallback) {
    case Fallback::None:
        return "none"_utf16;
    case Fallback::Code:
        return "code"_utf16;
    default:
        VERIFY_NOT_REACHED();
    }
}

// 12.5.1 CanonicalCodeForDisplayNames ( type, code ), https://tc39.es/ecma402/#sec-canonicalcodefordisplaynames
ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::Type type, Utf16View code)
{
    // 1. If type is "language", then
    if (type == DisplayNames::Type::Language) {
        // a. If code does not match the unicode_language_id production, throw a RangeError exception.
        if (!Unicode::parse_unicode_language_id(code).has_value())
            return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "language"sv);

        // b. If IsWellFormedLanguageTag(code) is false, throw a RangeError exception.
        if (!is_well_formed_language_tag(code))
            return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, code);

        // c. Return ! CanonicalizeUnicodeLocaleId(code).
        auto canonicalized_tag = canonicalize_unicode_locale_id(code);
        return PrimitiveString::create(vm, canonicalized_tag);
    }

    // 2. If type is "region", then
    if (type == DisplayNames::Type::Region) {
        // a. If code does not match the unicode_region_subtag production, throw a RangeError exception.
        if (!Unicode::is_unicode_region_subtag(code))
            return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "region"sv);

        // b. Return the ASCII-uppercase of code.
        return PrimitiveString::create(vm, code.to_ascii_uppercase());
    }

    // 3. If type is "script", then
    if (type == DisplayNames::Type::Script) {
        // a. If code does not match the unicode_script_subtag production, throw a RangeError exception.
        if (!Unicode::is_unicode_script_subtag(code))
            return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "script"sv);

        // Assert: The length of code is 4, and every code unit of code represents an ASCII letter (0x0041 through 0x005A and 0x0061 through 0x007A, both inclusive).
        VERIFY(code.length_in_code_units() == 4);
        VERIFY(all_of(code, is_ascii_alpha));

        // c. Let first be the ASCII-uppercase of the substring of code from 0 to 1.
        // d. Let rest be the ASCII-lowercase of the substring of code from 1.
        // e. Return the string-concatenation of first and rest.
        return PrimitiveString::create(vm, code.to_ascii_titlecase());
    }

    // 4. If type is "calendar", then
    if (type == DisplayNames::Type::Calendar) {
        // a. If code does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
        if (!Unicode::is_type_identifier(code))
            return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);

        // b. If code uses any of the backwards compatibility syntax described in Unicode Technical Standard #35 LDML § 3.3 BCP 47 Conformance, throw a RangeError exception.
        if (code.contains(u"_"sv))
            return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);

        // c. Return the ASCII-lowercase of code.
        return PrimitiveString::create(vm, code.to_ascii_lowercase());
    }

    // 5. If type is "dateTimeField", then
    if (type == DisplayNames::Type::DateTimeField) {
        // a. If the result of IsValidDateTimeFieldCode(code) is false, throw a RangeError exception.
        if (!is_valid_date_time_field_code(code))
            return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "dateTimeField"sv);

        // b. Return code.
        return PrimitiveString::create(vm, Utf16String::from_utf16(code));
    }

    // 6. Assert: type is "currency".
    VERIFY(type == DisplayNames::Type::Currency);

    // 7. If ! IsWellFormedCurrencyCode(code) is false, throw a RangeError exception.
    if (!is_well_formed_currency_code(code))
        return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "currency"sv);

    // 8. Return the ASCII-uppercase of code.
    return PrimitiveString::create(vm, code.to_ascii_uppercase());
}

// 12.5.2 IsValidDateTimeFieldCode ( field ), https://tc39.es/ecma402/#sec-isvaliddatetimefieldcode
bool is_valid_date_time_field_code(Utf16View field)
{
    // 1. If field is listed in the Code column of Table 19, return true.
    // 2. Return false.
    return field.is_one_of("era"sv, "year"sv, "quarter"sv, "month"sv, "weekOfYear"sv, "weekday"sv, "day"sv, "dayPeriod"sv, "hour"sv, "minute"sv, "second"sv, "timeZoneName"sv);
}

}
