#pragma once

#include "Geometry.hpp"

#include "MayaFlux/Portal/Forma/Context.hpp"

#include "MayaFlux/Kinesis/Spatial/Projection.hpp"

namespace MayaFlux::Portal::Forma::Geometry {

/**
 * @brief Register a drag handler writing a projected cursor position to @p state.
 *
 * The projection carries the geometry-specific mapping.
 * Kinesis::Projection supplies the inverses matching the geometry functions
 * above: axis_fraction for the faders, unit_square for position_picker,
 * angle_fraction for radial, path_fraction for stroke_slider.
 *
 * @param ctx     Context owning the element's callbacks.
 * @param id      Element id returned from create_element.
 * @param state   MappedState written on each drag event.
 * @param project NDC to value mapping.
 * @param btn     Mouse button that must be held.
 */
template <typename T>
void wire_drag(
    Context& ctx,
    uint32_t id,
    std::shared_ptr<MappedState<T>> state,
    std::function<T(glm::vec2)> project,
    IO::MouseButtons btn = IO::MouseButtons::Left)
{
    if (!state || !project)
        return;

    ctx.wire_drag(
        id,
        [state = std::move(state), project = std::move(project)](glm::vec2 ndc) {
            state->write(project(ndc));
        },
        btn);
}

/**
 * @brief Wire drag interaction for a drawable canvas element.
 *
 * Registers a left-button drag callback on @p ctx that maps NDC cursor
 * position to a sample index and amplitude, writes into @p state, and
 * increments the version. Linear interpolation fills the range between the
 * previously touched index and the current one, preventing sparse samples
 * under fast drag.
 *
 * @param ctx     Context owning the element.
 * @param id      Element id from the Mapped.
 * @param state   MappedState<vector<float>> to write into.
 * @param bounds  Canvas NDC bounds — must match those passed to drawable_canvas().
 */
MAYAFLUX_API void wire_canvas_drag(
    Context& ctx,
    uint32_t id,
    std::shared_ptr<MappedState<std::vector<float>>> state,
    Kinesis::AABB2D bounds);

/**
 * @brief Produce a Form::wire that registers a drag writing a projected
 *        cursor position.
 *
 * @param project NDC to value mapping, from Kinesis::Projection.
 * @param btn     Mouse button that must be held.
 */
template <typename T>
[[nodiscard]] auto drag_with(
    std::function<T(glm::vec2)> project,
    IO::MouseButtons btn = IO::MouseButtons::Left)
{
    return [project = std::move(project), btn](
               Context& ctx, uint32_t id, std::shared_ptr<MappedState<T>> state) {
        wire_drag<T>(ctx, id, std::move(state), project, btn);
    };
}

/**
 * @brief Produce a Form::wire that registers a press flipping a bool state.
 */
[[nodiscard]] inline auto press_flip(IO::MouseButtons btn = IO::MouseButtons::Left)
{
    return [btn](Context& ctx, uint32_t id, std::shared_ptr<MappedState<bool>> state) {
        ctx.on_press(id, btn, [state = std::move(state)](uint32_t, glm::vec2) {
            state->write(!state->value);
        });
    };
}

/**
 * @brief Produce a Form::wire that registers canvas painting over @p bounds.
 */
[[nodiscard]] inline auto paint_over(Kinesis::AABB2D bounds)
{
    return [bounds](Context& ctx, uint32_t id,
               std::shared_ptr<MappedState<std::vector<float>>> state) {
        wire_canvas_drag(ctx, id, std::move(state), bounds);
    };
}

/**
 * @brief Produce a Form::wire that writes the cursor position on hover.
 *
 * No button held. For geometry whose value is an NDC position rather than
 * a normalized parameter.
 */
[[nodiscard]] inline auto follow_move()
{
    return [](Context& ctx, uint32_t id, std::shared_ptr<MappedState<glm::vec2>> state) {
        ctx.on_move(id, [state = std::move(state)](uint32_t, glm::vec2 ndc) {
            state->write(ndc);
        });
    };
}

// =============================================================================
// Horizontal fader
//
// Value in [0, 1] moves a handle quad along a track quad.
// Two quads: track (static) and handle (value-driven).
// Both written as TRIANGLE_STRIP pairs into a single buffer.
//
// Track: full extent of bounds.
// Handle: small square at x = bounds.min.x + value * bounds.width().
//
// Hit region follows the handle, updated on every sync().
// =============================================================================

/**
 * @brief Geometry function for a horizontal fader in NDC space.
 * @param bounds      Full extent of the fader in NDC.
 * @param handle_w    Handle width in NDC units.
 * @param track_color Track quad color.
 * @param handle_color Handle quad color.
 */
[[nodiscard]] MAYAFLUX_API Form<float> horizontal_fader(
    Kinesis::AABB2D bounds,
    float handle_w,
    glm::vec3 track_color = glm::vec3(0.3F),
    glm::vec3 handle_color = glm::vec3(0.9F));

// =============================================================================
// Vertical fader
//
// Symmetric counterpart to horizontal_fader. Value in [0, 1] moves a handle
// quad upward along a track quad.
//
// Track: thin vertical band through the center of bounds.
// Handle: small square at y = bounds.min.y + value * (bounds.height() - handle_h).
//
// Hit region follows the handle, updated on every sync().
// Topology: TRIANGLE_STRIP (two quad pairs via to_mesh_vertices).
// =============================================================================

/**
 * @brief Geometry function for a vertical fader in NDC space.
 * @param bounds       Full extent of the fader in NDC.
 * @param handle_h     Handle height in NDC units.
 * @param track_color  Track quad color.
 * @param handle_color Handle quad color.
 */
[[nodiscard]] MAYAFLUX_API Form<float> vertical_fader(
    Kinesis::AABB2D bounds,
    float handle_h,
    glm::vec3 track_color = glm::vec3(0.3F),
    glm::vec3 handle_color = glm::vec3(0.9F));

// =============================================================================
// Radial / arc
//
// Value in [0, 1] sweeps an indicator line around the centre of a region.
// angle_start and angle_end in radians, measured from +X, CCW.
// Produces a LINE_LIST of two vertices: centre to indicator tip.
//
// The radius is the region's smaller half-extent, so the sweep is inscribed
// regardless of aspect. For a centre and radius, build the region with
// Kinesis::AABB2D::from_ndc(centre, glm::vec2(radius)).
// =============================================================================

/**
 * @brief Geometry function for a radial indicator in NDC space.
 * @param region      NDC region the arc is inscribed in.
 * @param angle_start Start angle in radians (value = 0).
 * @param angle_end   End angle in radians (value = 1).
 * @param color       Line color.
 */
[[nodiscard]] MAYAFLUX_API Form<float> radial(
    Kinesis::AABB2D region,
    float angle_start,
    float angle_end,
    glm::vec3 color = glm::vec3(0.9F));

// =============================================================================
// Point
//
// Value is glm::vec2 in NDC space. Produces a single POINT_LIST vertex.
// Hit region is a circle centered on the point.
// Use as a cursor follower, node handle, or any positioned point primitive.
// =============================================================================

/**
 * @brief Geometry function for a positioned point in NDC space.
 *
 * Value type is glm::vec2 (NDC position). Renders a single PointVertex
 * and sets a circular hit region centered on the position. Suitable as
 * a cursor follower, node handle, or any draggable point primitive.
 *
 * @param color      Point color.
 * @param size       Point size in pixels.
 * @param hit_radius Hit region radius in NDC units.
 */
[[nodiscard]] MAYAFLUX_API Form<glm::vec2> point(
    glm::vec3 color = glm::vec3(1.0F),
    float size = 10.0F,
    float hit_radius = 0.04F);

// =============================================================================
// 2D position picker
//
// Value is glm::vec2 in [0,1]x[0,1], mapped to a point inside bounds.
// Produces a single POINT_LIST vertex at the mapped position.
// =============================================================================

/**
 * @brief Geometry function for a 2D position picker in NDC space.
 * @param bounds Full extent of the pick area in NDC.
 * @param color  Point color.
 * @param size   Point size in pixels.
 */
[[nodiscard]] MAYAFLUX_API Form<glm::vec2> position_picker(
    Kinesis::AABB2D bounds,
    glm::vec3 color = glm::vec3(0.9F),
    float size = 8.0F);

// =============================================================================
// Stroke slider
//
// Value in [0, 1] positions a handle point along a user-supplied polyline.
// Renders two layers: the full path as a LINE_LIST in track_color, a
// highlighted prefix segment from path start to the handle position in
// fill_color, and a PointVertex handle on a caller-supplied secondary buffer.
//
// The secondary buffer must be a POINT_LIST FormaBuffer registered with the
// same BufferManager and window before this function is called. The caller
// adds it as a separate Element and relates it to the path element via
// Layer::relate_to so removal and visibility cascade.
//
// Hit region: stroke_bounds over the full path at half_thickness.
// bounds_hint: tight AABB enclosing all path points.
//
// Topology for the path buffer: LINE_LIST.
// Topology for the handle buffer: POINT_LIST.
// =============================================================================

/**
 * @brief Geometry function for a value scrubber along an arbitrary polyline.
 *
 * Value in [0, 1] maps to arc-length position along @p path. The full path
 * is rendered as a LINE_LIST in @p track_color; the prefix from the path
 * start to the handle position is rendered in @p fill_color. The handle
 * itself is a PointVertex submitted to @p handle_buf each sync.
 *
 * @param path           Ordered polyline vertices in NDC. Copied into closure.
 * @param handle_buf     POINT_LIST FormaBuffer for the handle point.
 *                       Must be registered and have setup_rendering called.
 * @param half_thickness Hit region half-thickness in NDC units.
 * @param track_color    Color of the full path.
 * @param fill_color     Color of the prefix segment up to the handle.
 * @param handle_color   Color of the handle point.
 * @param handle_size    Handle point size in pixels.
 */
[[nodiscard]] MAYAFLUX_API Form<float> stroke_slider(
    std::span<const glm::vec2> path,
    std::shared_ptr<Buffers::FormaBuffer> handle_buf,
    float half_thickness = 0.02F,
    glm::vec3 track_color = glm::vec3(0.3F),
    glm::vec3 fill_color = glm::vec3(0.2F, 0.6F, 1.0F),
    glm::vec3 handle_color = glm::vec3(0.95F),
    float handle_size = 10.0F);

// =============================================================================
// Toggle
//
// Value type: bool. Renders a filled rect in one of two colors.
// The geometry function is purely visual — it carries no interaction.
// The caller wires on_press to flip state->value:
//
//   surface.ctx().on_press(el.element.id, IO::MouseButtons::Left,
//       [state = el.state](uint32_t, glm::vec2) {
//           state->write(!state->value);
//       });
//
// Topology: TRIANGLE_STRIP (4 MeshVertex via to_mesh_vertices).
// =============================================================================

/**
 * @brief Geometry function for a boolean toggle in NDC space.
 *
 * Renders @p region as a filled rect in @p color_off or @p color_on based
 * on the current bool value. Interaction is the caller's responsibility.
 *
 * @param region    NDC bounds of the toggle.
 * @param color_off Fill color when false.
 * @param color_on  Fill color when true.
 */
[[nodiscard]] MAYAFLUX_API Form<bool> toggle(
    Kinesis::AABB2D region,
    glm::vec3 color_off = glm::vec3(0.25F),
    glm::vec3 color_on = glm::vec3(0.2F, 0.7F, 0.4F));

// =============================================================================
// Level meter
//
// Value type: float in [0, 1]. Renders a filled bar from the origin edge of
// bounds proportional to the value, with the remainder as a second color.
// No handle, no hit region. Suitable for audio level, progress, any scalar readout.
//
// Orientation:
//   horizontal = true  - bar grows left to right
//   horizontal = false - bar grows bottom to top
//
// Topology: TRIANGLE_STRIP (two quad pairs via to_mesh_vertices).
// =============================================================================

/**
 * @brief Geometry function for a level meter in NDC space.
 *
 * No interaction. Drive from a node via bridge().at(el.state).bind(node).
 *
 * @param bounds      Full extent of the meter in NDC.
 * @param horizontal  True for left-to-right fill, false for bottom-to-top.
 * @param fill_color  Color of the active (filled) portion.
 * @param track_color Color of the inactive remainder.
 */
[[nodiscard]] MAYAFLUX_API Form<float> level_meter(
    Kinesis::AABB2D bounds,
    bool horizontal = true,
    glm::vec3 fill_color = glm::vec3(0.2F, 0.7F, 0.3F),
    glm::vec3 track_color = glm::vec3(0.15F));

// =============================================================================
// Crosshair
//
// Value type: glm::vec2 (NDC position). Renders two LINE_LIST segments —
// horizontal and vertical — crossing at the value position.
// Hit region: circle centered on the position.
//
// Pairs naturally with position_picker sharing the same MappedState<glm::vec2>.
// Topology: LINE_LIST (4 LineVertex).
// =============================================================================

/**
 * @brief Geometry function for a crosshair indicator in NDC space.
 *
 * @param arm_len    Half-length of each arm in NDC units.
 * @param color      Line color.
 * @param thickness  Line thickness (maps to LineVertex::thickness).
 * @param hit_radius Hit region radius in NDC units.
 */
[[nodiscard]] MAYAFLUX_API Form<glm::vec2> crosshair(
    float arm_len = 0.04F,
    glm::vec3 color = glm::vec3(0.9F),
    float thickness = 1.F,
    float hit_radius = 0.05F);

// =============================================================================
// Drawable canvas
//
// value is vector<float> of N samples in [0, 1] mapped to the Y axis.
// X positions are distributed evenly across bounds.
// Renders as LINE_LIST: N-1 segments connecting adjacent samples.
//
// on_drag callback pattern:
//   ctx->on_drag(el.element.id, IO::MouseButtons::Left,
//       [&el, bounds](uint32_t, glm::vec2 ndc) {
//           auto& v = el.state->value;
//           const float t = (ndc.x - bounds.min.x) / bounds.width();
//           const size_t i = static_cast<size_t>(
//               std::clamp(t, 0.F, 1.F) * (v.size() - 1));
//           const float a = (ndc.y - bounds.min.y) / bounds.height();
//           v[i] = std::clamp(a, 0.F, 1.F);
//           ++el.state->version;
//       });
//
// For sparse-sample prevention at high drag speed, interpolate between the
// previous and current index before calling state->write().
//
// Topology: LINE_LIST.
// =============================================================================

/**
 * @brief Geometry function for a drawable curve canvas in NDC space.
 *
 * Renders the sample vector as a LINE_LIST polyline. Each adjacent sample
 * pair becomes one segment. The hit region covers the full canvas bounds.
 *
 * @param bounds      Canvas extent in NDC.
 * @param color       Line color.
 * @param thickness   LineVertex thickness value.
 */
[[nodiscard]] MAYAFLUX_API Form<std::vector<float>> drawable_canvas(
    Kinesis::AABB2D bounds,
    glm::vec3 color = glm::vec3(0.8F),
    float thickness = 1.5F);

} // namespace MayaFlux::Portal::Forma::Geometry
