use crate::Pt;
use crate::ShaderOpts;
use crate::Text;
#[cfg(feature = "model-3d")]
pub(crate) use crate::drawable_3d::DrawCommand3D;
use crate::image_shader::ImageShaderBindings;

/// Trait for objects that can be drawn into an [`Image`][crate::Image].
pub trait Drawable {
    /// Associated options for configuring the draw call (e.g., [`DrawOption`][crate::DrawOption]).
    type Options;

    /// Renders the object into the specified target image.
    ///
    /// This is the low-level drawing interface. Users should typically call
    /// `target.draw(ctx, drawable, options)` instead.
    fn draw_to(self, ctx: &mut crate::Context, target: crate::Image, options: Self::Options);
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) struct ImageCommand {
    pub id: u32,
    pub target_texture_id: u32,
    pub opts: DrawOption,
    pub shader_id: u32,
    pub shader_opts: ShaderOpts,
    pub shader_bindings: ImageShaderBindings,
    pub size: [Pt; 2],
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) struct TextCommand {
    pub target_texture_id: u32,
    pub text: Box<Text>,
    pub opts: DrawOption,
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum DrawCommand {
    Image(Box<ImageCommand>),
    Text(Box<TextCommand>),
}

/// Controls how an image is sampled when the drawn quad is larger than one tile.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ImageRepeat {
    /// Scale the image to fill the drawn quad.
    #[default]
    Stretch,
    /// Repeat the image horizontally and vertically.
    Repeat,
    /// Repeat horizontally and keep a single tile vertically.
    RepeatX,
    /// Repeat vertically and keep a single tile horizontally.
    RepeatY,
    /// Repeat horizontally and stretch vertically.
    RepeatXStretchY,
    /// Repeat vertically and stretch horizontally.
    RepeatYStretchX,
    /// Draw one tile and leave the rest transparent.
    NoRepeat,
}

/// Unified options for drawing images and text.
///
/// Controls the position, rotation, and scale of drawn items.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DrawOption {
    /// Position in logical units relative to the target's top-left corner.
    position: [Pt; 2],
    /// Rotation in radians.
    rotation: f32,
    /// Scale factors (x, y). Applied after size.
    scale: [f32; 2],
    opacity: f32,
    repeat: ImageRepeat,
    tile_size: Option<[Pt; 2]>,
}

impl Default for DrawOption {
    fn default() -> Self {
        Self {
            position: [Pt(0.0), Pt(0.0)],
            scale: [1.0, 1.0],
            rotation: 0.0,
            opacity: 1.0,
            repeat: ImageRepeat::Stretch,
            tile_size: None,
        }
    }
}

impl DrawOption {
    /// Creates a new DrawOption with position, rotation, and scale.
    pub fn new(position: [Pt; 2], rotation: f32, scale: [f32; 2]) -> Self {
        Self {
            position,
            rotation,
            scale,
            opacity: 1.0,
            repeat: ImageRepeat::Stretch,
            tile_size: None,
        }
    }

    pub fn position(&self) -> [Pt; 2] {
        self.position
    }

    /// Sets the drawing position relative to the current target's top-left corner.
    pub fn with_position(mut self, position: [Pt; 2]) -> Self {
        self.position = position;
        self
    }

    pub fn set_position(&mut self, x: Pt, y: Pt) {
        self.position = [x, y];
    }

    pub fn rotation(&self) -> f32 {
        self.rotation
    }

    /// Sets the rotation in radians.
    pub fn with_rotation(mut self, rotation: f32) -> Self {
        self.rotation = rotation;
        self
    }

    pub fn scale(&self) -> [f32; 2] {
        self.scale
    }

    /// Sets the scale multiplier (e.g., [2.0, 2.0] for double size).
    pub fn with_scale(mut self, scale: [f32; 2]) -> Self {
        self.scale = scale;
        self
    }

    pub fn opacity(&self) -> f32 {
        self.opacity
    }

    /// Sets the opacity (alpha multiplier), from 0.0 to 1.0.
    pub fn with_opacity(mut self, opacity: f32) -> Self {
        self.opacity = opacity.clamp(0.0, 1.0);
        self
    }

    pub fn repeat(&self) -> ImageRepeat {
        self.repeat
    }

    pub fn with_repeat(mut self, repeat: ImageRepeat) -> Self {
        self.repeat = repeat;
        self
    }

    pub fn tile_size(&self) -> Option<[Pt; 2]> {
        self.tile_size
    }

    pub fn with_tile_size(mut self, tile_size: [Pt; 2]) -> Self {
        self.tile_size = Some(tile_size);
        self
    }

    pub fn clear_tile_size(mut self) -> Self {
        self.tile_size = None;
        self
    }
}
