use crate::graphics::core::Graphics;

#[cfg(all(not(target_arch = "wasm32"), target_os = "android"))]
#[allow(dead_code)]
pub(crate) const PREFERRED_WGPU_BACKENDS: &[wgpu::Backends] = &[wgpu::Backends::PRIMARY];

#[cfg(not(all(not(target_arch = "wasm32"), target_os = "android")))]
#[allow(dead_code)]
pub(crate) const PREFERRED_WGPU_BACKENDS: &[wgpu::Backends] = &[wgpu::Backends::PRIMARY];

#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
use wasm_bindgen_futures;

#[cfg(not(target_arch = "wasm32"))]
use std::future::Future;
#[cfg(not(target_arch = "wasm32"))]
use std::pin::Pin;
#[cfg(not(target_arch = "wasm32"))]
use std::task::{Context as TaskContext, Poll, RawWaker, RawWakerVTable, Waker};

pub(crate) enum GraphicsInitState {
    NotStarted,
    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
    Pending,
    Ready(Box<Option<Graphics>>),
    Failed,
}

pub(crate) fn finalize_graphics(init_state: &mut GraphicsInitState) -> Option<Graphics> {
    let GraphicsInitState::Ready(slot) = init_state else {
        return None;
    };
    let graphics = slot.take()?;
    eprintln!("[spot][platform] Finalizing graphics...");
    Some(graphics)
}

#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn begin_graphics_init(
    init_state: &mut GraphicsInitState,
    instance: &wgpu::Instance,
    surface: &wgpu::Surface<'static>,
    width: u32,
    height: u32,
    transparent: bool,
) {
    match init_state {
        GraphicsInitState::NotStarted => {}
        GraphicsInitState::Ready(_) | GraphicsInitState::Failed => return,
    }

    let graphics_r = block_on(Graphics::new(instance, surface, width, height, transparent));
    match graphics_r {
        Ok(graphics) => *init_state = GraphicsInitState::Ready(Box::new(Some(graphics))),
        Err(e) => {
            eprintln!("[spot][init] Graphics::new failed: {:?}", e);
            *init_state = GraphicsInitState::Failed;
        }
    }
}

#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub(crate) fn begin_graphics_init(
    init_state: &mut GraphicsInitState,
    instance: wgpu::Instance,
    surface_ptr: *const wgpu::Surface<'static>,
    width: u32,
    height: u32,
    transparent: bool,
    callback: Box<dyn FnOnce(anyhow::Result<Graphics>)>,
) {
    match init_state {
        GraphicsInitState::NotStarted => {}
        GraphicsInitState::Pending | GraphicsInitState::Ready(_) | GraphicsInitState::Failed => {
            return;
        }
    }

    *init_state = GraphicsInitState::Pending;
    spawn_graphics_init(instance, surface_ptr, width, height, transparent, callback);
}

#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn block_on<F: Future>(mut future: F) -> F::Output {
    fn noop_raw_waker() -> RawWaker {
        fn clone(_: *const ()) -> RawWaker {
            noop_raw_waker()
        }
        fn wake(_: *const ()) {}
        fn wake_by_ref(_: *const ()) {}
        fn drop(_: *const ()) {}

        static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
        RawWaker::new(std::ptr::null(), &VTABLE)
    }

    let waker = unsafe { Waker::from_raw(noop_raw_waker()) };
    let mut cx = TaskContext::from_waker(&waker);
    let mut future = unsafe { Pin::new_unchecked(&mut future) };
    loop {
        match future.as_mut().poll(&mut cx) {
            Poll::Ready(v) => return v,
            Poll::Pending => std::thread::yield_now(),
        }
    }
}

pub(crate) fn create_wgpu_instance() -> wgpu::Instance {
    #[cfg(all(not(target_arch = "wasm32"), target_os = "android"))]
    {
        wgpu::Instance::new(&wgpu::InstanceDescriptor {
            backends: wgpu::Backends::GL,
            ..Default::default()
        })
    }
    #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
    {
        wgpu::Instance::default()
    }

    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
    {
        wgpu::Instance::new(&wgpu::InstanceDescriptor {
            backends: wgpu::Backends::all(),
            ..Default::default()
        })
    }
}

#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub(crate) fn spawn_graphics_init(
    instance: wgpu::Instance,
    surface_ptr: *const wgpu::Surface<'static>,
    width: u32,
    height: u32,
    transparent: bool,
    callback: Box<dyn FnOnce(anyhow::Result<Graphics>)>,
) {
    wasm_bindgen_futures::spawn_local(async move {
        let surface = unsafe { &*surface_ptr };
        let r = Graphics::new(&instance, surface, width, height, transparent).await;
        callback(r);
    });
}

#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub(crate) fn try_resume_audio(ctx: &mut crate::Context) {
    if let Some(a) = ctx.runtime.audio.as_mut() {
        a.try_resume();
    }
}

pub(crate) fn align_write_texture_bytes(
    bytes_per_row: u32,
    height: u32,
    data: Vec<u8>,
) -> (Vec<u8>, u32) {
    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
    {
        let align = 256u32;
        let padded = bytes_per_row.div_ceil(align) * align;
        if padded == bytes_per_row {
            return (data, bytes_per_row);
        }

        let mut out = vec![0u8; (padded * height) as usize];
        for row in 0..height {
            let src_off = (row * bytes_per_row) as usize;
            let dst_off = (row * padded) as usize;
            out[dst_off..dst_off + bytes_per_row as usize]
                .copy_from_slice(&data[src_off..src_off + bytes_per_row as usize]);
        }
        (out, padded)
    }

    #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
    {
        let _ = height;
        (data, bytes_per_row)
    }
}

pub(crate) fn surface_usage(surface_caps: &wgpu::SurfaceCapabilities) -> wgpu::TextureUsages {
    #[cfg(target_os = "android")]
    {
        let _ = surface_caps;
        wgpu::TextureUsages::RENDER_ATTACHMENT
    }

    #[cfg(not(target_os = "android"))]
    {
        let desired_usage = wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST;
        if surface_caps.usages.contains(desired_usage) {
            desired_usage
        } else {
            wgpu::TextureUsages::RENDER_ATTACHMENT
        }
    }
}
