//! Standalone wgpu SDF raymarcher.
//!
//! Opens a window, creates a GPU pipeline from a WGSL shader string,
//! and renders with orbit camera controls. Used by the CLI `view` command.

pub mod pipeline;
pub mod camera;

use anyhow::Result;
use camera::Camera;
use pipeline::RenderPipeline;
use std::sync::Arc;
use winit::application::ApplicationHandler;
use winit::dpi::PhysicalSize;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowId};

pub fn run(wgsl_source: String, bounding_radius: f64) -> Result<()> {
    let event_loop = EventLoop::new()?;
    let mut app = App {
        state: None,
        wgsl_source,
        bounding_radius,
    };
    event_loop.run_app(&mut app)?;
    Ok(())
}

struct App {
    state: Option<RenderState>,
    wgsl_source: String,
    bounding_radius: f64,
}

struct RenderState {
    window: Arc<Window>,
    surface: wgpu::Surface<'static>,
    device: wgpu::Device,
    queue: wgpu::Queue,
    config: wgpu::SurfaceConfiguration,
    pipeline: RenderPipeline,
    camera: Camera,
    mouse_state: MouseState,
    start_time: std::time::Instant,
}

#[derive(Default)]
struct MouseState {
    dragging: bool,
    last_x: f64,
    last_y: f64,
}

impl ApplicationHandler for App {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        if self.state.is_some() {
            return;
        }

        let attrs = Window::default_attributes()
            .with_title("Cord")
            .with_inner_size(PhysicalSize::new(1280u32, 720));
        let window = Arc::new(event_loop.create_window(attrs).unwrap());

        let state = pollster::block_on(init_render_state(
            window,
            &self.wgsl_source,
            self.bounding_radius,
        ));

        match state {
            Ok(s) => self.state = Some(s),
            Err(e) => {
                eprintln!("render init failed: {e}");
                event_loop.exit();
            }
        }
    }

    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
        let Some(state) = &mut self.state else { return };

        match event {
            WindowEvent::CloseRequested => event_loop.exit(),

            WindowEvent::Resized(size) => {
                if size.width > 0 && size.height > 0 {
                    state.config.width = size.width;
                    state.config.height = size.height;
                    state.surface.configure(&state.device, &state.config);
                    state.window.request_redraw();
                }
            }

            WindowEvent::MouseInput { state: btn_state, button, .. } => {
                if button == winit::event::MouseButton::Left {
                    state.mouse_state.dragging = btn_state == winit::event::ElementState::Pressed;
                }
            }

            WindowEvent::CursorMoved { position, .. } => {
                if state.mouse_state.dragging {
                    let dx = position.x - state.mouse_state.last_x;
                    let dy = position.y - state.mouse_state.last_y;
                    state.camera.orbit(dx as f32 * 0.005, dy as f32 * 0.005);
                    state.window.request_redraw();
                }
                state.mouse_state.last_x = position.x;
                state.mouse_state.last_y = position.y;
            }

            WindowEvent::MouseWheel { delta, .. } => {
                let scroll = match delta {
                    winit::event::MouseScrollDelta::LineDelta(_, y) => y,
                    winit::event::MouseScrollDelta::PixelDelta(p) => p.y as f32 * 0.01,
                };
                state.camera.zoom(scroll);
                state.window.request_redraw();
            }

            WindowEvent::RedrawRequested => {
                let output = match state.surface.get_current_texture() {
                    Ok(t) => t,
                    Err(wgpu::SurfaceError::Lost) => {
                        state.surface.configure(&state.device, &state.config);
                        return;
                    }
                    Err(e) => {
                        eprintln!("surface error: {e}");
                        return;
                    }
                };
                let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());

                let elapsed = state.start_time.elapsed().as_secs_f32();
                state.pipeline.update_uniforms(
                    &state.queue,
                    state.config.width,
                    state.config.height,
                    elapsed,
                    &state.camera,
                );

                let mut encoder = state.device.create_command_encoder(
                    &wgpu::CommandEncoderDescriptor { label: Some("render") },
                );
                {
                    let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                        label: Some("main"),
                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                            view: &view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
                                store: wgpu::StoreOp::Store,
                            },
                        })],
                        depth_stencil_attachment: None,
                        ..Default::default()
                    });
                    state.pipeline.draw(&mut pass);
                }
                state.queue.submit(std::iter::once(encoder.finish()));
                output.present();
            }

            _ => {}
        }
    }
}

async fn init_render_state(
    window: Arc<Window>,
    wgsl_source: &str,
    bounding_radius: f64,
) -> Result<RenderState> {
    let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
        backends: wgpu::Backends::all(),
        ..Default::default()
    });

    let surface = instance.create_surface(window.clone())?;

    let adapter = instance
        .request_adapter(&wgpu::RequestAdapterOptions {
            power_preference: wgpu::PowerPreference::HighPerformance,
            compatible_surface: Some(&surface),
            force_fallback_adapter: false,
        })
        .await
        .ok_or_else(|| anyhow::anyhow!("no suitable GPU adapter"))?;

    let (device, queue) = adapter
        .request_device(&wgpu::DeviceDescriptor {
            label: Some("cord"),
            ..Default::default()
        }, None)
        .await?;

    let size = window.inner_size();
    let caps = surface.get_capabilities(&adapter);
    let format = caps.formats.iter()
        .find(|f| f.is_srgb())
        .copied()
        .unwrap_or(caps.formats[0]);

    let config = wgpu::SurfaceConfiguration {
        usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
        format,
        width: size.width.max(1),
        height: size.height.max(1),
        present_mode: wgpu::PresentMode::Fifo,
        alpha_mode: caps.alpha_modes[0],
        view_formats: vec![],
        desired_maximum_frame_latency: 2,
    };
    surface.configure(&device, &config);

    let scene_scale = (bounding_radius as f32).max(0.1);
    let pipeline = RenderPipeline::new(&device, format, wgsl_source, scene_scale)?;
    let camera = Camera::new(scene_scale);

    Ok(RenderState {
        window,
        surface,
        device,
        queue,
        config,
        pipeline,
        camera,
        mouse_state: MouseState::default(),
        start_time: std::time::Instant::now(),
    })
}
