#pragma once

#include "foundation/result.h"
#include "paint/blend.h"

#include <cstdint>
#include <span>
#include <string>
#include <string_view>
#include <vector>

namespace pp::document {

constexpr std::uint32_t max_canvas_dimension = 131072;
constexpr std::uint32_t max_layer_count = 1024;
constexpr std::uint32_t max_frame_count = 100000;
constexpr std::uint32_t min_frame_duration_ms = 1;
constexpr std::size_t min_document_history_entries = 2;
constexpr std::size_t max_document_history_entries = 10000;
constexpr std::size_t max_layer_name_length = 128;
constexpr std::uint32_t cube_face_count = 6;
constexpr std::uint32_t rgba8_components = 4;
constexpr std::uint32_t alpha8_components = 1;
constexpr std::uint64_t max_face_pixel_payload_bytes = 1024ULL * 1024ULL * 1024ULL;

struct DocumentConfig {
    std::uint32_t width = 0;
    std::uint32_t height = 0;
    std::uint32_t layer_count = 1;
};

struct LayerFacePixels {
    std::uint32_t face_index = 0;
    std::uint32_t x = 0;
    std::uint32_t y = 0;
    std::uint32_t width = 0;
    std::uint32_t height = 0;
    std::vector<std::uint8_t> rgba8;
};

struct SelectionMask {
    std::uint32_t face_index = 0;
    std::uint32_t x = 0;
    std::uint32_t y = 0;
    std::uint32_t width = 0;
    std::uint32_t height = 0;
    std::vector<std::uint8_t> alpha8;
};

struct AnimationFrame {
    std::uint32_t duration_ms = 100;
    std::vector<LayerFacePixels> face_pixels;
};

struct Layer {
    std::string name;
    bool visible = true;
    bool alpha_locked = false;
    float opacity = 1.0F;
    pp::paint::BlendMode blend_mode = pp::paint::BlendMode::normal;
    std::vector<AnimationFrame> frames;
};

struct DocumentLayerConfig {
    std::string_view name;
    bool visible = true;
    bool alpha_locked = false;
    float opacity = 1.0F;
    pp::paint::BlendMode blend_mode = pp::paint::BlendMode::normal;
    std::span<const AnimationFrame> frames;
};

struct DocumentSnapshotConfig {
    std::uint32_t width = 0;
    std::uint32_t height = 0;
    std::span<const DocumentLayerConfig> layers;
    std::span<const AnimationFrame> frames;
    std::span<const SelectionMask> selection_masks;
};

class CanvasDocument {
public:
    [[nodiscard]] static pp::foundation::Result<CanvasDocument> create(DocumentConfig config);
    [[nodiscard]] static pp::foundation::Result<CanvasDocument> create_from_snapshot(DocumentSnapshotConfig config);

    [[nodiscard]] std::uint32_t width() const noexcept;
    [[nodiscard]] std::uint32_t height() const noexcept;
    [[nodiscard]] std::size_t active_layer_index() const noexcept;
    [[nodiscard]] std::size_t active_frame_index() const noexcept;
    [[nodiscard]] std::uint64_t animation_duration_ms() const noexcept;
    [[nodiscard]] pp::foundation::Result<std::uint64_t> layer_animation_duration_ms(std::size_t index) const noexcept;
    [[nodiscard]] std::size_t face_pixel_payload_count() const noexcept;
    [[nodiscard]] std::size_t selection_mask_payload_count() const noexcept;
    [[nodiscard]] std::span<const Layer> layers() const noexcept;
    [[nodiscard]] std::span<const AnimationFrame> frames() const noexcept;
    [[nodiscard]] std::span<const SelectionMask> selection_masks() const noexcept;

    [[nodiscard]] pp::foundation::Result<std::size_t> add_layer(std::string_view name);
    [[nodiscard]] pp::foundation::Status remove_layer(std::size_t index);
    [[nodiscard]] pp::foundation::Status move_layer(std::size_t from, std::size_t to);
    [[nodiscard]] pp::foundation::Status set_active_layer(std::size_t index) noexcept;
    [[nodiscard]] pp::foundation::Status rename_layer(std::size_t index, std::string_view name);
    [[nodiscard]] pp::foundation::Status set_layer_visible(std::size_t index, bool visible) noexcept;
    [[nodiscard]] pp::foundation::Status set_layer_alpha_locked(std::size_t index, bool alpha_locked) noexcept;
    [[nodiscard]] pp::foundation::Status set_layer_opacity(std::size_t index, float opacity) noexcept;
    [[nodiscard]] pp::foundation::Status set_layer_blend_mode(std::size_t index, pp::paint::BlendMode blend_mode) noexcept;

    [[nodiscard]] pp::foundation::Result<std::size_t> add_frame(std::uint32_t duration_ms);
    [[nodiscard]] pp::foundation::Result<std::size_t> duplicate_frame(std::size_t index);
    [[nodiscard]] pp::foundation::Status remove_frame(std::size_t index);
    [[nodiscard]] pp::foundation::Status move_frame(std::size_t from, std::size_t to);
    [[nodiscard]] pp::foundation::Status set_frame_duration(std::size_t index, std::uint32_t duration_ms) noexcept;
    [[nodiscard]] pp::foundation::Status set_active_frame(std::size_t index) noexcept;
    [[nodiscard]] pp::foundation::Status set_layer_frame_face_pixels(
        std::size_t layer_index,
        std::size_t frame_index,
        LayerFacePixels pixels);
    [[nodiscard]] pp::foundation::Status set_selection_mask(SelectionMask mask);
    [[nodiscard]] pp::foundation::Status clear_selection_mask(std::uint32_t face_index) noexcept;

private:
    std::uint32_t width_ = 0;
    std::uint32_t height_ = 0;
    std::size_t active_layer_index_ = 0;
    std::size_t active_frame_index_ = 0;
    std::vector<Layer> layers_;
    std::vector<AnimationFrame> frames_;
    std::vector<SelectionMask> selection_masks_;
};

class DocumentHistory {
public:
    [[nodiscard]] static pp::foundation::Result<DocumentHistory> create(
        CanvasDocument initial_document,
        std::size_t max_entries = 256);

    [[nodiscard]] const CanvasDocument& current() const noexcept;
    [[nodiscard]] std::size_t size() const noexcept;
    [[nodiscard]] std::size_t current_index() const noexcept;
    [[nodiscard]] bool can_undo() const noexcept;
    [[nodiscard]] bool can_redo() const noexcept;

    [[nodiscard]] pp::foundation::Status apply(CanvasDocument next_document);
    [[nodiscard]] pp::foundation::Status undo() noexcept;
    [[nodiscard]] pp::foundation::Status redo() noexcept;

private:
    std::size_t max_entries_ = 0;
    std::size_t current_index_ = 0;
    std::vector<CanvasDocument> entries_;
};

}
