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

#include <LibUnicode/Calendar.h>
#include <LibUnicode/RustFFI.h>

namespace Unicode {

static constexpr bool is_valid_month_code_string(Utf16View month_code)
{
    // MonthCode :::
    //     M00L
    //     M0 NonZeroDigit L[opt]
    //     M NonZeroDigit DecimalDigit L[opt]
    auto length = month_code.length_in_code_units();

    if (length != 3 && length != 4)
        return false;

    if (month_code.code_unit_at(0) != 'M')
        return false;

    if (!is_ascii_digit(month_code.code_unit_at(1)) || !is_ascii_digit(month_code.code_unit_at(2)))
        return false;

    if (length == 3 && month_code.code_unit_at(1) == '0' && month_code.code_unit_at(2) == '0')
        return false;
    if (length == 4 && month_code.code_unit_at(3) != 'L')
        return false;

    return true;
}

// 12.2.1 ParseMonthCode ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-parsemonthcode
Optional<MonthCode> parse_month_code(Utf16View month_code)
{
    // 3. If ParseText(StringToCodePoints(monthCode), MonthCode) is a List of errors, throw a RangeError exception.
    if (!is_valid_month_code_string(month_code))
        return {};

    // 4. Let isLeapMonth be false.
    auto is_leap_month = false;

    // 5. If the length of monthCode = 4, then
    if (month_code.length_in_code_units() == 4) {
        // a. Assert: The fourth code unit of monthCode is 0x004C (LATIN CAPITAL LETTER L).
        VERIFY(month_code.code_unit_at(3) == 'L');

        // b. Set isLeapMonth to true.
        is_leap_month = true;
    }

    // 6. Let monthCodeDigits be the substring of monthCode from 1 to 3.

    // 7. Let monthNumber be ℝ(StringToNumber(monthCodeDigits)).
    auto month_number = static_cast<u8>((month_code.code_unit_at(1) - '0') * 10 + (month_code.code_unit_at(2) - '0'));

    // 8. Return the Record { [[MonthNumber]]: monthNumber, [[IsLeapMonth]]: isLeapMonth }.
    return MonthCode { month_number, is_leap_month };
}

// 12.2.2 CreateMonthCode ( monthNumber, isLeapMonth ), https://tc39.es/proposal-temporal/#sec-temporal-createmonthcode
Utf16String create_month_code(u8 month_number, bool is_leap_month)
{
    // 1. Assert: If isLeapMonth is false, monthNumber > 0.
    if (!is_leap_month)
        VERIFY(month_number > 0);

    // 2. Let numberPart be ToZeroPaddedDecimalString(monthNumber, 2).

    // 3. If isLeapMonth is true, then
    if (is_leap_month) {
        // a. Return the string-concatenation of the code unit 0x004D (LATIN CAPITAL LETTER M), numberPart, and the
        //    code unit 0x004C (LATIN CAPITAL LETTER L).
        u8 month_code[] = { 'M', static_cast<u8>('0' + (month_number / 10)), static_cast<u8>('0' + (month_number % 10)), 'L' };
        return Utf16String::from_ascii_without_validation(month_code);
    }

    // 4. Return the string-concatenation of the code unit 0x004D (LATIN CAPITAL LETTER M) and numberPart.
    u8 month_code[] = { 'M', static_cast<u8>('0' + (month_number / 10)), static_cast<u8>('0' + (month_number % 10)) };
    return Utf16String::from_ascii_without_validation(month_code);
}

static ReadonlyBytes month_code_as_ascii_bytes(Utf16View month_code, Bytes buffer)
{
    VERIFY(month_code.length_in_code_units() <= buffer.size());

    for (size_t i = 0; i < month_code.length_in_code_units(); ++i) {
        auto code_unit = month_code.code_unit_at(i);
        VERIFY(is_ascii(code_unit));
        buffer[i] = static_cast<u8>(code_unit);
    }

    return { buffer.data(), month_code.length_in_code_units() };
}

CalendarDate iso_date_to_calendar_date(StringView calendar, ISODate iso_date)
{
    auto result = FFI::icu_iso_date_to_calendar_date(calendar.bytes().data(), calendar.bytes().size(), iso_date.year, iso_date.month, iso_date.day);

    return CalendarDate {
        .era = {},
        .era_year = {},
        .year = result.year,
        .month = result.month,
        .month_code = Utf16String::from_ascii_without_validation({ result.month_code, result.month_code_length }),
        .day = result.day,
        .day_of_week = result.day_of_week,
        .day_of_year = result.day_of_year,
        .week_of_year = {},
        .days_in_week = result.days_in_week,
        .days_in_month = result.days_in_month,
        .days_in_year = result.days_in_year,
        .months_in_year = result.months_in_year,
        .in_leap_year = result.in_leap_year,
    };
}

Optional<ISODate> calendar_date_to_iso_date(StringView calendar, i32 year, u8 month, u8 day)
{
    auto result = FFI::icu_calendar_date_to_iso_date(calendar.bytes().data(), calendar.bytes().size(), year, month, day);
    if (!result.has_value)
        return {};

    return ISODate { result.iso_date.year, result.iso_date.month, result.iso_date.day };
}

Optional<ISODate> iso_year_and_month_code_to_iso_date(StringView calendar, i32 year, Utf16View month_code, u8 day)
{
    u8 month_code_buffer[4];
    auto month_code_bytes = month_code_as_ascii_bytes(month_code, month_code_buffer);
    auto result = FFI::icu_iso_year_and_month_code_to_iso_date(calendar.bytes().data(), calendar.bytes().size(), year, month_code_bytes.data(), month_code_bytes.size(), day);
    if (!result.has_value)
        return {};

    return ISODate { result.iso_date.year, result.iso_date.month, result.iso_date.day };
}

Optional<ISODate> calendar_year_and_month_code_to_iso_date(StringView calendar, i32 arithmetic_year, Utf16View month_code, u8 day)
{
    u8 month_code_buffer[4];
    auto month_code_bytes = month_code_as_ascii_bytes(month_code, month_code_buffer);
    auto result = FFI::icu_calendar_year_and_month_code_to_iso_date(calendar.bytes().data(), calendar.bytes().size(), arithmetic_year, month_code_bytes.data(), month_code_bytes.size(), day);
    if (!result.has_value)
        return {};

    return ISODate { result.iso_date.year, result.iso_date.month, result.iso_date.day };
}

u8 calendar_months_in_year(StringView calendar, i32 arithmetic_year)
{
    return FFI::icu_calendar_months_in_year(calendar.bytes().data(), calendar.bytes().size(), arithmetic_year);
}

u8 calendar_days_in_month(StringView calendar, i32 arithmetic_year, u8 ordinal_month)
{
    return FFI::icu_calendar_days_in_month(calendar.bytes().data(), calendar.bytes().size(), arithmetic_year, ordinal_month);
}

u8 calendar_max_days_in_month_code(StringView calendar, Utf16View month_code)
{
    u8 month_code_buffer[4];
    auto month_code_bytes = month_code_as_ascii_bytes(month_code, month_code_buffer);
    return FFI::icu_calendar_max_days_in_month_code(calendar.bytes().data(), calendar.bytes().size(), month_code_bytes.data(), month_code_bytes.size());
}

bool calendar_year_contains_month_code(StringView calendar, i32 arithmetic_year, Utf16View month_code)
{
    u8 month_code_buffer[4];
    auto month_code_bytes = month_code_as_ascii_bytes(month_code, month_code_buffer);
    return FFI::icu_year_contains_month_code(calendar.bytes().data(), calendar.bytes().size(), arithmetic_year, month_code_bytes.data(), month_code_bytes.size());
}

}
