#include "assets/brush_package.h"

#include <cctype>
#include <utility>

namespace pp::assets {
namespace {

[[nodiscard]] std::uint16_t read_u16_le(std::span<const std::byte> bytes, std::size_t offset) noexcept
{
    const auto lo = static_cast<std::uint16_t>(std::to_integer<unsigned char>(bytes[offset]));
    const auto hi = static_cast<std::uint16_t>(std::to_integer<unsigned char>(bytes[offset + 1U]));
    return static_cast<std::uint16_t>(lo | static_cast<std::uint16_t>(hi << 8U));
}

[[nodiscard]] bool is_word_extension(std::string_view value) noexcept
{
    if (value.empty()) {
        return false;
    }

    for (const char raw : value) {
        const auto ch = static_cast<unsigned char>(raw);
        if (std::isalnum(ch) == 0 && ch != '_') {
            return false;
        }
    }

    return true;
}

} // namespace

pp::foundation::Status validate_ppbr_header(
    std::string_view magic,
    std::uint16_t major,
    std::uint16_t minor) noexcept
{
    if (magic != "PPBR") {
        return pp::foundation::Status::invalid_argument("PPBR header magic is invalid");
    }

    // DEBT-0049: preserve legacy version acceptance until PPBR compatibility fixtures exist.
    if (major != ppbr_legacy_major_version && minor != ppbr_legacy_minor_version) {
        return pp::foundation::Status::invalid_argument("PPBR version is unsupported");
    }

    return pp::foundation::Status::success();
}

pp::foundation::Result<PpbrHeader> parse_ppbr_header(std::span<const std::byte> bytes) noexcept
{
    if (bytes.size() < ppbr_header_size) {
        return pp::foundation::Result<PpbrHeader>::failure(
            pp::foundation::Status::out_of_range("PPBR header is truncated"));
    }

    const std::string_view magic(reinterpret_cast<const char*>(bytes.data()), 4U);
    const auto major = read_u16_le(bytes, 4U);
    const auto minor = read_u16_le(bytes, 6U);
    const auto status = validate_ppbr_header(magic, major, minor);
    if (!status.ok()) {
        return pp::foundation::Result<PpbrHeader>::failure(status);
    }

    return pp::foundation::Result<PpbrHeader>::success(PpbrHeader {
        .major = major,
        .minor = minor,
    });
}

pp::foundation::Result<std::string> normalize_ppbr_export_path(std::string_view requested_path)
{
    if (requested_path.empty()) {
        return pp::foundation::Result<std::string>::failure(
            pp::foundation::Status::invalid_argument("PPBR export path must not be empty"));
    }

    std::string path(requested_path);
    if (requested_path.find(".ppbr") == std::string_view::npos) {
        path += ".ppbr";
    }

    return pp::foundation::Result<std::string>::success(std::move(path));
}

pp::foundation::Result<PpbrExportPaths> plan_ppbr_export_paths(
    std::string_view requested_path,
    std::string_view override_data_directory,
    bool export_data,
    PpbrDataDirectoryPolicy data_directory_policy)
{
    const auto normalized = normalize_ppbr_export_path(requested_path);
    if (!normalized) {
        return pp::foundation::Result<PpbrExportPaths>::failure(normalized.status());
    }

    const auto slash = normalized.value().find_last_of("/\\");
    if (slash == std::string::npos || slash + 1U >= normalized.value().size()) {
        return pp::foundation::Result<PpbrExportPaths>::failure(
            pp::foundation::Status::invalid_argument("PPBR export path must include a directory and file name"));
    }

    const auto dot = normalized.value().find_last_of('.');
    if (dot == std::string::npos || dot <= slash + 1U || dot + 1U >= normalized.value().size()) {
        return pp::foundation::Result<PpbrExportPaths>::failure(
            pp::foundation::Status::invalid_argument("PPBR export path must include a file extension"));
    }

    PpbrExportPaths paths;
    paths.package_path = normalized.value();
    paths.directory = normalized.value().substr(0, slash);
    paths.stem = normalized.value().substr(slash + 1U, dot - slash - 1U);
    paths.extension = normalized.value().substr(dot + 1U);
    if (!is_word_extension(paths.extension)) {
        return pp::foundation::Result<PpbrExportPaths>::failure(
            pp::foundation::Status::invalid_argument("PPBR export path extension contains unsupported characters"));
    }

    if (data_directory_policy == PpbrDataDirectoryPolicy::override_directory) {
        paths.data_directory = std::string(override_data_directory) + "/" + paths.stem + "_data";
    } else {
        paths.data_directory = paths.directory + "/" + paths.stem + "_data";
    }
    paths.data_directory_enabled = export_data && !paths.data_directory.empty();

    return pp::foundation::Result<PpbrExportPaths>::success(std::move(paths));
}

pp::foundation::Result<BrushPackageImageTargetPaths> plan_brush_package_image_target_paths(
    std::string_view data_path,
    BrushPackageImageKind kind,
    std::string_view image_name,
    std::string_view image_extension)
{
    if (data_path.empty()) {
        return pp::foundation::Result<BrushPackageImageTargetPaths>::failure(
            pp::foundation::Status::invalid_argument("brush package data path must not be empty"));
    }
    if (image_name.empty()) {
        return pp::foundation::Result<BrushPackageImageTargetPaths>::failure(
            pp::foundation::Status::invalid_argument("brush package image name must not be empty"));
    }
    if (!is_word_extension(image_extension)) {
        return pp::foundation::Result<BrushPackageImageTargetPaths>::failure(
            pp::foundation::Status::invalid_argument("brush package image extension contains unsupported characters"));
    }

    const auto directory = kind == BrushPackageImageKind::brush_tip ? "brushes" : "patterns";
    const std::string base_path = std::string(data_path) + "/" + directory + "/" + std::string(image_name)
        + "." + std::string(image_extension);

    return pp::foundation::Result<BrushPackageImageTargetPaths>::success(BrushPackageImageTargetPaths {
        .image_path = base_path,
        .thumbnail_path = std::string(data_path) + "/" + directory + "/thumbs/" + std::string(image_name)
            + "." + std::string(image_extension),
    });
}

} // namespace pp::assets
