use super::*;

pub(crate) fn lerp_polygon(a: &Polygon, b: &Polygon, t: f32) -> Polygon {
    let count = a.points.len().max(b.points.len()).max(3);
    let lhs = expand_polygon_vertices(&a.points, count);
    let rhs = expand_polygon_vertices(&b.points, count);
    let points = lhs
        .iter()
        .zip(rhs.iter())
        .map(|(&(ax, ay), &(bx, by))| (ax + (bx - ax) * t, ay + (by - ay) * t))
        .collect();
    Polygon {
        points,
        color: lerp_color(a.color, b.color, t),
    }
}

fn expand_polygon_vertices(points: &[(f32, f32)], count: usize) -> Vec<(f32, f32)> {
    debug_assert!(count >= points.len());
    let mut expanded = points.to_vec();
    // Empty inputs collapse to an origin-anchored degenerate polygon rather
    // than panicking. Callers that care about validity should check vertex
    // counts upstream (typically at the Python binding boundary).
    let repeat = points.last().copied().unwrap_or((0.0, 0.0));
    expanded.resize(count, repeat);
    expanded
}

fn lerp_pair(a: [f32; 2], b: [f32; 2], t: f32) -> [f32; 2] {
    [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]
}

pub(crate) fn lerp_path(a: &VectorPath, b: &VectorPath, t: f32) -> VectorPath {
    if a.commands.len() != b.commands.len() {
        return if t >= 0.5 { b.clone() } else { a.clone() };
    }

    let mut commands = Vec::with_capacity(a.commands.len());
    for (lhs, rhs) in a.commands.iter().zip(b.commands.iter()) {
        let command = match (lhs, rhs) {
            (PathCommand::MoveTo(a), PathCommand::MoveTo(b)) => {
                PathCommand::MoveTo(lerp_pair(*a, *b, t))
            }
            (PathCommand::LineTo(a), PathCommand::LineTo(b)) => {
                PathCommand::LineTo(lerp_pair(*a, *b, t))
            }
            (
                PathCommand::QuadTo {
                    ctrl: a_ctrl,
                    to: a_to,
                },
                PathCommand::QuadTo {
                    ctrl: b_ctrl,
                    to: b_to,
                },
            ) => PathCommand::QuadTo {
                ctrl: lerp_pair(*a_ctrl, *b_ctrl, t),
                to: lerp_pair(*a_to, *b_to, t),
            },
            (
                PathCommand::CubicTo {
                    ctrl1: a_ctrl1,
                    ctrl2: a_ctrl2,
                    to: a_to,
                },
                PathCommand::CubicTo {
                    ctrl1: b_ctrl1,
                    ctrl2: b_ctrl2,
                    to: b_to,
                },
            ) => PathCommand::CubicTo {
                ctrl1: lerp_pair(*a_ctrl1, *b_ctrl1, t),
                ctrl2: lerp_pair(*a_ctrl2, *b_ctrl2, t),
                to: lerp_pair(*a_to, *b_to, t),
            },
            (PathCommand::Close, PathCommand::Close) => PathCommand::Close,
            _ => return if t >= 0.5 { b.clone() } else { a.clone() },
        };
        commands.push(command);
    }

    VectorPath {
        commands,
        color: lerp_color(a.color, b.color, t),
    }
}

pub(crate) fn lerp_color(a: Color, b: Color, t: f32) -> Color {
    Color::rgba(
        a.r + (b.r - a.r) * t,
        a.g + (b.g - a.g) * t,
        a.b + (b.b - a.b) * t,
        a.a + (b.a - a.a) * t,
    )
}

pub(crate) fn lerp_circle(a: Circle, b: Circle, t: f32) -> Circle {
    Circle {
        x: a.x + (b.x - a.x) * t,
        y: a.y + (b.y - a.y) * t,
        radius: a.radius + (b.radius - a.radius) * t,
        color: lerp_color(a.color, b.color, t),
    }
}

pub(crate) fn lerp_rect(a: Rectangle, b: Rectangle, t: f32) -> Rectangle {
    Rectangle {
        cx: a.cx + (b.cx - a.cx) * t,
        cy: a.cy + (b.cy - a.cy) * t,
        width: a.width + (b.width - a.width) * t,
        height: a.height + (b.height - a.height) * t,
        color: lerp_color(a.color, b.color, t),
    }
}

pub(crate) fn lerp_line(a: Line, b: Line, t: f32) -> Line {
    Line {
        x1: a.x1 + (b.x1 - a.x1) * t,
        y1: a.y1 + (b.y1 - a.y1) * t,
        x2: a.x2 + (b.x2 - a.x2) * t,
        y2: a.y2 + (b.y2 - a.y2) * t,
        thickness: a.thickness + (b.thickness - a.thickness) * t,
        color: lerp_color(a.color, b.color, t),
    }
}

pub(crate) fn lerp_mandelbrot(a: Mandelbrot, b: Mandelbrot, t: f32) -> Mandelbrot {
    Mandelbrot {
        cx: a.cx + (b.cx - a.cx) * t,
        cy: a.cy + (b.cy - a.cy) * t,
        zoom: a.zoom + (b.zoom - a.zoom) * t,
        max_iter: (a.max_iter as f32 + (b.max_iter as f32 - a.max_iter as f32) * t) as u32,
        color_a: lerp_color(a.color_a, b.color_a, t),
        color_b: lerp_color(a.color_b, b.color_b, t),
        alpha: a.alpha + (b.alpha - a.alpha) * t,
    }
}

pub(crate) fn lerp_julia(a: Julia, b: Julia, t: f32) -> Julia {
    Julia {
        c_re: a.c_re + (b.c_re - a.c_re) * t,
        c_im: a.c_im + (b.c_im - a.c_im) * t,
        zoom: a.zoom + (b.zoom - a.zoom) * t,
        max_iter: (a.max_iter as f32 + (b.max_iter as f32 - a.max_iter as f32) * t) as u32,
        color_a: lerp_color(a.color_a, b.color_a, t),
        color_b: lerp_color(a.color_b, b.color_b, t),
        alpha: a.alpha + (b.alpha - a.alpha) * t,
    }
}

pub(crate) fn lerp_sphere_surface(a: SphereSurface, b: SphereSurface, t: f32) -> SphereSurface {
    SphereSurface {
        radius: a.radius + (b.radius - a.radius) * t,
        color: lerp_color(a.color, b.color, t),
        u_steps: if t >= 0.5 { b.u_steps } else { a.u_steps },
        v_steps: if t >= 0.5 { b.v_steps } else { a.v_steps },
        thickness: a.thickness + (b.thickness - a.thickness) * t,
    }
}

pub(crate) fn lerp_torus_surface(a: TorusSurface, b: TorusSurface, t: f32) -> TorusSurface {
    TorusSurface {
        major_radius: a.major_radius + (b.major_radius - a.major_radius) * t,
        minor_radius: a.minor_radius + (b.minor_radius - a.minor_radius) * t,
        color: lerp_color(a.color, b.color, t),
        major_segments: if t >= 0.5 {
            b.major_segments
        } else {
            a.major_segments
        },
        minor_segments: if t >= 0.5 {
            b.minor_segments
        } else {
            a.minor_segments
        },
        thickness: a.thickness + (b.thickness - a.thickness) * t,
    }
}

pub(crate) fn lerp_text(a: &Text, b: &Text, t: f32) -> Text {
    if a.content == b.content {
        Text {
            cx: a.cx + (b.cx - a.cx) * t,
            cy: a.cy + (b.cy - a.cy) * t,
            size: a.size + (b.size - a.size) * t,
            color: lerp_color(a.color, b.color, t),
            content: a.content.clone(),
            origin: if t >= 0.5 { b.origin } else { a.origin },
        }
    } else if t >= 0.5 {
        b.clone()
    } else {
        a.clone()
    }
}

/// Interpolate two shapes. Same variant: field-lerp. Cross-variant: snap at t=0.5.
pub fn lerp_shape(a: &Shape, b: &Shape, t: f32) -> Shape {
    match (a, b) {
        (Shape::Circle(x), Shape::Circle(y)) => Shape::Circle(lerp_circle(*x, *y, t)),
        (Shape::Rect(x), Shape::Rect(y)) => Shape::Rect(lerp_rect(*x, *y, t)),
        (Shape::Line(x), Shape::Line(y)) => Shape::Line(lerp_line(*x, *y, t)),
        (Shape::Mandelbrot(x), Shape::Mandelbrot(y)) => {
            Shape::Mandelbrot(lerp_mandelbrot(*x, *y, t))
        }
        (Shape::Julia(x), Shape::Julia(y)) => Shape::Julia(lerp_julia(*x, *y, t)),
        (Shape::SphereSurface(x), Shape::SphereSurface(y)) => {
            Shape::SphereSurface(lerp_sphere_surface(*x, *y, t))
        }
        (Shape::TorusSurface(x), Shape::TorusSurface(y)) => {
            Shape::TorusSurface(lerp_torus_surface(*x, *y, t))
        }
        (Shape::Text(x), Shape::Text(y)) => Shape::Text(lerp_text(x, y, t)),
        (Shape::Polygon(x), Shape::Polygon(y)) => Shape::Polygon(lerp_polygon(x, y, t)),
        (Shape::Path(x), Shape::Path(y)) => Shape::Path(lerp_path(x, y, t)),
        (
            Shape::StrokedPath {
                path: x,
                thickness: tx,
            },
            Shape::StrokedPath {
                path: y,
                thickness: ty,
            },
        ) => Shape::StrokedPath {
            path: lerp_path(x, y, t),
            thickness: tx + (ty - tx) * t,
        },
        (_, _) => {
            if t >= 0.5 {
                b.clone()
            } else {
                a.clone()
            }
        }
    }
}
