//! Partial path / polyline / shape sampling for ShowCreation animations.
//!
//! Mirrors the standard `GetPartial::upto(ratio)` style algorithm: flatten
//! curves to a polyline by arc length, walk cumulative segment lengths, and
//! emit a prefix.

use super::*;

/// Number of linear sub-segments used to flatten each QuadTo / CubicTo curve.
/// Fixed (non-adaptive) for simplicity; 32 keeps visual error well below a pixel
/// at the world-unit scales dwanim uses.
const CURVE_SAMPLES: usize = 32;

/// Resample a polyline to keep only the prefix from 0 to `t` of total arc length.
///
/// Returns a polyline whose final vertex is interpolated within the segment that
/// straddles `t*L`. Always returns at least the first point when input is
/// non-empty and `t > 0`. `t` is clamped to `[0, 1]`.
pub fn partial_polyline(points: &[(f32, f32)], t: f32) -> Vec<(f32, f32)> {
    if points.is_empty() {
        return Vec::new();
    }
    let t = t.clamp(0.0, 1.0);
    if t <= 0.0 {
        return vec![points[0]];
    }
    if t >= 1.0 {
        return points.to_vec();
    }

    let lengths: Vec<f32> = points
        .windows(2)
        .map(|w| {
            let dx = w[1].0 - w[0].0;
            let dy = w[1].1 - w[0].1;
            (dx * dx + dy * dy).sqrt()
        })
        .collect();
    let total: f32 = lengths.iter().sum();
    if total <= 0.0 {
        return vec![points[0]];
    }

    let target = total * t;
    let mut acc = 0.0f32;
    let mut out = vec![points[0]];
    for (i, &seg_len) in lengths.iter().enumerate() {
        if acc + seg_len >= target {
            let remain = target - acc;
            let frac = if seg_len > 0.0 { remain / seg_len } else { 0.0 };
            let (x0, y0) = points[i];
            let (x1, y1) = points[i + 1];
            out.push((x0 + (x1 - x0) * frac, y0 + (y1 - y0) * frac));
            return out;
        }
        out.push(points[i + 1]);
        acc += seg_len;
    }
    out
}

/// Trim a `VectorPath` to its first `t` fraction by arc length.
///
/// Multi-subpath limitation: any extra `MoveTo` after the first is treated as a
/// `LineTo` so the path is one continuous polyline. Curves are flattened with
/// a fixed number of sub-segments. `Close` is dropped (open prefix).
///
/// Edge cases:
/// - `t <= 0` → path with a single `MoveTo` at the first sample (or empty if
///   the input has no anchor points).
/// - `t >= 1` → clone of input.
/// - empty path → empty output.
pub fn partial_vector_path(path: &VectorPath, t: f32) -> VectorPath {
    let t = t.clamp(0.0, 1.0);
    if t >= 1.0 {
        return path.clone();
    }
    let polyline = flatten_path(path);
    if polyline.is_empty() {
        return VectorPath {
            commands: Vec::new(),
            color: path.color,
        };
    }
    if t <= 0.0 {
        return VectorPath {
            commands: vec![PathCommand::MoveTo([polyline[0].0, polyline[0].1])],
            color: path.color,
        };
    }
    let trimmed = partial_polyline(&polyline, t);
    let mut commands = Vec::with_capacity(trimmed.len());
    for (i, &(x, y)) in trimmed.iter().enumerate() {
        if i == 0 {
            commands.push(PathCommand::MoveTo([x, y]));
        } else {
            commands.push(PathCommand::LineTo([x, y]));
        }
    }
    VectorPath {
        commands,
        color: path.color,
    }
}

fn flatten_path(path: &VectorPath) -> Vec<(f32, f32)> {
    let mut out: Vec<(f32, f32)> = Vec::new();
    let mut cur: Option<(f32, f32)> = None;
    for cmd in &path.commands {
        match cmd {
            PathCommand::MoveTo([x, y]) => {
                debug_assert!(
                    out.is_empty(),
                    "partial_vector_path: multi-subpath input is silently \
                     flattened into a single polyline; pass one subpath at a \
                     time"
                );
                let p = (*x, *y);
                out.push(p);
                cur = Some(p);
            }
            PathCommand::LineTo([x, y]) => {
                debug_assert!(
                    !out.is_empty(),
                    "VectorPath started with LineTo before any MoveTo"
                );
                let p = (*x, *y);
                if out.is_empty() {
                    // Defensive fallback for paths missing an opening MoveTo:
                    // treat the LineTo target as the implicit start as well so
                    // downstream code never sees a zero-length polyline.
                    out.push(p);
                }
                out.push(p);
                cur = Some(p);
            }
            PathCommand::QuadTo { ctrl, to } => {
                let start = cur.unwrap_or_else(|| out.last().copied().unwrap_or((0.0, 0.0)));
                if out.is_empty() {
                    out.push(start);
                }
                for k in 1..=CURVE_SAMPLES {
                    let s = k as f32 / CURVE_SAMPLES as f32;
                    let x = quad_lerp(start.0, ctrl[0], to[0], s);
                    let y = quad_lerp(start.1, ctrl[1], to[1], s);
                    out.push((x, y));
                }
                cur = Some((to[0], to[1]));
            }
            PathCommand::CubicTo { ctrl1, ctrl2, to } => {
                let start = cur.unwrap_or_else(|| out.last().copied().unwrap_or((0.0, 0.0)));
                if out.is_empty() {
                    out.push(start);
                }
                for k in 1..=CURVE_SAMPLES {
                    let s = k as f32 / CURVE_SAMPLES as f32;
                    let x = cubic_lerp(start.0, ctrl1[0], ctrl2[0], to[0], s);
                    let y = cubic_lerp(start.1, ctrl1[1], ctrl2[1], to[1], s);
                    out.push((x, y));
                }
                cur = Some((to[0], to[1]));
            }
            PathCommand::Close => {}
        }
    }
    out
}

fn quad_lerp(a: f32, b: f32, c: f32, t: f32) -> f32 {
    let one = 1.0 - t;
    one * one * a + 2.0 * one * t * b + t * t * c
}

fn cubic_lerp(a: f32, b: f32, c: f32, d: f32, t: f32) -> f32 {
    let one = 1.0 - t;
    one * one * one * a + 3.0 * one * one * t * b + 3.0 * one * t * t * c + t * t * t * d
}
