#pragma once

#include "foundation/result.h"

#include <cmath>
#include <cstddef>

namespace pp::app {

struct AppTaskDispatchPlan {
    bool execute_immediately = false;
    bool queue_task = false;
    bool remove_matching_unique_task = false;
    bool notify_worker = false;
    bool wait_for_completion = false;
    bool request_redraw = false;
    // When true, dispatch attempts from non-target threads are rejected instead
    // of being queued for later execution.
    bool reject_unsafe_cross_thread_dispatch = false;
};

struct AppAsyncRedrawPlan {
    bool set_redraw = true;
    bool notify_ui = true;
};

struct AppQueueDrainPlan {
    bool mark_running = true;
    bool drain_tasks = false;
    bool wrap_in_render_context = false;
    std::size_t task_count = 0;
};

struct AppUiTickPlan {
    bool mark_running = true;
    bool execute_tasks = false;
    bool tick_app = true;
    bool update_before_render = false;
    bool enqueue_render_frame = false;
    std::size_t task_count = 0;
};

struct AppUiLoopTimerPlan {
    bool update_platform_frame = true;
    float frame_accumulator = 0.0F;
    float fps_accumulator = 0.0F;
    float reload_accumulator = 0.0F;
    bool report_rendered_frames = false;
    int reported_frame_count = 0;
    int rendered_frames_after_report = 0;
    bool check_live_asset_reload = false;
};

struct AppUiLoopRedrawPlan {
    bool tick_app = true;
    bool update_before_render = false;
    bool enqueue_render_frame = false;
    bool reset_frame_accumulator = false;
    int rendered_frames = 0;
};

struct AppThreadStartPlan {
    bool start_thread = true;
    bool mark_running = true;
};

struct AppThreadStopPlan {
    bool mark_not_running = true;
    bool notify_worker = true;
    bool join_thread = false;
};

[[nodiscard]] constexpr AppTaskDispatchPlan plan_app_task_dispatch(
    bool already_on_target_thread,
    bool unique,
    std::size_t queued_task_count,
    bool worker_running,
    bool wait_for_completion,
    bool request_redraw_after_dispatch,
    bool reject_unsafe_cross_thread_dispatch = false) noexcept
{
    const bool queue_task = !already_on_target_thread && !reject_unsafe_cross_thread_dispatch;
    return AppTaskDispatchPlan {
        .execute_immediately = already_on_target_thread,
        .queue_task = queue_task,
        .remove_matching_unique_task = queue_task && unique && queued_task_count > 0U,
        .notify_worker = queue_task,
        .wait_for_completion = queue_task && worker_running && wait_for_completion,
        .request_redraw = !(!already_on_target_thread && reject_unsafe_cross_thread_dispatch)
            && request_redraw_after_dispatch,
        .reject_unsafe_cross_thread_dispatch = !already_on_target_thread
            && reject_unsafe_cross_thread_dispatch,
    };
}

[[nodiscard]] constexpr AppAsyncRedrawPlan plan_app_async_redraw() noexcept
{
    return AppAsyncRedrawPlan {};
}

[[nodiscard]] constexpr AppQueueDrainPlan plan_app_render_queue_drain(std::size_t queued_task_count) noexcept
{
    const bool drain = queued_task_count > 0U;
    return AppQueueDrainPlan {
        .mark_running = true,
        .drain_tasks = drain,
        .wrap_in_render_context = drain,
        .task_count = queued_task_count,
    };
}

[[nodiscard]] constexpr AppQueueDrainPlan plan_app_ui_queue_drain(std::size_t queued_task_count) noexcept
{
    return AppQueueDrainPlan {
        .mark_running = true,
        .drain_tasks = queued_task_count > 0U,
        .wrap_in_render_context = false,
        .task_count = queued_task_count,
    };
}

[[nodiscard]] constexpr AppUiTickPlan plan_app_ui_thread_tick(
    std::size_t queued_task_count,
    bool redraw) noexcept
{
    return AppUiTickPlan {
        .mark_running = true,
        .execute_tasks = queued_task_count > 0U,
        .tick_app = true,
        .update_before_render = redraw,
        .enqueue_render_frame = redraw,
        .task_count = queued_task_count,
    };
}

[[nodiscard]] inline pp::foundation::Result<AppUiLoopTimerPlan> plan_app_ui_loop_timers(
    float delta_time_seconds,
    float frame_accumulator,
    float fps_accumulator,
    float reload_accumulator,
    int rendered_frames,
    bool live_asset_reloading_enabled)
{
    if (!std::isfinite(delta_time_seconds) || !std::isfinite(frame_accumulator)
        || !std::isfinite(fps_accumulator) || !std::isfinite(reload_accumulator)) {
        return pp::foundation::Result<AppUiLoopTimerPlan>::failure(
            pp::foundation::Status::invalid_argument("UI loop timer values must be finite"));
    }

    if (delta_time_seconds < 0.0F || frame_accumulator < 0.0F
        || fps_accumulator < 0.0F || reload_accumulator < 0.0F || rendered_frames < 0) {
        return pp::foundation::Result<AppUiLoopTimerPlan>::failure(
            pp::foundation::Status::invalid_argument("UI loop timer values must not be negative"));
    }

    AppUiLoopTimerPlan plan;
    plan.frame_accumulator = frame_accumulator + delta_time_seconds;
    plan.fps_accumulator = fps_accumulator + delta_time_seconds;
    plan.reload_accumulator = reload_accumulator;
    plan.rendered_frames_after_report = rendered_frames;

    if (plan.fps_accumulator > 1.0F) {
        plan.report_rendered_frames = true;
        plan.reported_frame_count = rendered_frames;
        plan.fps_accumulator = 0.0F;
        plan.rendered_frames_after_report = 0;
    }

    if (live_asset_reloading_enabled) {
        plan.reload_accumulator += delta_time_seconds;
        if (plan.reload_accumulator > 1.0F) {
            plan.reload_accumulator = 0.0F;
            plan.check_live_asset_reload = true;
        }
    }

    return pp::foundation::Result<AppUiLoopTimerPlan>::success(plan);
}

[[nodiscard]] constexpr AppUiLoopRedrawPlan plan_app_ui_loop_redraw(
    bool redraw,
    int rendered_frames) noexcept
{
    return AppUiLoopRedrawPlan {
        .tick_app = true,
        .update_before_render = redraw,
        .enqueue_render_frame = redraw,
        .reset_frame_accumulator = redraw,
        .rendered_frames = rendered_frames + (redraw ? 1 : 0),
    };
}

[[nodiscard]] constexpr AppThreadStartPlan plan_app_thread_start() noexcept
{
    return AppThreadStartPlan {};
}

[[nodiscard]] constexpr AppThreadStopPlan plan_app_thread_stop(bool thread_joinable) noexcept
{
    return AppThreadStopPlan {
        .mark_not_running = true,
        .notify_worker = true,
        .join_thread = thread_joinable,
    };
}

} // namespace pp::app
