use tiny_skia::Pixmap;

use crate::{
    render::{Renderer, RendererLimits},
    system::System,
    value::{Ref, Reticle, Value, Vec2},
    vm::{Exception, Vm},
};

#[derive(Debug, Clone, Copy)]
pub struct Trampoline {
    pub value: Value,
}

// NOTE: This must be kept in sync with haku.js.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Cont {
    Scribble,
    Dotter,
}

impl Trampoline {
    pub fn new(init: Value) -> Self {
        Self { value: init }
    }

    pub fn cont(&self, vm: &Vm) -> Cont {
        let Some((_, refv)) = vm.get_ref_value(self.value) else {
            return Cont::Scribble;
        };

        match refv {
            Ref::Reticle(_) => Cont::Dotter,
            _ => Cont::Scribble,
        }
    }

    pub fn scribble(
        &mut self,
        vm: &Vm,
        pixmap: &mut Pixmap,
        translation: Vec2,
        limits: &RendererLimits,
    ) -> Result<(), Exception> {
        let mut renderer = Renderer::new(&mut *pixmap, limits);
        renderer.translate(translation.x, translation.y);
        renderer.render(vm, self.value)
    }

    pub fn dotter(
        &mut self,
        vm: &mut Vm,
        system: &System,
        from: Vec2,
        to: Vec2,
        num: f32,
    ) -> Result<(), Exception> {
        let (_, vref) = vm.get_ref_value(self.value).expect("value must be a ref");
        let &Ref::Reticle(Reticle::Dotter {
            draw: Value::Ref(draw_id),
        }) = vref
        else {
            panic!("value must be a dotter reticle");
        };

        let dotter = vm.create_ref(Ref::Closure(system.create_dotter(
            from.into(),
            to.into(),
            num,
        )))?;

        let value = vm.run(system, draw_id, &[Value::Ref(dotter)])?;
        self.value = value;

        // Prevent event handling weirdness on the frontend by disallowing continuing dotters
        // with anything but scribbles.
        if self.cont(vm) != Cont::Scribble {
            return Err(vm.create_exception("a dotter must be continued with a scribble"));
        }

        Ok(())
    }
}
