#pragma once

#include "foundation/result.h"

#include <cmath>
#include <cstddef>

namespace pp::app {

struct AppPointerDispatchPlan {
    bool request_redraw = true;
    bool dispatch_designer_first = false;
    bool dispatch_main_if_not_consumed = false;
    float normalized_x = 0.0F;
    float normalized_y = 0.0F;
};

struct AppInputDispatchPlan {
    bool request_redraw = true;
    bool dispatch_main = false;
};

struct AppGestureDispatchPlan {
    bool request_redraw = true;
    bool dispatch_main = false;
    float normalized_x = 0.0F;
    float normalized_y = 0.0F;
    float distance = 0.0F;
    float distance_delta = 0.0F;
    float position_delta_x = 0.0F;
    float position_delta_y = 0.0F;
};

struct AppKeyDispatchPlan {
    bool request_redraw = true;
    bool dispatch_main = false;
    bool set_key_down = false;
    bool sync_vr_camera_rotation = false;
};

struct AppUiVisibilityTogglePlan {
    bool next_ui_visible = true;
    std::size_t first_panel_child_index = 1;
    std::size_t panel_child_count = 0;
};

struct AppStylusAttachPlan {
    bool set_has_stylus = true;
    bool enable_canvas_touch_lock = false;
};

[[nodiscard]] inline pp::foundation::Status validate_input_zoom(float zoom)
{
    if (!std::isfinite(zoom) || zoom <= 0.0F) {
        return pp::foundation::Status::invalid_argument("input zoom must be finite and positive");
    }

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

[[nodiscard]] inline pp::foundation::Result<AppPointerDispatchPlan> plan_app_pointer_dispatch(
    float x,
    float y,
    float zoom,
    bool has_designer_layout,
    bool has_main_layout)
{
    const auto zoom_status = validate_input_zoom(zoom);
    if (!zoom_status.ok()) {
        return pp::foundation::Result<AppPointerDispatchPlan>::failure(zoom_status);
    }

    if (!std::isfinite(x) || !std::isfinite(y)) {
        return pp::foundation::Result<AppPointerDispatchPlan>::failure(
            pp::foundation::Status::invalid_argument("input coordinates must be finite"));
    }

    return pp::foundation::Result<AppPointerDispatchPlan>::success(AppPointerDispatchPlan {
        .request_redraw = true,
        .dispatch_designer_first = has_designer_layout,
        .dispatch_main_if_not_consumed = has_main_layout,
        .normalized_x = x / zoom,
        .normalized_y = y / zoom,
    });
}

[[nodiscard]] constexpr AppPointerDispatchPlan plan_app_mouse_cancel_dispatch(
    bool has_designer_layout,
    bool has_main_layout) noexcept
{
    return AppPointerDispatchPlan {
        .request_redraw = true,
        .dispatch_designer_first = has_designer_layout,
        .dispatch_main_if_not_consumed = has_main_layout,
        .normalized_x = 0.0F,
        .normalized_y = 0.0F,
    };
}

[[nodiscard]] constexpr AppInputDispatchPlan plan_app_main_input_dispatch(bool has_main_layout) noexcept
{
    return AppInputDispatchPlan {
        .request_redraw = true,
        .dispatch_main = has_main_layout,
    };
}

[[nodiscard]] inline pp::foundation::Result<AppGestureDispatchPlan> plan_app_gesture_dispatch(
    float x0,
    float y0,
    float x1,
    float y1,
    float previous_x0,
    float previous_y0,
    float previous_x1,
    float previous_y1,
    float zoom,
    bool has_main_layout)
{
    const auto zoom_status = validate_input_zoom(zoom);
    if (!zoom_status.ok()) {
        return pp::foundation::Result<AppGestureDispatchPlan>::failure(zoom_status);
    }

    if (!std::isfinite(x0) || !std::isfinite(y0) || !std::isfinite(x1) || !std::isfinite(y1)
        || !std::isfinite(previous_x0) || !std::isfinite(previous_y0)
        || !std::isfinite(previous_x1) || !std::isfinite(previous_y1)) {
        return pp::foundation::Result<AppGestureDispatchPlan>::failure(
            pp::foundation::Status::invalid_argument("gesture coordinates must be finite"));
    }

    const float midpoint_x = (x0 + x1) * 0.5F;
    const float midpoint_y = (y0 + y1) * 0.5F;
    const float previous_midpoint_x = (previous_x0 + previous_x1) * 0.5F;
    const float previous_midpoint_y = (previous_y0 + previous_y1) * 0.5F;
    const float dx = x1 - x0;
    const float dy = y1 - y0;
    const float previous_dx = previous_x1 - previous_x0;
    const float previous_dy = previous_y1 - previous_y0;
    const float distance = std::sqrt(dx * dx + dy * dy);
    const float previous_distance = std::sqrt(previous_dx * previous_dx + previous_dy * previous_dy);

    return pp::foundation::Result<AppGestureDispatchPlan>::success(AppGestureDispatchPlan {
        .request_redraw = true,
        .dispatch_main = has_main_layout,
        .normalized_x = midpoint_x / zoom,
        .normalized_y = midpoint_y / zoom,
        .distance = distance,
        .distance_delta = distance - previous_distance,
        .position_delta_x = midpoint_x - previous_midpoint_x,
        .position_delta_y = midpoint_y - previous_midpoint_y,
    });
}

[[nodiscard]] constexpr AppKeyDispatchPlan plan_app_key_down_dispatch(
    bool has_main_layout,
    bool is_spacebar,
    bool vr_active) noexcept
{
    return AppKeyDispatchPlan {
        .request_redraw = true,
        .dispatch_main = has_main_layout,
        .set_key_down = true,
        .sync_vr_camera_rotation = is_spacebar && vr_active,
    };
}

[[nodiscard]] constexpr AppKeyDispatchPlan plan_app_key_up_dispatch(bool has_main_layout) noexcept
{
    return AppKeyDispatchPlan {
        .request_redraw = true,
        .dispatch_main = has_main_layout,
        .set_key_down = false,
        .sync_vr_camera_rotation = false,
    };
}

[[nodiscard]] inline pp::foundation::Result<AppUiVisibilityTogglePlan> plan_app_ui_visibility_toggle(
    bool current_ui_visible,
    bool has_main_layout,
    std::size_t main_child_count,
    std::size_t panel_child_count)
{
    if (!has_main_layout) {
        return pp::foundation::Result<AppUiVisibilityTogglePlan>::failure(
            pp::foundation::Status::invalid_argument("UI toggle requires a main layout"));
    }

    if (main_child_count <= 1U) {
        return pp::foundation::Result<AppUiVisibilityTogglePlan>::failure(
            pp::foundation::Status::invalid_argument("UI toggle requires a panel container child"));
    }

    return pp::foundation::Result<AppUiVisibilityTogglePlan>::success(AppUiVisibilityTogglePlan {
        .next_ui_visible = !current_ui_visible,
        .first_panel_child_index = 1U,
        .panel_child_count = panel_child_count,
    });
}

[[nodiscard]] constexpr AppStylusAttachPlan plan_app_stylus_attach(bool has_canvas) noexcept
{
    return AppStylusAttachPlan {
        .set_has_stylus = true,
        .enable_canvas_touch_lock = has_canvas,
    };
}

} // namespace pp::app
