#pragma once

struct GraphData; // forward declare

struct ForceLayoutConfig {
    float repulsion    = 500.0f;  // repulsion strength between all nodes
    float attraction   = 0.01f;   // spring constant for edges
    float damping      = 0.85f;   // velocity decay per step
    float min_distance = 1.0f;    // minimum distance (avoid division by zero)
    float theta        = 0.5f;    // Barnes-Hut threshold (0 = exact, 1 = fast)
    float gravity      = 0.1f;    // pull toward center (prevents drift)
    float max_velocity = 50.0f;   // cap velocity per axis
    int   iterations   = 1;       // steps per call
};

// Perform one (or more) steps of force-directed layout.
// Modifies node positions (x, y) and velocities (vx, vy) in-place.
// Returns the total kinetic energy (sum of |v|^2). When energy < threshold,
// layout has converged.
float graph_force_layout_step(GraphData& graph, const ForceLayoutConfig& config = {});

// Reset: randomize positions within [-spread, spread], zero velocities.
void graph_force_layout_reset(GraphData& graph, float spread = 200.0f);

// Preset layouts (non-iterative). Wrappers deprecados que delegan en
// graph_layouts.h. Se mantienen por compat — usar graph::layout_circular /
// graph::layout_grid directamente en codigo nuevo.
void graph_layout_circular(GraphData& graph, float radius = 100.0f);
void graph_layout_grid(GraphData& graph, float spacing = 20.0f);

// Auto-pause helper. Pure: el caller mantiene `consecutive_low_frames` y se
// encarga de incrementarlo / ponerlo a cero cada frame.
//
// Patron de uso tipico:
//   static int low = 0;
//   float energy = graph_force_layout_step(g, cfg);
//   float per_node = g.node_count > 0 ? energy / g.node_count : 0.0f;
//   if (per_node < threshold) low++; else low = 0;
//   if (graph_force_layout_should_pause(low, min_consecutive)) running = false;
//
// Devuelve `true` si la energia ha caido por debajo del umbral durante al
// menos `min_consecutive` frames consecutivos. La firma toma `low_frames`
// directamente (en lugar de manejar el contador internamente) para que la
// funcion sea pura — facil de testear y sin estado oculto.
bool graph_force_layout_should_pause(int consecutive_low_frames, int min_consecutive);
