#pragma once

#include "app_core/app_dialog.h"
#include "foundation/result.h"

#include <cstddef>
#include <limits>
#include <string_view>

namespace pp::app {

enum class RecordingStartAction {
    start_thread,
    no_op_already_running,
};

enum class RecordingStopAction {
    stop_thread,
    no_op_not_running,
};

struct RecordingClearPlan {
    bool stop_running_recording = false;
    bool delete_recorded_files = false;
    int frame_count_after_clear = 0;
};

struct RecordingExportPlan {
    std::size_t frame_count = 0;
    int progress_total = 0;
};

struct RecordingWorkerIterationPlan {
    bool continue_running = true;
    bool encode_frame = false;
    bool clear_dirty_stroke = false;
    bool update_frame_label = false;
};

class RecordingServices {
public:
    virtual ~RecordingServices() = default;

    virtual void start_thread() = 0;
    virtual void stop_thread() = 0;
    virtual void delete_recorded_files() = 0;
    virtual void set_frame_count(int frame_count) = 0;
    virtual void update_frame_label() = 0;
    virtual void begin_export(int progress_total) = 0;
    virtual void write_mp4(std::string_view path) = 0;
    virtual void end_export() = 0;
};

[[nodiscard]] constexpr RecordingStartAction plan_recording_start(bool is_running) noexcept
{
    return is_running
        ? RecordingStartAction::no_op_already_running
        : RecordingStartAction::start_thread;
}

[[nodiscard]] constexpr RecordingStopAction plan_recording_stop(bool is_running) noexcept
{
    return is_running
        ? RecordingStopAction::stop_thread
        : RecordingStopAction::no_op_not_running;
}

[[nodiscard]] constexpr RecordingClearPlan plan_recording_clear(
    bool is_running,
    bool platform_deletes_recorded_files) noexcept
{
    return {
        is_running,
        platform_deletes_recorded_files,
        0,
    };
}

[[nodiscard]] constexpr RecordingExportPlan plan_recording_export(std::size_t frame_count) noexcept
{
    const auto max_progress_total = static_cast<std::size_t>(std::numeric_limits<int>::max());
    return {
        frame_count,
        frame_count > max_progress_total ? std::numeric_limits<int>::max() : static_cast<int>(frame_count),
    };
}

[[nodiscard]] inline AppProgressDialogPlan plan_recording_export_progress_dialog(
    const RecordingExportPlan& plan)
{
    return plan_app_progress_dialog("Exporting MP4 movie", plan.progress_total);
}

[[nodiscard]] constexpr RecordingWorkerIterationPlan plan_recording_worker_iteration(
    bool is_running_after_wake,
    bool has_encoder,
    bool has_canvas_document) noexcept
{
    const bool encode = is_running_after_wake && has_encoder && has_canvas_document;
    return {
        .continue_running = is_running_after_wake,
        .encode_frame = encode,
        .clear_dirty_stroke = encode,
        .update_frame_label = encode,
    };
}

[[nodiscard]] inline pp::foundation::Status execute_recording_start_action(
    RecordingStartAction action,
    RecordingServices& services)
{
    switch (action) {
    case RecordingStartAction::start_thread:
        services.start_thread();
        return pp::foundation::Status::success();
    case RecordingStartAction::no_op_already_running:
        return pp::foundation::Status::success();
    }

    return pp::foundation::Status::invalid_argument("unknown recording start action");
}

[[nodiscard]] inline pp::foundation::Status execute_recording_stop_action(
    RecordingStopAction action,
    RecordingServices& services)
{
    switch (action) {
    case RecordingStopAction::stop_thread:
        services.stop_thread();
        return pp::foundation::Status::success();
    case RecordingStopAction::no_op_not_running:
        return pp::foundation::Status::success();
    }

    return pp::foundation::Status::invalid_argument("unknown recording stop action");
}

[[nodiscard]] inline pp::foundation::Status execute_recording_clear_plan(
    const RecordingClearPlan& plan,
    RecordingServices& services)
{
    if (plan.stop_running_recording) {
        services.stop_thread();
    }
    if (plan.delete_recorded_files) {
        services.delete_recorded_files();
    }
    services.set_frame_count(plan.frame_count_after_clear);
    services.update_frame_label();
    return pp::foundation::Status::success();
}

[[nodiscard]] inline pp::foundation::Status execute_recording_export_plan(
    const RecordingExportPlan& plan,
    RecordingServices& services,
    std::string_view path)
{
    services.begin_export(plan.progress_total);
    services.write_mp4(path);
    services.end_export();
    return pp::foundation::Status::success();
}

}
