use crate::plugin::EngineEvent;
use wasmtime::{Instance, Store, TypedFunc};

pub struct PluginInstance {
    pub id: u64,
    pub store: Store<PluginState>,
    pub instance: Instance,
    pub on_init: Option<TypedFunc<(), ()>>,
    pub on_frame: Option<TypedFunc<(), ()>>,
}

pub struct PluginState {
    pub wasi: wasmtime_wasi::p1::WasiP1Ctx,
    pub pending_commands: Vec<crate::plugin::EngineCommand>,
    pub pending_custom_commands: Vec<Vec<u8>>,
}

impl PluginState {
    pub fn new(wasi: wasmtime_wasi::p1::WasiP1Ctx) -> Self {
        Self {
            wasi,
            pending_commands: Vec::new(),
            pending_custom_commands: Vec::new(),
        }
    }

    pub fn push_custom_command(&mut self, bytes: Vec<u8>) {
        self.pending_custom_commands.push(bytes);
    }
}

pub fn send_engine_event_to_plugin(plugin: &mut PluginInstance, event: &EngineEvent) {
    let event_bytes = match event.to_bytes() {
        Ok(bytes) => bytes,
        Err(error) => {
            tracing::error!("Failed to serialize engine event: {}", error);
            return;
        }
    };

    send_bytes_to_plugin(plugin, &event_bytes, "plugin_alloc", "plugin_receive_event");
}

pub fn send_bytes_to_plugin(
    plugin: &mut PluginInstance,
    event_bytes: &[u8],
    alloc_fn: &str,
    receive_fn: &str,
) {
    let plugin_alloc = match plugin
        .instance
        .get_typed_func::<u32, u32>(&mut plugin.store, alloc_fn)
    {
        Ok(func) => func,
        Err(_) => return,
    };

    let ptr = match plugin_alloc.call(&mut plugin.store, event_bytes.len() as u32) {
        Ok(ptr) => ptr,
        Err(error) => {
            tracing::error!("Plugin {} alloc failed: {}", plugin.id, error);
            return;
        }
    };

    let memory = match plugin.instance.get_memory(&mut plugin.store, "memory") {
        Some(mem) => mem,
        None => {
            tracing::error!("Plugin {} has no memory export", plugin.id);
            return;
        }
    };

    if let Err(error) = memory.write(&mut plugin.store, ptr as usize, event_bytes) {
        tracing::error!("Plugin {} memory write failed: {}", plugin.id, error);
        return;
    }

    let receive_fn_typed = match plugin
        .instance
        .get_typed_func::<(u32, u32), ()>(&mut plugin.store, receive_fn)
    {
        Ok(func) => func,
        Err(_) => return,
    };

    if let Err(error) = receive_fn_typed.call(&mut plugin.store, (ptr, event_bytes.len() as u32)) {
        tracing::error!("Plugin {} receive_event failed: {}", plugin.id, error);
    }
}
