#pragma once

#include "MayaFlux/Kakshya/NDData/MeshData.hpp"
#include "MayaFlux/Kakshya/NDData/VertexFormats.hpp"

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

namespace MayaFlux::Kinesis {

/**
 * @brief Generate vertices along a circular path
 * @param center Circle center in 3D space
 * @param radius Circle radius
 * @param segments Number of subdivisions (must be >= 3)
 * @param normal Circle plane normal (default: XY plane, Z-up)
 * @return Vertices generated by angle θ ∈ [0, 2π)
 *
 * Generates N+1 vertices where last vertex equals first (closed curve).
 * For LINE_STRIP topology. For POINT_LIST, use N vertices.
 */
MAYAFLUX_API std::vector<glm::vec3> generate_circle(
    const glm::vec3& center,
    float radius,
    size_t segments,
    const glm::vec3& normal = glm::vec3(0, 0, 1));

/**
 * @brief Generate vertices along an elliptical path
 * @param center Ellipse center
 * @param semi_major Semi-major axis length
 * @param semi_minor Semi-minor axis length
 * @param segments Number of subdivisions
 * @param normal Ellipse plane normal
 * @return Vertices generated by eccentric anomaly
 */
MAYAFLUX_API std::vector<glm::vec3> generate_ellipse(
    const glm::vec3& center,
    float semi_major,
    float semi_minor,
    size_t segments,
    const glm::vec3& normal = glm::vec3(0, 0, 1));

/**
 * @brief Generate vertices of an axis-aligned rectangular path
 * @param center Rectangle centroid
 * @param width Extent along local X axis
 * @param height Extent along local Y axis
 * @param normal Rectangle plane normal
 * @return 5 vertices (closed rectangular path for LINE_STRIP)
 */
MAYAFLUX_API std::vector<glm::vec3> generate_rectangle(
    const glm::vec3& center,
    float width,
    float height,
    const glm::vec3& normal = glm::vec3(0, 0, 1));

/**
 * @brief Generate vertices of a regular n-gon
 * @param center Polygon centroid
 * @param radius Circumradius (center to vertex distance)
 * @param sides Number of sides (must be >= 3)
 * @param normal Polygon plane normal
 * @param phase_offset Angular offset in radians (default: 0)
 * @return N+1 vertices (closed path)
 *
 * Vertices positioned at angles: θ_k = (2πk/N) + phase_offset
 */
MAYAFLUX_API std::vector<glm::vec3> generate_regular_polygon(
    const glm::vec3& center,
    float radius,
    size_t sides,
    const glm::vec3& normal = glm::vec3(0, 0, 1),
    float phase_offset = 0.0F);

/**
 * @brief Apply rigid transformation to vertex set
 * @param vertices Vertices to transform (modified in-place)
 * @param transform Affine transformation matrix (4x4)
 */
MAYAFLUX_API void apply_transform(
    std::vector<glm::vec3>& vertices,
    const glm::mat4& transform);

/**
 * @brief Apply rotation to vertex set around arbitrary axis
 * @param vertices Vertices to transform (modified in-place)
 * @param axis Rotation axis (will be normalized)
 * @param angle Rotation angle in radians (right-hand rule)
 * @param origin Rotation pivot (default: coordinate origin)
 */
MAYAFLUX_API void apply_rotation(
    std::vector<glm::vec3>& vertices,
    const glm::vec3& axis,
    float angle,
    const glm::vec3& origin = glm::vec3(0));

/**
 * @brief Apply translation to vertex set
 * @param vertices Vertices to transform (modified in-place)
 * @param displacement Translation vector
 */
MAYAFLUX_API void apply_translation(
    std::vector<glm::vec3>& vertices,
    const glm::vec3& displacement);

/**
 * @brief Apply uniform scaling to vertex set
 * @param vertices Vertices to transform (modified in-place)
 * @param scale Scale factor (must be > 0)
 * @param origin Scaling pivot (default: coordinate origin)
 */
MAYAFLUX_API void apply_uniform_scale(
    std::vector<glm::vec3>& vertices,
    float scale,
    const glm::vec3& origin = glm::vec3(0));

/**
 * @brief Apply non-uniform scaling to vertex set
 * @param vertices Vertices to transform (modified in-place)
 * @param scale Per-axis scale factors
 * @param origin Scaling center
 */
MAYAFLUX_API void apply_scale(
    std::vector<glm::vec3>& vertices,
    const glm::vec3& scale,
    const glm::vec3& origin = glm::vec3(0));

/**
 * @brief Compute normal vectors along a piecewise-linear path
 * @param path_vertices Sequential vertices defining curve
 * @param normal_length Magnitude of normal vectors
 * @param stride Sample every stride-th vertex (default: 1)
 * @return Line segments (pairs) representing normals (for LINE_LIST topology)
 *
 * Normal at vertex i: perpendicular to tangent (v[i+1] - v[i])
 * Returned as pairs: [midpoint - normal/2, midpoint + normal/2]
 */
MAYAFLUX_API std::vector<Kakshya::LineVertex> compute_path_normals(
    const std::vector<Kakshya::LineVertex>& path_vertices,
    float normal_length,
    size_t stride = 1);

/**
 * @brief Compute tangent vectors along a piecewise-linear path
 * @param path_vertices Sequential vertices defining curve
 * @param tangent_length Magnitude of tangent vectors
 * @param stride Sample every stride-th vertex
 * @return Line segments (pairs) representing tangents (for LINE_LIST topology)
 *
 * Tangent at vertex i: direction (v[i+1] - v[i])
 * Returned as pairs: [vertex - tangent/2, vertex + tangent/2]
 */
MAYAFLUX_API std::vector<Kakshya::LineVertex> compute_path_tangents(
    const std::vector<Kakshya::LineVertex>& path_vertices,
    float tangent_length,
    size_t stride = 1);

/**
 * @brief Compute curvature vectors along a path (2nd derivative approximation)
 * @param path_vertices Sequential vertices defining curve
 * @param curvature_scale Magnitude scaling factor
 * @param stride Sample every stride-th vertex
 * @return Line segments representing discrete curvature
 *
 * Curvature at i approximated by: (v[i+1] - 2*v[i] + v[i-1])
 */
MAYAFLUX_API std::vector<Kakshya::LineVertex> compute_path_curvature(
    const std::vector<Kakshya::LineVertex>& path_vertices,
    float curvature_scale,
    size_t stride = 1);

/**
 * @brief Sample parametric curve uniformly in parameter space
 * @param curve Parametric function: [0,1] → ℝ³
 * @param samples Number of samples
 * @return Vertices sampled at t_k = k/(N-1) for k ∈ [0, N-1]
 */
MAYAFLUX_API std::vector<glm::vec3> sample_parametric_curve(
    const std::function<glm::vec3(float)>& curve,
    size_t samples);

/**
 * @brief Resample path vertices for arc-length parameterization
 * @param path_vertices Input vertices (arbitrary spacing)
 * @param num_samples Desired output sample count
 * @return Vertices uniformly distributed by arc length
 *
 * Uses piecewise linear arc length estimation.
 * Output vertices have constant spacing along curve.
 */
MAYAFLUX_API std::vector<Kakshya::LineVertex> reparameterize_by_arc_length(
    const std::vector<Kakshya::LineVertex>& path_vertices,
    size_t num_samples);

/**
 * @brief Project vertices onto plane defined by normal
 * @param vertices Vertices to project (modified in-place)
 * @param plane_point Point on projection plane
 * @param plane_normal Plane normal vector
 */
MAYAFLUX_API void project_onto_plane(
    std::vector<glm::vec3>& vertices,
    const glm::vec3& plane_point,
    const glm::vec3& plane_normal);

/**
 * @brief Compute convex hull of vertex set (2D projection)
 * @param vertices Input vertex set
 * @param projection_normal Normal of projection plane (default: Z-axis)
 * @return Vertices forming convex hull boundary (closed path)
 *
 * Uses Graham scan on projected coordinates.
 */
MAYAFLUX_API std::vector<glm::vec3> compute_convex_hull_2d(
    const std::vector<glm::vec3>& vertices,
    const glm::vec3& projection_normal = glm::vec3(0, 0, 1));

/**
 * @brief Apply color interpolation to position vertices
 * @param positions Input position vertices
 * @param colors Color stops for interpolation
 * @param color_positions Normalized positions [0,1] for each color stop
 * @param default_thickness Line thickness for all vertices (default: 1.0)
 * @return LineVertex array with interpolated colors
 *
 * Example:
 *   colors = {red, blue}
 *   color_positions = {0.0, 1.0}
 *   → Linear gradient from red to blue along path
 *
 * If color_positions.empty(), distributes colors uniformly.
 */
[[nodiscard]] MAYAFLUX_API std::vector<Kakshya::Vertex> apply_color_gradient(
    const std::vector<glm::vec3>& positions,
    const std::vector<glm::vec3>& colors,
    const std::vector<float>& color_positions = {},
    float scalar = 1.0F);

/**
 * @brief Apply uniform color to position vertices
 */
[[nodiscard]] MAYAFLUX_API std::vector<Kakshya::Vertex> apply_uniform_color(
    const std::vector<glm::vec3>& positions,
    const glm::vec3& color,
    float scalar = 1.0F);

/**
 * @brief Convert positions to LineVertex with per-vertex colors
 */
[[nodiscard]] MAYAFLUX_API std::vector<Kakshya::Vertex> apply_vertex_colors(
    const std::vector<glm::vec3>& positions,
    const std::vector<glm::vec3>& colors,
    float scalar = 1.0F);

/**
 * @struct QuadGeometry
 * @brief Textured quad vertex data together with its semantic layout descriptor.
 *
 * Returned by generate_quad(). Callers memcpy vertices into their staging
 * buffer and forward layout to set_vertex_layout().
 */
struct QuadGeometry {
    std::array<Kakshya::TextureQuadVertex, 4> vertices;
    Kakshya::VertexLayout layout;
};

/**
 * @brief Generate a textured quad centred on the origin.
 * @param position Translation in XY (z remains 0). Default: no translation.
 * @param scale Extents in XY before rotation. Default: {1,1} → NDC-spanning quad.
 * @param rotation Rotation angle in radians around Z axis. Default: 0.
 * @return QuadGeometry ready for TRIANGLE_STRIP draw.
 *
 * UV origin is bottom-left (0,1) to match Vulkan image layout.
 * Base vertices sit at ±scale before rotation and translation are applied.
 */
[[nodiscard]] MAYAFLUX_API QuadGeometry generate_quad(
    glm::vec2 position = glm::vec2(0.0F),
    glm::vec2 scale = glm::vec2(1.0F),
    float rotation = 0.0F);

// ------------------------------------------------------------
// 2D filled / textured quads
// ------------------------------------------------------------

/**
 * @brief Generate a filled TRIANGLE_STRIP quad from an AABB2D.
 * @param region NDC axis-aligned bounds.
 * @param color  Uniform fill color.
 */
[[nodiscard]] MAYAFLUX_API std::array<Kakshya::Vertex, 4> filled_rect(
    Kinesis::AABB2D region,
    glm::vec3 color = glm::vec3(1.F));

/**
 * @brief Generate a UV-mapped TRIANGLE_STRIP quad from an AABB2D.
 *
 * UV origin is bottom-left (0,1) matching Vulkan image layout.
 * Replaces handwritten textured quad construction in Forma callers.
 *
 * @param region NDC axis-aligned bounds.
 */
[[nodiscard]] MAYAFLUX_API std::array<Kakshya::TextureQuadVertex, 4> textured_rect(
    Kinesis::AABB2D region);

// ------------------------------------------------------------
// 3D wireframe shapes
// ------------------------------------------------------------

/**
 * @brief Generate a cuboid wireframe as LINE_LIST pairs.
 * @param center Centre of the cuboid.
 * @param half   Half-extents along each axis.
 * @param color  Uniform edge color.
 */
[[nodiscard]] MAYAFLUX_API std::vector<Kakshya::Vertex> cuboid_wireframe(
    const glm::vec3& center,
    const glm::vec3& half,
    const glm::vec3& color = glm::vec3(1.F));

/**
 * @brief Generate a solid box as an indexed TRIANGLE_LIST mesh.
 *
 * Six faces, four vertices each, two triangles per face. Per-face normals point
 * outward along the face axis. UV origin is bottom-left matching Vulkan image
 * layout and the generate_quad convention.
 *
 * @param center       Centre of the box in world space.
 * @param half_extents Half-size along each axis. Non-uniform values yield a cuboid.
 * @param subdivisions Reserved for future per-face tessellation. Currently ignored.
 * @return MeshData ready for MeshBuffer::set_mesh_data() or MeshWriterNode::set_mesh().
 */
[[nodiscard]] MAYAFLUX_API Kakshya::MeshData generate_box(
    const glm::vec3& center,
    const glm::vec3& half_extents,
    uint32_t subdivisions = 1);

/**
 * @brief Generate a subdivided flat grid in the XZ plane.
 *
 * Vertices lie at Y = 0. U maps to X, V maps to Z. UV covers [0,1] across
 * the full extent. Normals point along +Y throughout.
 *
 * @param center    World-space centre of the grid.
 * @param extent_x  Total width along X.
 * @param extent_z  Total depth along Z.
 * @param cols      Number of columns (cells along X). Clamped to minimum 1.
 * @param rows      Number of rows (cells along Z). Clamped to minimum 1.
 * @return MeshData ready for TRIANGLE_LIST draw.
 */
[[nodiscard]] MAYAFLUX_API Kakshya::MeshData generate_grid(
    const glm::vec3& center,
    float extent_x,
    float extent_z,
    uint32_t cols,
    uint32_t rows,
    const glm::vec3& normal = glm::vec3(0.0F, 1.0F, 0.0F));

/**
 * @brief Generate a mesh from an arbitrary parametric surface function.
 *
 * @p fn maps (u, v) in [0,1]^2 to a world-space position. Normals are
 * computed via finite differences on @p fn so they follow the surface
 * exactly regardless of shape. UV is mapped directly from (u, v).
 *
 * Any surface expressible as a function of two parameters is valid:
 * torus, Möbius band, Klein bottle approximation, spherical harmonic
 * deformation, audio-driven terrain, or any procedural form.
 *
 * @param fn        Surface function: (u, v) -> glm::vec3.
 * @param u_segs    Subdivisions along U. Clamped to minimum 1.
 * @param v_segs    Subdivisions along V. Clamped to minimum 1.
 * @return MeshData ready for TRIANGLE_LIST draw.
 */
[[nodiscard]] MAYAFLUX_API Kakshya::MeshData generate_parametric_surface(
    const std::function<glm::vec3(float u, float v)>& fn,
    uint32_t u_segs,
    uint32_t v_segs);

/**
 * @brief Extrude a circle along an arbitrary 3D path.
 *
 * Each path point becomes a ring of @p radial_segments vertices. The ring
 * frame is propagated along the path using parallel transport, which
 * eliminates the twisting that naive normal-aligned cross-sections produce.
 *
 * @p radius_fn maps the normalised path parameter t in [0,1] to a radius.
 * A constant lambda gives a uniform tube. Any callable works: audio node
 * output, envelope, position-dependent modulation.
 *
 * Caps are flat and optional. An open tube (no caps) is suitable for chains
 * and paths; a capped tube is suitable for solid cylindrical forms.
 *
 * @param path            Ordered world-space positions defining the spine.
 *                        Minimum 2 points. Clamped to minimum 2.
 * @param radius_fn       Callable: float(float t) returning radius at t.
 * @param radial_segments Number of vertices per ring. Clamped to minimum 3.
 * @param capped          If true, flat polygon caps are added at both ends.
 * @return MeshData ready for TRIANGLE_LIST draw.
 */
[[nodiscard]] MAYAFLUX_API Kakshya::MeshData generate_tube(
    std::span<const glm::vec3> path,
    const std::function<float(float t)>& radius_fn,
    uint32_t radial_segments = 8,
    bool capped = false);

/**
 * @brief Revolve a 2D profile curve around the Y axis.
 *
 * @p profile_fn maps t in [0,1] to a point in the XY plane. X is the radial
 * distance from the Y axis; Y is the height. Negative X values are valid and
 * produce interior geometry. The profile is revolved through @p sweep_radians,
 * defaulting to a full revolution.
 *
 * UV: u maps to the revolution angle in [0,1]; v maps to the profile
 * parameter t.
 *
 * @param profile_fn      Callable: glm::vec2(float t) in the XY plane.
 * @param profile_segs    Samples along the profile. Clamped to minimum 2.
 * @param radial_segs     Subdivisions around the axis. Clamped to minimum 3.
 * @param sweep_radians   Arc of revolution. Default: full circle.
 * @return MeshData ready for TRIANGLE_LIST draw.
 */
[[nodiscard]] MAYAFLUX_API Kakshya::MeshData generate_revolution(
    const std::function<glm::vec2(float t)>& profile_fn,
    uint32_t profile_segs,
    uint32_t radial_segs,
    float sweep_radians = glm::two_pi<float>());

/**
 * @brief Extract an isosurface from a scalar field using marching cubes.
 *
 * Evaluates @p field at every corner of a regular grid spanning
 * [@p bounds_min, @p bounds_max] with @p res_x * @p res_y * @p res_z cells.
 * Triangles are generated wherever the field crosses @p iso_level.
 * Vertex normals are estimated via central finite differences on the field.
 *
 * Any Kinesis::SpatialField is valid: distance(), combine(), chain(),
 * or any user-supplied glm::vec3 -> float lambda wrapped in a SpatialField.
 *
 * @param field      SpatialField: glm::vec3 -> float.
 * @param bounds_min World-space minimum corner of the evaluation volume.
 * @param bounds_max World-space maximum corner of the evaluation volume.
 * @param res_x      Cell count along X. Clamped to minimum 1.
 * @param res_y      Cell count along Y. Clamped to minimum 1.
 * @param res_z      Cell count along Z. Clamped to minimum 1.
 * @param iso_level  Isosurface threshold value. Default 0.0.
 * @return MeshData ready for TRIANGLE_LIST draw, or empty MeshData if the
 *         field produces no crossings at iso_level.
 */
[[nodiscard]] MAYAFLUX_API Kakshya::MeshData generate_sdf_mesh(
    const Kinesis::SpatialField& field,
    const glm::vec3& bounds_min,
    const glm::vec3& bounds_max,
    uint32_t res_x,
    uint32_t res_y,
    uint32_t res_z,
    float iso_level = 0.0F);

/**
 * @brief Extract an isosurface mesh from a scalar field over a lattice.
 * @param field Sampled at grid corners in world space.
 * @param lattice Cell counts and world bounds of the evaluation volume.
 * @param iso_level Field value the surface is placed at.
 * @return Mesh data, empty if the field has no crossings at @p iso_level.
 */
MAYAFLUX_API Kakshya::MeshData generate_sdf_mesh(
    const Kinesis::SpatialField& field,
    const Lattice3D& lattice,
    float iso_level = 0.0F);

} // namespace MayaFlux::Kinesis
