use leptos::prelude::*;
use log::*;

use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::{prelude::*, JsCast};
use web_sys::{HtmlCanvasElement, WebGl2RenderingContext as GL, WebGlProgram, WebGlShader};

// Vertex shader source
const VERTEX_SHADER_SRC: &str = r#"#version 300 es
        precision mediump float;

        void main() {
            // Define the vertices of a triangle
            const vec2 positions[3] = vec2[3](
                vec2(0.0, 0.5),
                vec2(-0.5, -0.5),
                vec2(0.5, -0.5)
            );
            
            gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
        }
        "#;

// Fragment shader source
const FRAGMENT_SHADER_SRC: &str = r#"#version 300 es
        precision mediump float;
        out vec4 fragColor;

        void main() {
            fragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
        "#;

// Implementation for WASM/browser targets
fn set_up_wgpu_triangle(canvas_id: &str) {
    let window = web_sys::window().unwrap();
    let document = window.document().unwrap();

    // Check if document is ready
    if let Some(element) = document.get_element_by_id(canvas_id) {
        let canvas = element.dyn_into::<HtmlCanvasElement>().unwrap();

        if let Err(e) = set_up_webgl(&canvas) {
            error!("WebGL setup failed: {:?}", e);
        } else {
            info!("WebGL triangle renderer initialized");
        }
    }
}

// Sets up WebGL and renders a triangle
fn set_up_webgl(canvas: &HtmlCanvasElement) -> Result<(), JsValue> {
    // Get WebGL context
    let context = canvas.get_context("webgl2")?.unwrap().dyn_into::<GL>()?;

    // Set clear color and viewport
    context.clear_color(0.1, 0.2, 0.3, 1.0);
    context.clear(GL::COLOR_BUFFER_BIT);

    // Create shaders
    let vert_shader = compile_shader(&context, GL::VERTEX_SHADER, VERTEX_SHADER_SRC)?;

    let frag_shader = compile_shader(&context, GL::FRAGMENT_SHADER, FRAGMENT_SHADER_SRC)?;

    // Create program
    let program = link_program(&context, &vert_shader, &frag_shader)?;
    context.use_program(Some(&program));

    // Create empty vertex array object (required for core profile)
    let vao = context
        .create_vertex_array()
        .ok_or("Could not create vertex array object")?;
    context.bind_vertex_array(Some(&vao));

    // Start rendering
    render_loop(context);

    Ok(())
}

// Compiles a shader from source
fn compile_shader(context: &GL, shader_type: u32, source: &str) -> Result<WebGlShader, String> {
    let shader = context
        .create_shader(shader_type)
        .ok_or_else(|| String::from("Unable to create shader object"))?;

    context.shader_source(&shader, source);
    context.compile_shader(&shader);

    if context
        .get_shader_parameter(&shader, GL::COMPILE_STATUS)
        .as_bool()
        .unwrap_or(false)
    {
        Ok(shader)
    } else {
        Err(context
            .get_shader_info_log(&shader)
            .unwrap_or_else(|| String::from("Unknown error creating shader")))
    }
}

// Links a shader program
fn link_program(
    context: &GL,
    vert_shader: &WebGlShader,
    frag_shader: &WebGlShader,
) -> Result<WebGlProgram, String> {
    let program = context
        .create_program()
        .ok_or_else(|| String::from("Unable to create shader program"))?;

    context.attach_shader(&program, vert_shader);
    context.attach_shader(&program, frag_shader);
    context.link_program(&program);

    if context
        .get_program_parameter(&program, GL::LINK_STATUS)
        .as_bool()
        .unwrap_or(false)
    {
        Ok(program)
    } else {
        Err(context
            .get_program_info_log(&program)
            .unwrap_or_else(|| String::from("Unknown error creating program")))
    }
}

// Sets up an animation loop
fn render_loop(context: GL) {
    // Create a render function
    let f = Rc::new(RefCell::new(None));
    let g = f.clone();

    *g.borrow_mut() = Some(Closure::new(move || {
        // Clear the canvas
        context.clear_color(0.1, 0.2, 0.3, 1.0);
        context.clear(GL::COLOR_BUFFER_BIT);

        // Draw the triangle (3 vertices starting at index 0)
        context.draw_arrays(GL::TRIANGLES, 0, 3);

        // Request next frame
        request_animation_frame(f.borrow().as_ref().unwrap());
    }));

    // Start the render loop
    request_animation_frame(g.borrow().as_ref().unwrap());
}

// Helper function to request animation frame
fn request_animation_frame(f: &Closure<dyn FnMut()>) {
    web_sys::window()
        .unwrap()
        .request_animation_frame(f.as_ref().unchecked_ref())
        .expect("should register `requestAnimationFrame` OK");
}

#[component]
pub fn WgpuTriangle() -> impl IntoView {
    // Use a canvas with a fixed ID
    let canvas_id = "triangle-canvas";

    // create_effect(move |_| {
    //     // This will only run client-side code
    //     if cfg!(target_arch = "wasm32") {
    //         set_up_wgpu_triangle(canvas_id);
    //     }
    // });

    view! {
        <div class="triangle-container" style="width: 100%; height: 100%">
            <canvas
                id=canvas_id
                width="800"
                height="600"
                style="width: 100%; height: 100%; border: 1px solid #ccc"
            ></canvas>
        </div>
    }
}
