//! Coordinate system: center-origin. Short axis spans [-1, +1]; long axis spans
//! [-ratio, +ratio] where ratio = max(width, height) / min(width, height). All
//! shapes (Circle, Rectangle, Polygon, Line, Path) share this world-unit
//! convention and are aspect-invariant: the renderer multiplies input x by
//! scale_x = min(W,H)/W and input y by scale_y = min(W,H)/H so circles are
//! round and squares are square regardless of frame ratio.

use bytemuck::{Pod, Zeroable};

#[derive(Debug, Clone, Copy)]
pub struct VideoConfig {
    pub width: u32,
    pub height: u32,
    pub fps: u32,
    pub total_frames: u32,
}

impl VideoConfig {
    pub fn frame_byte_len(&self) -> usize {
        (self.width as usize) * (self.height as usize) * 4
    }
}

#[derive(Debug, Clone)]
pub struct RawFrame {
    pub rgba: Vec<u8>,
}

impl RawFrame {
    pub fn black(width: u32, height: u32) -> Self {
        let n = (width as usize) * (height as usize);
        let mut rgba = vec![0u8; n * 4];
        for i in 0..n {
            rgba[i * 4 + 3] = 0xff;
        }
        Self { rgba }
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)]
pub struct Color {
    pub r: f32,
    pub g: f32,
    pub b: f32,
    pub a: f32,
}

impl Color {
    pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
        Self { r, g, b, a: 1.0 }
    }
    pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
        Self { r, g, b, a }
    }
    pub const BLACK: Self = Self::rgb(0.0, 0.0, 0.0);
    pub const WHITE: Self = Self::rgb(1.0, 1.0, 1.0);
    pub const RED: Self = Self::rgb(1.0, 0.0, 0.0);

    pub fn as_array(self) -> [f32; 4] {
        [self.r, self.g, self.b, self.a]
    }
}

#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
pub struct VPoint {
    pub pos: [f32; 2],
    pub rgba: [f32; 4],
}

#[derive(Debug, Clone, Default)]
pub struct VMobject {
    pub vertices: Vec<VPoint>,
    pub indices: Vec<u32>,
}

#[derive(Debug, Clone)]
pub enum RenderItem {
    Compute(ComputePass),
    Vector(VMobject),
    Text(Text),
}

// Scene is intentionally not Send + Sync in v1: the renderer drives rendering
// on a single thread, and making Scene implementations thread-shareable would
// complicate the forthcoming Python binding (PyO3 wrappers are !Send by
// default). Revisit in Phase 2+ when the concurrency model is finalized.
pub trait Scene {
    fn config(&self) -> VideoConfig;
    fn render_error(&self) -> Option<String> {
        None
    }
    fn clear_color(&self, frame_idx: u32) -> Color;
    fn mobjects(&self, _frame_idx: u32) -> Vec<VMobject> {
        Vec::new()
    }
    fn mandelbrot(&self, _frame_idx: u32) -> Option<Mandelbrot> {
        None
    }
    fn compute_passes(&self, frame_idx: u32) -> Vec<ComputePass> {
        self.mandelbrot(frame_idx)
            .map(ComputePass::Mandelbrot)
            .into_iter()
            .collect()
    }
    fn camera_3d(&self, _frame_idx: u32) -> Option<ThreeDCamera> {
        None
    }
    fn texts(&self, _frame_idx: u32) -> Vec<Text> {
        Vec::new()
    }
    fn render_items(&self, frame_idx: u32) -> Vec<RenderItem> {
        let mut items = Vec::new();
        items.extend(
            self.compute_passes(frame_idx)
                .into_iter()
                .map(RenderItem::Compute),
        );
        items.extend(self.mobjects(frame_idx).into_iter().map(RenderItem::Vector));
        items.extend(self.texts(frame_idx).into_iter().map(RenderItem::Text));
        items
    }
}

// ---- Primitives ---------------------------------------------------------

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Circle {
    pub x: f32,
    pub y: f32,
    pub radius: f32,
    pub color: Color,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Rectangle {
    pub cx: f32,
    pub cy: f32,
    pub width: f32,
    pub height: f32,
    pub color: Color,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Line {
    pub x1: f32,
    pub y1: f32,
    pub x2: f32,
    pub y2: f32,
    pub thickness: f32,
    pub color: Color,
}

/// Anchor/origin point used to place rectangular-bounds objects.
///
/// `Center` is the default center-origin behavior: the object's position marks
/// its visual center. The corner variants interpret the object's position as
/// that corner of its rendered bounding box.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum Anchor {
    #[default]
    Center,
    TopLeft,
    TopRight,
    BottomLeft,
    BottomRight,
}

impl Anchor {
    pub fn center_from_anchor(self, x: f32, y: f32, width: f32, height: f32) -> (f32, f32) {
        match self {
            Anchor::Center => (x, y),
            Anchor::TopLeft => (x + width * 0.5, y - height * 0.5),
            Anchor::TopRight => (x - width * 0.5, y - height * 0.5),
            Anchor::BottomLeft => (x + width * 0.5, y + height * 0.5),
            Anchor::BottomRight => (x - width * 0.5, y + height * 0.5),
        }
    }

    pub fn top_left_from_anchor(self, x: f32, y: f32, width: f32, height: f32) -> (f32, f32) {
        match self {
            Anchor::Center => (x - width * 0.5, y + height * 0.5),
            Anchor::TopLeft => (x, y),
            Anchor::TopRight => (x - width, y),
            Anchor::BottomLeft => (x, y + height),
            Anchor::BottomRight => (x - width, y + height),
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct Text {
    pub cx: f32,
    pub cy: f32,
    pub size: f32,
    pub color: Color,
    pub content: String,
    pub origin: Anchor,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Mandelbrot {
    pub cx: f32,
    pub cy: f32,
    pub zoom: f32,
    pub max_iter: u32,
    pub color_a: Color,
    pub color_b: Color,
    pub alpha: f32,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Julia {
    pub c_re: f32,
    pub c_im: f32,
    pub zoom: f32,
    pub max_iter: u32,
    pub color_a: Color,
    pub color_b: Color,
    pub alpha: f32,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ComputePass {
    Mandelbrot(Mandelbrot),
    Julia(Julia),
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ThreeDCamera {
    pub yaw: f32,
    pub pitch: f32,
    pub distance: f32,
    pub focal_length: f32,
    pub scale: f32,
}

impl Default for ThreeDCamera {
    fn default() -> Self {
        Self {
            yaw: 0.7,
            pitch: 0.6,
            distance: 3.0,
            focal_length: 1.6,
            scale: 1.0,
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SphereSurface {
    pub radius: f32,
    pub color: Color,
    pub u_steps: u32,
    pub v_steps: u32,
    pub thickness: f32,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct TorusSurface {
    pub major_radius: f32,
    pub minor_radius: f32,
    pub color: Color,
    pub major_segments: u32,
    pub minor_segments: u32,
    pub thickness: f32,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Polygon {
    pub points: Vec<(f32, f32)>,
    pub color: Color,
}

#[derive(Clone, Debug, PartialEq)]
pub enum PathCommand {
    MoveTo([f32; 2]),
    LineTo([f32; 2]),
    QuadTo {
        ctrl: [f32; 2],
        to: [f32; 2],
    },
    CubicTo {
        ctrl1: [f32; 2],
        ctrl2: [f32; 2],
        to: [f32; 2],
    },
    Close,
}

#[derive(Clone, Debug, PartialEq)]
pub struct VectorPath {
    pub commands: Vec<PathCommand>,
    pub color: Color,
}

#[derive(Clone, Debug, PartialEq)]
pub enum Shape {
    Circle(Circle),
    Rect(Rectangle),
    Line(Line),
    Mandelbrot(Mandelbrot),
    Julia(Julia),
    SphereSurface(SphereSurface),
    TorusSurface(TorusSurface),
    Text(Text),
    Polygon(Polygon),
    Path(VectorPath),
    /// A stroked vector path with explicit thickness; renderer must draw a
    /// continuous stroke without per-segment caps. Color lives on the path.
    StrokedPath {
        path: VectorPath,
        thickness: f32,
    },
}

impl Shape {
    /// Scale the shape's alpha channel by `a_scale` (multiplicative, **not**
    /// idempotent). Repeated calls compound: calling `set_alpha(0.5)` twice
    /// leaves the alpha at 0.25 of the original. Animation code always
    /// operates on freshly cloned shapes to avoid this; reuse with care.
    pub fn set_alpha(&mut self, a_scale: f32) {
        match self {
            Shape::Circle(c) => c.color.a *= a_scale,
            Shape::Rect(r) => r.color.a *= a_scale,
            Shape::Line(l) => l.color.a *= a_scale,
            Shape::Mandelbrot(m) => m.alpha *= a_scale,
            Shape::Julia(j) => j.alpha *= a_scale,
            Shape::SphereSurface(s) => s.color.a *= a_scale,
            Shape::TorusSurface(s) => s.color.a *= a_scale,
            Shape::Text(t) => t.color.a *= a_scale,
            Shape::Polygon(p) => p.color.a *= a_scale,
            Shape::Path(p) => p.color.a *= a_scale,
            Shape::StrokedPath { path, .. } => path.color.a *= a_scale,
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct SceneShape {
    /// Shape ids must be unique within a scene state (`Vec<SceneShape>`).
    pub id: u64,
    pub layer: i32,
    pub shape: Shape,
}

impl SceneShape {
    pub fn new(id: u64, shape: Shape) -> Self {
        Self {
            id,
            layer: 0,
            shape,
        }
    }

    pub fn new_with_layer(id: u64, layer: i32, shape: Shape) -> Self {
        Self { id, layer, shape }
    }

    pub fn set_alpha(&mut self, a_scale: f32) {
        self.shape.set_alpha(a_scale);
    }
}

pub(crate) fn find_shape_indices(base: &[SceneShape], id: u64) -> Vec<usize> {
    base.iter()
        .enumerate()
        .filter_map(|(index, shape)| (shape.id == id).then_some(index))
        .collect()
}

mod lerp;
pub(crate) use lerp::*;

pub mod partial;
pub use partial::{partial_polyline, partial_vector_path};
