#include "assets/ppi_header.h"

#include "assets/image_metadata.h"
#include "foundation/binary_stream.h"

#include <array>
#include <bit>
#include <cmath>
#include <limits>
#include <string>
#include <string_view>
#include <utility>

namespace pp::assets {

namespace {

[[nodiscard]] pp::foundation::Result<std::uint32_t> read_u32(pp::foundation::ByteReader& reader) noexcept
{
    return reader.read_u32_le();
}

[[nodiscard]] pp::foundation::Result<std::uint32_t> read_positive_i32(
    pp::foundation::ByteReader& reader,
    const char* message) noexcept
{
    const auto value = reader.read_u32_le();
    if (!value) {
        return value;
    }

    if (value.value() > static_cast<std::uint32_t>(std::numeric_limits<std::int32_t>::max())) {
        return pp::foundation::Result<std::uint32_t>::failure(
            pp::foundation::Status::out_of_range(message));
    }

    return value;
}

[[nodiscard]] pp::foundation::Result<float> read_f32(pp::foundation::ByteReader& reader) noexcept
{
    const auto bits = reader.read_u32_le();
    if (!bits) {
        return pp::foundation::Result<float>::failure(bits.status());
    }

    return pp::foundation::Result<float>::success(std::bit_cast<float>(bits.value()));
}

void append_u32(std::vector<std::byte>& bytes, std::uint32_t value)
{
    bytes.push_back(static_cast<std::byte>(value & 0xffU));
    bytes.push_back(static_cast<std::byte>((value >> 8U) & 0xffU));
    bytes.push_back(static_cast<std::byte>((value >> 16U) & 0xffU));
    bytes.push_back(static_cast<std::byte>((value >> 24U) & 0xffU));
}

void append_f32(std::vector<std::byte>& bytes, float value)
{
    append_u32(bytes, std::bit_cast<std::uint32_t>(value));
}

void append_ascii(std::vector<std::byte>& bytes, std::string_view value)
{
    for (const auto ch : value) {
        bytes.push_back(static_cast<std::byte>(ch));
    }
}

[[nodiscard]] pp::foundation::Status skip_bytes(
    pp::foundation::ByteReader& reader,
    std::size_t bytes) noexcept
{
    const auto skipped = reader.read_bytes(bytes);
    if (!skipped) {
        return skipped.status();
    }

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

[[nodiscard]] pp::foundation::Status validate_canvas_size(std::uint32_t width, std::uint32_t height) noexcept
{
    if (width == 0 || height == 0) {
        return pp::foundation::Status::invalid_argument("PPI canvas dimensions must be greater than zero");
    }

    if (width > max_ppi_canvas_dimension || height > max_ppi_canvas_dimension) {
        return pp::foundation::Status::out_of_range("PPI canvas dimensions exceed the configured limit");
    }

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

[[nodiscard]] std::string generated_layer_name(std::string_view base_name, std::uint32_t layer_index, std::uint32_t layer_count)
{
    if (layer_count == 1U) {
        return std::string(base_name);
    }

    std::string name(base_name);
    name.push_back(' ');
    name += std::to_string(layer_index + 1U);
    return name;
}

[[nodiscard]] pp::foundation::Status add_payload_bytes(PpiBodySummary& summary, std::uint32_t bytes) noexcept
{
    const auto next = summary.compressed_face_bytes + static_cast<std::uint64_t>(bytes);
    if (next > max_ppi_face_payload_bytes) {
        return pp::foundation::Status::out_of_range("PPI compressed face payload exceeds the configured limit");
    }

    summary.compressed_face_bytes = next;
    return pp::foundation::Status::success();
}

[[nodiscard]] pp::foundation::Result<ImageMetadata> validate_face_png_payload(
    std::span<const std::byte> payload,
    std::uint32_t width,
    std::uint32_t height) noexcept
{
    const auto metadata = parse_png_metadata(payload);
    if (!metadata) {
        return pp::foundation::Result<ImageMetadata>::failure(metadata.status());
    }

    if (metadata.value().width != width || metadata.value().height != height) {
        return pp::foundation::Result<ImageMetadata>::failure(
            pp::foundation::Status::invalid_argument("PPI face PNG dimensions do not match the dirty box"));
    }

    if (metadata.value().bit_depth != 8U || metadata.value().components != 4U
        || metadata.value().color_type != ImageColorType::rgba) {
        return pp::foundation::Result<ImageMetadata>::failure(
            pp::foundation::Status::invalid_argument("PPI face PNG payload must be 8-bit RGBA"));
    }

    return pp::foundation::Result<ImageMetadata>::success(metadata.value());
}

}

pp::foundation::Result<PpiHeaderInfo> parse_ppi_header(std::span<const std::byte> bytes) noexcept
{
    if (bytes.size() < ppi_header_size) {
        return pp::foundation::Result<PpiHeaderInfo>::failure(
            pp::foundation::Status::out_of_range("PPI header is truncated"));
    }

    pp::foundation::ByteReader reader(bytes.subspan(0, ppi_header_size));
    const auto magic = reader.read_bytes(4);
    if (!magic || magic.value()[0] != std::byte { 'P' } || magic.value()[1] != std::byte { 'P' }
        || magic.value()[2] != std::byte { 'I' } || magic.value()[3] != std::byte { 0 }) {
        return pp::foundation::Result<PpiHeaderInfo>::failure(
            pp::foundation::Status::invalid_argument("PPI header magic is invalid"));
    }

    PpiHeaderInfo info;
    const auto doc_major = read_u32(reader);
    const auto doc_minor = read_u32(reader);
    const auto soft_major = read_u32(reader);
    const auto soft_minor = read_u32(reader);
    const auto soft_fix = read_u32(reader);
    const auto soft_build = read_u32(reader);
    const auto thumb_width = read_u32(reader);
    const auto thumb_height = read_u32(reader);
    const auto thumb_components = read_u32(reader);
    if (!doc_major || !doc_minor || !soft_major || !soft_minor || !soft_fix || !soft_build
        || !thumb_width || !thumb_height || !thumb_components) {
        return pp::foundation::Result<PpiHeaderInfo>::failure(
            pp::foundation::Status::out_of_range("PPI header is truncated"));
    }

    info.document_version = { doc_major.value(), doc_minor.value() };
    info.software_version = {
        soft_major.value(),
        soft_minor.value(),
        soft_fix.value(),
        soft_build.value(),
    };
    info.thumbnail = {
        thumb_width.value(),
        thumb_height.value(),
        thumb_components.value(),
    };

    if (info.document_version.major != 0 || info.document_version.minor < 1) {
        return pp::foundation::Result<PpiHeaderInfo>::failure(
            pp::foundation::Status::invalid_argument("PPI document version is unsupported"));
    }

    if (info.thumbnail.width != 128 || info.thumbnail.height != 128 || info.thumbnail.components != 4) {
        return pp::foundation::Result<PpiHeaderInfo>::failure(
            pp::foundation::Status::invalid_argument("PPI thumbnail descriptor is invalid"));
    }

    return pp::foundation::Result<PpiHeaderInfo>::success(info);
}

pp::foundation::Result<std::size_t> ppi_thumbnail_byte_size(PpiThumbnailInfo thumbnail) noexcept
{
    if (thumbnail.width == 0 || thumbnail.height == 0 || thumbnail.components == 0) {
        return pp::foundation::Result<std::size_t>::failure(
            pp::foundation::Status::invalid_argument("PPI thumbnail descriptor is invalid"));
    }

    const auto width = static_cast<std::uint64_t>(thumbnail.width);
    const auto height = static_cast<std::uint64_t>(thumbnail.height);
    const auto components = static_cast<std::uint64_t>(thumbnail.components);
    if (width > std::numeric_limits<std::uint64_t>::max() / height) {
        return pp::foundation::Result<std::size_t>::failure(
            pp::foundation::Status::out_of_range("PPI thumbnail byte size overflows"));
    }

    const auto pixels = width * height;
    if (pixels > std::numeric_limits<std::uint64_t>::max() / components) {
        return pp::foundation::Result<std::size_t>::failure(
            pp::foundation::Status::out_of_range("PPI thumbnail byte size overflows"));
    }

    const auto bytes = pixels * components;
    if (bytes > static_cast<std::uint64_t>(std::numeric_limits<std::size_t>::max())) {
        return pp::foundation::Result<std::size_t>::failure(
            pp::foundation::Status::out_of_range("PPI thumbnail byte size exceeds addressable memory"));
    }

    return pp::foundation::Result<std::size_t>::success(static_cast<std::size_t>(bytes));
}

pp::foundation::Result<PpiProjectLayout> parse_ppi_project_layout(std::span<const std::byte> bytes) noexcept
{
    const auto header = parse_ppi_header(bytes);
    if (!header) {
        return pp::foundation::Result<PpiProjectLayout>::failure(header.status());
    }

    const auto thumbnail_bytes = ppi_thumbnail_byte_size(header.value().thumbnail);
    if (!thumbnail_bytes) {
        return pp::foundation::Result<PpiProjectLayout>::failure(thumbnail_bytes.status());
    }

    if (thumbnail_bytes.value() > std::numeric_limits<std::size_t>::max() - ppi_header_size) {
        return pp::foundation::Result<PpiProjectLayout>::failure(
            pp::foundation::Status::out_of_range("PPI thumbnail byte size overflows"));
    }

    const auto body_offset = ppi_header_size + thumbnail_bytes.value();
    if (bytes.size() < body_offset) {
        return pp::foundation::Result<PpiProjectLayout>::failure(
            pp::foundation::Status::out_of_range("PPI thumbnail payload is truncated"));
    }

    return pp::foundation::Result<PpiProjectLayout>::success(PpiProjectLayout {
        .header = header.value(),
        .thumbnail_offset = ppi_header_size,
        .thumbnail_bytes = thumbnail_bytes.value(),
        .body_offset = body_offset,
        .body_bytes = bytes.size() - body_offset,
    });
}

namespace {

pp::foundation::Result<PpiBodySummary> parse_ppi_body_impl(
    PpiHeaderInfo header,
    std::span<const std::byte> body,
    PpiBodyIndex* index) noexcept
{
    if (index != nullptr) {
        index->summary = {};
        index->layers.clear();
    }

    pp::foundation::ByteReader reader(body);
    const auto width = read_positive_i32(reader, "PPI canvas width is outside the supported range");
    const auto height = read_positive_i32(reader, "PPI canvas height is outside the supported range");
    const auto layer_count = read_positive_i32(reader, "PPI layer count is outside the supported range");
    if (!width || !height || !layer_count) {
        return pp::foundation::Result<PpiBodySummary>::failure(
            !width ? width.status() : (!height ? height.status() : layer_count.status()));
    }

    const auto canvas_status = validate_canvas_size(width.value(), height.value());
    if (!canvas_status.ok()) {
        return pp::foundation::Result<PpiBodySummary>::failure(canvas_status);
    }

    if (layer_count.value() == 0 || layer_count.value() > max_ppi_layer_count) {
        return pp::foundation::Result<PpiBodySummary>::failure(
            pp::foundation::Status::out_of_range("PPI layer count is outside the configured range"));
    }

    PpiBodySummary summary {
        .width = width.value(),
        .height = height.value(),
        .layer_count = layer_count.value(),
        .declared_frame_count = 1,
    };

    std::vector<bool> seen_orders;
    if (index != nullptr) {
        index->layers.resize(summary.layer_count);
        seen_orders.assign(summary.layer_count, false);
    }

    if (header.document_version.minor >= 3U) {
        const auto declared_frames = read_positive_i32(reader, "PPI declared frame count is outside the supported range");
        if (!declared_frames) {
            return pp::foundation::Result<PpiBodySummary>::failure(declared_frames.status());
        }

        if (declared_frames.value() == 0 || declared_frames.value() > max_ppi_frame_count) {
            return pp::foundation::Result<PpiBodySummary>::failure(
                pp::foundation::Status::out_of_range("PPI declared frame count is outside the configured range"));
        }
        summary.declared_frame_count = declared_frames.value();
    }

    for (std::uint32_t layer_index = 0; layer_index < summary.layer_count; ++layer_index) {
        const auto order = read_positive_i32(reader, "PPI layer order is outside the supported range");
        const auto opacity = read_f32(reader);
        const auto name_length = read_positive_i32(reader, "PPI layer name length is outside the supported range");
        if (!order || !opacity || !name_length) {
            return pp::foundation::Result<PpiBodySummary>::failure(
                !order ? order.status() : (!opacity ? opacity.status() : name_length.status()));
        }

        if (order.value() >= summary.layer_count) {
            return pp::foundation::Result<PpiBodySummary>::failure(
                pp::foundation::Status::out_of_range("PPI layer order is outside the layer list"));
        }

        if (index != nullptr) {
            if (seen_orders[order.value()]) {
                return pp::foundation::Result<PpiBodySummary>::failure(
                    pp::foundation::Status::invalid_argument("PPI layer order is duplicated"));
            }
            seen_orders[order.value()] = true;
        }

        if (!std::isfinite(opacity.value())) {
            return pp::foundation::Result<PpiBodySummary>::failure(
                pp::foundation::Status::invalid_argument("PPI layer opacity must be finite"));
        }

        if (opacity.value() < 0.0F || opacity.value() > 1.0F) {
            return pp::foundation::Result<PpiBodySummary>::failure(
                pp::foundation::Status::out_of_range("PPI layer opacity is outside the supported range"));
        }

        if (name_length.value() > max_ppi_layer_name_length) {
            return pp::foundation::Result<PpiBodySummary>::failure(
                pp::foundation::Status::out_of_range("PPI layer name exceeds the configured limit"));
        }

        const auto name_bytes = reader.read_bytes(name_length.value());
        if (!name_bytes) {
            return pp::foundation::Result<PpiBodySummary>::failure(name_bytes.status());
        }

        PpiLayerSummary layer_summary;
        if (index != nullptr) {
            layer_summary.stored_order = order.value();
            layer_summary.opacity = opacity.value();
            layer_summary.name.reserve(name_bytes.value().size());
            for (const auto byte : name_bytes.value()) {
                layer_summary.name.push_back(static_cast<char>(std::to_integer<unsigned char>(byte)));
            }
        }

        if (header.document_version.minor >= 2U) {
            const auto blend_mode = read_positive_i32(reader, "PPI layer blend mode is outside the supported range");
            const auto alpha_locked = reader.read_u8();
            const auto visible = reader.read_u8();
            if (!blend_mode || !alpha_locked || !visible) {
                return pp::foundation::Result<PpiBodySummary>::failure(
                    !blend_mode ? blend_mode.status() : (!alpha_locked ? alpha_locked.status() : visible.status()));
            }

            if (alpha_locked.value() > 1U || visible.value() > 1U) {
                return pp::foundation::Result<PpiBodySummary>::failure(
                    pp::foundation::Status::invalid_argument("PPI layer boolean field is invalid"));
            }

            if (blend_mode.value() > 4U) {
                return pp::foundation::Result<PpiBodySummary>::failure(
                    pp::foundation::Status::out_of_range("PPI layer blend mode is outside the supported range"));
            }

            if (index != nullptr) {
                layer_summary.blend_mode = blend_mode.value();
                layer_summary.alpha_locked = alpha_locked.value() != 0U;
                layer_summary.visible = visible.value() != 0U;
            }
        }

        std::uint32_t layer_frames = 1;
        if (header.document_version.minor >= 3U) {
            const auto frame_count = read_positive_i32(reader, "PPI layer frame count is outside the supported range");
            if (!frame_count) {
                return pp::foundation::Result<PpiBodySummary>::failure(frame_count.status());
            }

            if (frame_count.value() == 0 || frame_count.value() > max_ppi_frame_count) {
                return pp::foundation::Result<PpiBodySummary>::failure(
                    pp::foundation::Status::out_of_range("PPI layer frame count is outside the configured range"));
            }
            layer_frames = frame_count.value();
        }

        if (summary.total_layer_frames > max_ppi_frame_count - layer_frames) {
            return pp::foundation::Result<PpiBodySummary>::failure(
                pp::foundation::Status::out_of_range("PPI total frame count exceeds the configured limit"));
        }
        summary.total_layer_frames += layer_frames;

        if (index != nullptr) {
            layer_summary.frames.resize(layer_frames);
        }

        for (std::uint32_t frame_index = 0; frame_index < layer_frames; ++frame_index) {
            if (header.document_version.minor >= 3U) {
                const auto duration = read_positive_i32(reader, "PPI frame duration is outside the supported range");
                if (!duration) {
                    return pp::foundation::Result<PpiBodySummary>::failure(duration.status());
                }

                if (duration.value() == 0) {
                    return pp::foundation::Result<PpiBodySummary>::failure(
                        pp::foundation::Status::invalid_argument("PPI frame duration must be greater than zero"));
                }

                if (index != nullptr) {
                    layer_summary.frames[frame_index].duration_ms = duration.value();
                }
            }

            for (std::uint32_t face = 0; face < 6U; ++face) {
                const auto has_data = read_positive_i32(reader, "PPI face data flag is outside the supported range");
                if (!has_data) {
                    return pp::foundation::Result<PpiBodySummary>::failure(has_data.status());
                }

                if (has_data.value() > 1U) {
                    return pp::foundation::Result<PpiBodySummary>::failure(
                        pp::foundation::Status::invalid_argument("PPI face data flag is invalid"));
                }

                if (has_data.value() == 0U) {
                    continue;
                }

                ++summary.dirty_face_count;
                const auto x0 = read_positive_i32(reader, "PPI dirty box coordinate is outside the supported range");
                const auto y0 = read_positive_i32(reader, "PPI dirty box coordinate is outside the supported range");
                const auto x1 = read_positive_i32(reader, "PPI dirty box coordinate is outside the supported range");
                const auto y1 = read_positive_i32(reader, "PPI dirty box coordinate is outside the supported range");
                const auto data_size = read_positive_i32(reader, "PPI compressed face data size is outside the supported range");
                if (!x0 || !y0 || !x1 || !y1 || !data_size) {
                    return pp::foundation::Result<PpiBodySummary>::failure(
                        !x0 ? x0.status()
                            : (!y0 ? y0.status() : (!x1 ? x1.status() : (!y1 ? y1.status() : data_size.status()))));
                }

                if (x0.value() >= x1.value() || y0.value() >= y1.value() || x1.value() > summary.width
                    || y1.value() > summary.height) {
                    return pp::foundation::Result<PpiBodySummary>::failure(
                        pp::foundation::Status::out_of_range("PPI dirty box is outside the canvas"));
                }

                if (data_size.value() == 0U) {
                    return pp::foundation::Result<PpiBodySummary>::failure(
                        pp::foundation::Status::invalid_argument("PPI compressed face payload must not be empty"));
                }

                const auto byte_status = add_payload_bytes(summary, data_size.value());
                if (!byte_status.ok()) {
                    return pp::foundation::Result<PpiBodySummary>::failure(byte_status);
                }

                const auto payload_offset = reader.position();
                const auto payload = reader.read_bytes(data_size.value());
                if (!payload) {
                    return pp::foundation::Result<PpiBodySummary>::failure(payload.status());
                }

                const auto png_metadata = validate_face_png_payload(
                    payload.value(),
                    x1.value() - x0.value(),
                    y1.value() - y0.value());
                if (!png_metadata) {
                    return pp::foundation::Result<PpiBodySummary>::failure(png_metadata.status());
                }

                ++summary.rgba_face_payload_count;
                if (index != nullptr) {
                    layer_summary.frames[frame_index].faces[face] = PpiFacePayloadSummary {
                        .has_data = true,
                        .x0 = x0.value(),
                        .y0 = y0.value(),
                        .x1 = x1.value(),
                        .y1 = y1.value(),
                        .body_payload_offset = static_cast<std::uint32_t>(payload_offset),
                        .payload_bytes = data_size.value(),
                        .png_width = png_metadata.value().width,
                        .png_height = png_metadata.value().height,
                    };
                }
            }
        }

        if (index != nullptr) {
            index->layers[order.value()] = std::move(layer_summary);
        }
    }

    if (header.document_version.minor >= 3U && summary.total_layer_frames != summary.declared_frame_count) {
        return pp::foundation::Result<PpiBodySummary>::failure(
            pp::foundation::Status::invalid_argument("PPI declared frame count does not match layer frames"));
    }

    if (header.document_version.minor >= 4U) {
        const auto info_bytes = read_positive_i32(reader, "PPI info block size is outside the supported range");
        if (!info_bytes) {
            return pp::foundation::Result<PpiBodySummary>::failure(info_bytes.status());
        }

        summary.info_bytes = info_bytes.value();
        const auto info_status = skip_bytes(reader, summary.info_bytes);
        if (!info_status.ok()) {
            return pp::foundation::Result<PpiBodySummary>::failure(info_status);
        }
    }

    if (!reader.empty()) {
        return pp::foundation::Result<PpiBodySummary>::failure(
            pp::foundation::Status::invalid_argument("PPI body has trailing bytes"));
    }

    if (index != nullptr) {
        index->summary = summary;
    }

    return pp::foundation::Result<PpiBodySummary>::success(summary);
}

}

pp::foundation::Result<PpiBodySummary> parse_ppi_body_summary(
    PpiHeaderInfo header,
    std::span<const std::byte> body) noexcept
{
    return parse_ppi_body_impl(header, body, nullptr);
}

pp::foundation::Result<PpiBodyIndex> parse_ppi_body_index(
    PpiHeaderInfo header,
    std::span<const std::byte> body)
{
    PpiBodyIndex index;
    const auto summary = parse_ppi_body_impl(header, body, &index);
    if (!summary) {
        return pp::foundation::Result<PpiBodyIndex>::failure(summary.status());
    }

    return pp::foundation::Result<PpiBodyIndex>::success(std::move(index));
}

pp::foundation::Result<PpiProjectSummary> parse_ppi_project_summary(std::span<const std::byte> bytes) noexcept
{
    const auto layout = parse_ppi_project_layout(bytes);
    if (!layout) {
        return pp::foundation::Result<PpiProjectSummary>::failure(layout.status());
    }

    const auto body = parse_ppi_body_summary(
        layout.value().header,
        bytes.subspan(layout.value().body_offset, layout.value().body_bytes));
    if (!body) {
        return pp::foundation::Result<PpiProjectSummary>::failure(body.status());
    }

    return pp::foundation::Result<PpiProjectSummary>::success(PpiProjectSummary {
        .layout = layout.value(),
        .body = body.value(),
    });
}

pp::foundation::Result<PpiProjectIndex> parse_ppi_project_index(std::span<const std::byte> bytes)
{
    const auto layout = parse_ppi_project_layout(bytes);
    if (!layout) {
        return pp::foundation::Result<PpiProjectIndex>::failure(layout.status());
    }

    const auto body = parse_ppi_body_index(
        layout.value().header,
        bytes.subspan(layout.value().body_offset, layout.value().body_bytes));
    if (!body) {
        return pp::foundation::Result<PpiProjectIndex>::failure(body.status());
    }

    return pp::foundation::Result<PpiProjectIndex>::success(PpiProjectIndex {
        .layout = layout.value(),
        .body = body.value(),
    });
}

pp::foundation::Result<PpiDecodedProjectImages> decode_ppi_project_images(std::span<const std::byte> bytes)
{
    auto project = parse_ppi_project_index(bytes);
    if (!project) {
        return pp::foundation::Result<PpiDecodedProjectImages>::failure(project.status());
    }

    PpiDecodedProjectImages decoded {
        .project = project.value(),
        .faces = {},
    };
    decoded.faces.reserve(decoded.project.body.summary.rgba_face_payload_count);

    const auto body = bytes.subspan(decoded.project.layout.body_offset, decoded.project.layout.body_bytes);
    for (std::size_t layer_index = 0; layer_index < decoded.project.body.layers.size(); ++layer_index) {
        const auto& layer = decoded.project.body.layers[layer_index];
        for (std::size_t frame_index = 0; frame_index < layer.frames.size(); ++frame_index) {
            const auto& frame = layer.frames[frame_index];
            for (std::size_t face_index = 0; face_index < frame.faces.size(); ++face_index) {
                const auto& face = frame.faces[face_index];
                if (!face.has_data) {
                    continue;
                }

                if (face.body_payload_offset > body.size()
                    || face.payload_bytes > body.size() - face.body_payload_offset) {
                    return pp::foundation::Result<PpiDecodedProjectImages>::failure(
                        pp::foundation::Status::out_of_range("PPI face payload range is outside the body"));
                }

                const auto image = decode_png_rgba8(
                    body.subspan(face.body_payload_offset, face.payload_bytes));
                if (!image) {
                    return pp::foundation::Result<PpiDecodedProjectImages>::failure(image.status());
                }

                if (image.value().width != face.png_width || image.value().height != face.png_height) {
                    return pp::foundation::Result<PpiDecodedProjectImages>::failure(
                        pp::foundation::Status::invalid_argument("decoded PPI face payload dimensions changed"));
                }

                decoded.faces.push_back(PpiDecodedFacePayload {
                    .layer_index = static_cast<std::uint32_t>(layer_index),
                    .frame_index = static_cast<std::uint32_t>(frame_index),
                    .face_index = static_cast<std::uint32_t>(face_index),
                    .descriptor = face,
                    .image = image.value(),
                });
            }
        }
    }

    return pp::foundation::Result<PpiDecodedProjectImages>::success(std::move(decoded));
}

pp::foundation::Result<std::vector<std::byte>> create_ppi_project(PpiProjectConfig config)
{
    const auto canvas_status = validate_canvas_size(config.width, config.height);
    if (!canvas_status.ok()) {
        return pp::foundation::Result<std::vector<std::byte>>::failure(canvas_status);
    }

    if (config.layers.empty() || config.layers.size() > max_ppi_layer_count) {
        return pp::foundation::Result<std::vector<std::byte>>::failure(
            pp::foundation::Status::out_of_range("PPI layer count is outside the configured range"));
    }

    std::uint32_t total_frame_count = 0;
    std::vector<std::size_t> layer_frame_offsets;
    layer_frame_offsets.reserve(config.layers.size());
    for (const auto& layer : config.layers) {
        if (layer.name.empty()) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::invalid_argument("PPI layer name must not be empty"));
        }

        if (layer.name.size() > max_ppi_layer_name_length) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::out_of_range("PPI layer name exceeds the configured limit"));
        }

        if (!std::isfinite(layer.metadata.opacity)) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::invalid_argument("PPI layer opacity must be finite"));
        }

        if (layer.metadata.opacity < 0.0F || layer.metadata.opacity > 1.0F) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::out_of_range("PPI layer opacity is outside the supported range"));
        }

        if (layer.metadata.blend_mode > 4U) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::out_of_range("PPI layer blend mode is outside the supported range"));
        }

        if (layer.frames.empty() || layer.frames.size() > max_ppi_frame_count) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::out_of_range("PPI layer frame count is outside the configured range"));
        }

        if (layer.frames.size() > max_ppi_frame_count - total_frame_count) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::out_of_range("PPI total layer frame count exceeds the configured range"));
        }

        layer_frame_offsets.push_back(total_frame_count);
        total_frame_count += static_cast<std::uint32_t>(layer.frames.size());

        for (const auto& frame : layer.frames) {
            if (frame.duration_ms == 0) {
                return pp::foundation::Result<std::vector<std::byte>>::failure(
                    pp::foundation::Status::invalid_argument("PPI frame duration must be greater than zero"));
            }
        }
    }

    std::vector<std::array<bool, 6>> seen_faces(total_frame_count);
    std::uint64_t total_payload_bytes = 0;
    for (const auto& face : config.dirty_faces) {
        if (face.layer_index >= config.layers.size()) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::out_of_range("PPI dirty face layer index is outside the layer list"));
        }

        if (face.frame_index >= config.layers[face.layer_index].frames.size()) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::out_of_range("PPI dirty face frame index is outside the frame list"));
        }

        if (face.face_index >= 6U) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::out_of_range("PPI dirty face index is outside the cube face list"));
        }

        const auto slot_index = layer_frame_offsets[face.layer_index] + static_cast<std::size_t>(face.frame_index);
        if (seen_faces[slot_index][face.face_index]) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::invalid_argument("PPI dirty face slot is duplicated"));
        }
        seen_faces[slot_index][face.face_index] = true;

        if (face.width == 0 || face.height == 0) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::invalid_argument("PPI dirty face dimensions must be greater than zero"));
        }

        if (face.x > config.width || face.width > config.width - face.x
            || face.y > config.height || face.height > config.height - face.y) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::out_of_range("PPI dirty face box is outside the canvas"));
        }

        if (face.png_rgba8.empty()) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::invalid_argument("PPI dirty face PNG payload must not be empty"));
        }

        if (face.png_rgba8.size() > static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max())) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::out_of_range("PPI dirty face PNG payload is too large"));
        }

        const auto next_payload_bytes = total_payload_bytes + face.png_rgba8.size();
        if (next_payload_bytes > max_ppi_face_payload_bytes) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(
                pp::foundation::Status::out_of_range("PPI dirty face PNG payloads exceed the configured limit"));
        }
        total_payload_bytes = next_payload_bytes;

        const auto metadata = validate_face_png_payload(face.png_rgba8, face.width, face.height);
        if (!metadata) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(metadata.status());
        }
    }

    std::vector<std::byte> bytes {
        std::byte { 'P' },
        std::byte { 'P' },
        std::byte { 'I' },
        std::byte { 0 },
    };
    append_u32(bytes, 0);
    append_u32(bytes, 4);
    append_u32(bytes, 0);
    append_u32(bytes, 0);
    append_u32(bytes, 0);
    append_u32(bytes, 0);
    append_u32(bytes, 128);
    append_u32(bytes, 128);
    append_u32(bytes, 4);

    constexpr std::size_t thumbnail_bytes = 128U * 128U * 4U;
    bytes.resize(ppi_header_size + thumbnail_bytes, std::byte { 0 });

    append_u32(bytes, config.width);
    append_u32(bytes, config.height);
    append_u32(bytes, static_cast<std::uint32_t>(config.layers.size()));
    append_u32(bytes, total_frame_count);
    for (std::uint32_t layer = 0; layer < config.layers.size(); ++layer) {
        const auto& layer_config = config.layers[layer];
        append_u32(bytes, layer);
        append_f32(bytes, layer_config.metadata.opacity);
        append_u32(bytes, static_cast<std::uint32_t>(layer_config.name.size()));
        append_ascii(bytes, layer_config.name);
        append_u32(bytes, layer_config.metadata.blend_mode);
        bytes.push_back(layer_config.metadata.alpha_locked ? std::byte { 1 } : std::byte { 0 });
        bytes.push_back(layer_config.metadata.visible ? std::byte { 1 } : std::byte { 0 });
        append_u32(bytes, static_cast<std::uint32_t>(layer_config.frames.size()));
        for (std::uint32_t frame = 0; frame < layer_config.frames.size(); ++frame) {
            append_u32(bytes, layer_config.frames[frame].duration_ms);
            for (std::uint32_t face = 0; face < 6U; ++face) {
                const PpiDirtyFacePayloadConfig* dirty_face = nullptr;
                for (const auto& candidate : config.dirty_faces) {
                    if (candidate.layer_index == layer && candidate.frame_index == frame
                        && candidate.face_index == face) {
                        dirty_face = &candidate;
                        break;
                    }
                }

                if (dirty_face == nullptr) {
                    append_u32(bytes, 0);
                    continue;
                }

                append_u32(bytes, 1);
                append_u32(bytes, dirty_face->x);
                append_u32(bytes, dirty_face->y);
                append_u32(bytes, dirty_face->x + dirty_face->width);
                append_u32(bytes, dirty_face->y + dirty_face->height);
                append_u32(bytes, static_cast<std::uint32_t>(dirty_face->png_rgba8.size()));
                bytes.insert(bytes.end(), dirty_face->png_rgba8.begin(), dirty_face->png_rgba8.end());
            }
        }
    }
    append_u32(bytes, 0);

    return pp::foundation::Result<std::vector<std::byte>>::success(std::move(bytes));
}

pp::foundation::Result<std::vector<std::byte>> create_minimal_ppi_project(PpiMinimalProjectConfig config)
{
    if (config.layer_count == 0 || config.layer_count > max_ppi_layer_count) {
        return pp::foundation::Result<std::vector<std::byte>>::failure(
            pp::foundation::Status::out_of_range("PPI layer count is outside the configured range"));
    }

    if (config.frame_count == 0 || config.frame_count > max_ppi_frame_count) {
        return pp::foundation::Result<std::vector<std::byte>>::failure(
            pp::foundation::Status::out_of_range("PPI frame count is outside the configured range"));
    }

    std::vector<std::string> names;
    names.reserve(config.layer_count);
    std::vector<std::vector<PpiFrameConfig>> frame_lists;
    frame_lists.reserve(config.layer_count);
    std::vector<PpiLayerConfig> layers;
    layers.reserve(config.layer_count);
    for (std::uint32_t layer = 0; layer < config.layer_count; ++layer) {
        names.push_back(generated_layer_name(config.layer_name, layer, config.layer_count));
        auto& frames = frame_lists.emplace_back();
        frames.assign(config.frame_count, PpiFrameConfig { .duration_ms = config.frame_duration_ms });
        layers.push_back(PpiLayerConfig {
            .name = names.back(),
            .metadata = config.layer_metadata,
            .frames = std::span<const PpiFrameConfig>(frames.data(), frames.size()),
        });
    }

    return create_ppi_project(PpiProjectConfig {
        .width = config.width,
        .height = config.height,
        .layers = std::span<const PpiLayerConfig>(layers.data(), layers.size()),
        .dirty_faces = config.dirty_faces,
    });
}

}
