#include "ui_core/color.h"

namespace pp::ui {

namespace {

[[nodiscard]] int hex_value(char ch) noexcept
{
    if (ch >= '0' && ch <= '9') {
        return ch - '0';
    }
    if (ch >= 'a' && ch <= 'f') {
        return 10 + (ch - 'a');
    }
    if (ch >= 'A' && ch <= 'F') {
        return 10 + (ch - 'A');
    }
    return -1;
}

[[nodiscard]] pp::foundation::Result<std::uint8_t> parse_hex_byte(std::string_view value) noexcept
{
    const auto high = hex_value(value[0]);
    const auto low = hex_value(value[1]);
    if (high < 0 || low < 0) {
        return pp::foundation::Result<std::uint8_t>::failure(
            pp::foundation::Status::invalid_argument("color contains a non-hex character"));
    }

    return pp::foundation::Result<std::uint8_t>::success(
        static_cast<std::uint8_t>((high << 4) | low));
}

[[nodiscard]] pp::foundation::Result<std::uint8_t> parse_hex_nibble(char value) noexcept
{
    const auto nibble = hex_value(value);
    if (nibble < 0) {
        return pp::foundation::Result<std::uint8_t>::failure(
            pp::foundation::Status::invalid_argument("color contains a non-hex character"));
    }

    return pp::foundation::Result<std::uint8_t>::success(
        static_cast<std::uint8_t>((nibble << 4) | nibble));
}

}

pp::foundation::Result<ColorRgba8> parse_hex_color(std::string_view value) noexcept
{
    if (value.empty()) {
        return pp::foundation::Result<ColorRgba8>::failure(
            pp::foundation::Status::invalid_argument("color must not be empty"));
    }

    if (value.front() != '#') {
        return pp::foundation::Result<ColorRgba8>::failure(
            pp::foundation::Status::invalid_argument("color must start with #"));
    }

    const auto hex = value.substr(1);
    if (hex.size() != 3U && hex.size() != 4U && hex.size() != 6U && hex.size() != 8U) {
        return pp::foundation::Result<ColorRgba8>::failure(
            pp::foundation::Status::invalid_argument("color must use #rgb, #rgba, #rrggbb, or #rrggbbaa"));
    }

    ColorRgba8 color;
    if (hex.size() == 3U || hex.size() == 4U) {
        const auto r = parse_hex_nibble(hex[0]);
        const auto g = parse_hex_nibble(hex[1]);
        const auto b = parse_hex_nibble(hex[2]);
        const auto a = hex.size() == 4U ? parse_hex_nibble(hex[3])
                                        : pp::foundation::Result<std::uint8_t>::success(255);
        if (!r || !g || !b || !a) {
            return pp::foundation::Result<ColorRgba8>::failure(
                pp::foundation::Status::invalid_argument("color contains a non-hex character"));
        }
        color = ColorRgba8 { .r = r.value(), .g = g.value(), .b = b.value(), .a = a.value() };
        return pp::foundation::Result<ColorRgba8>::success(color);
    }

    const auto r = parse_hex_byte(hex.substr(0, 2));
    const auto g = parse_hex_byte(hex.substr(2, 2));
    const auto b = parse_hex_byte(hex.substr(4, 2));
    const auto a = hex.size() == 8U ? parse_hex_byte(hex.substr(6, 2))
                                    : pp::foundation::Result<std::uint8_t>::success(255);
    if (!r || !g || !b || !a) {
        return pp::foundation::Result<ColorRgba8>::failure(
            pp::foundation::Status::invalid_argument("color contains a non-hex character"));
    }

    color = ColorRgba8 { .r = r.value(), .g = g.value(), .b = b.value(), .a = a.value() };
    return pp::foundation::Result<ColorRgba8>::success(color);
}

}
