//! Stage 8 example: main menu + settings panel built with the engine's UI
//! system.
//!
//! Run with:
//!   cargo run -p oxide-examples --bin ui_menu
//!
//! Walks the full Stage-8 pipeline:
//!
//! - **Pieces 1–2** — widget tree, layout, themed visual style.
//! - **Piece 3** — text shaping + glyph atlas (loads a system font).
//! - **Piece 4a** — screen-space `UiOverlayPass` renders quads + glyphs.
//! - **Piece 5** — `Router` hit-tests cursor + tracks hover/press.
//! - **Piece 6** — immediate-mode `frame.clicked_left(...)` queries + typed
//!   `WidgetValue`s for the volume slider and invert-Y checkbox.
//!
//! Main menu has Play (logs), Settings (navigates), Quit (exits). The
//! settings panel has a draggable volume slider, a clickable invert-Y
//! checkbox, and a Back button.

#![deny(warnings)]

use oxide_engine::prelude::*;
use oxide_engine::ui::text::{common_system_font_paths, Font};
use oxide_engine::window::event::{ElementState, Key, NamedKey, WindowEvent};
use oxide_engine::window::RenderCtx;
use oxide_engine::winit::event::MouseButton;

#[derive(Clone, Copy, PartialEq)]
enum Screen {
    Main,
    Settings,
}

struct UiMenu {
    screen: Screen,
    volume: f32,
    invert_y: bool,
    /// True while the user is dragging the volume slider — set on
    /// `Pressed("volume", Left)`, cleared on release. While true the
    /// slider tracks cursor.x clamped to the track even if the cursor
    /// drifts outside the rect (standard "drag capture" behavior).
    dragging_volume: bool,
    router: UiRouter,
    theme: UiTheme,
    main_font_descriptor: UiFontRef,
    /// Built on the first frame once the surface format is known.
    gpu: Option<GpuState>,
}

struct GpuState {
    ui_pass: UiOverlayPass,
}

impl Default for UiMenu {
    fn default() -> Self {
        Self {
            screen: Screen::Main,
            volume: 0.6,
            invert_y: false,
            dragging_volume: false,
            router: UiRouter::new(),
            theme: build_theme(),
            main_font_descriptor: UiFontRef::regular("System"),
            gpu: None,
        }
    }
}

fn build_theme() -> UiTheme {
    let descriptor = UiFontRef::regular("System");
    UiTheme::new()
        .with_default(UiVisualStyle {
            foreground: Some(Color::WHITE),
            font: Some(descriptor.clone()),
            font_size: Some(16.0),
            ..UiVisualStyle::EMPTY
        })
        .with_style(
            "panel",
            UiVisualStyle {
                background: Some(Color::rgb(0.15, 0.16, 0.18)),
                ..UiVisualStyle::EMPTY
            },
        )
        .with_style(
            "button",
            UiVisualStyle {
                background: Some(Color::rgb(0.25, 0.27, 0.30)),
                foreground: Some(Color::WHITE),
                font: Some(descriptor.clone()),
                font_size: Some(16.0),
                ..UiVisualStyle::EMPTY
            },
        )
        .with_style(
            "button-hover",
            UiVisualStyle {
                background: Some(Color::rgb(0.35, 0.37, 0.40)),
                ..UiVisualStyle::EMPTY
            },
        )
        .with_style(
            "button-primary",
            UiVisualStyle {
                background: Some(Color::rgb(0.20, 0.45, 0.80)),
                foreground: Some(Color::WHITE),
                ..UiVisualStyle::EMPTY
            },
        )
        .with_style(
            "track",
            UiVisualStyle {
                background: Some(Color::rgb(0.10, 0.11, 0.13)),
                ..UiVisualStyle::EMPTY
            },
        )
        .with_style(
            "fill",
            UiVisualStyle {
                background: Some(Color::rgb(0.30, 0.55, 0.90)),
                ..UiVisualStyle::EMPTY
            },
        )
        .with_style(
            "checkbox-on",
            UiVisualStyle {
                background: Some(Color::rgb(0.30, 0.55, 0.90)),
                ..UiVisualStyle::EMPTY
            },
        )
        .with_style(
            "checkbox-off",
            UiVisualStyle {
                background: Some(Color::rgb(0.20, 0.22, 0.25)),
                ..UiVisualStyle::EMPTY
            },
        )
}

fn button(id: &str, text: &str, hovered: bool) -> Widget {
    Widget::leaf(Vec2::new(200.0, 48.0))
        .with_id(id)
        .with_text(text)
        .with_theme_style(if hovered { "button-hover" } else { "button" })
        .with_style(UiLayoutStyle {
            width: UiSizing::Fixed(200.0),
            height: UiSizing::Fixed(48.0),
            padding: UiInsets::all(12.0),
            align_horizontal: UiAlign::Center,
            align_vertical: UiAlign::Center,
            ..Default::default()
        })
}

fn primary_button(id: &str, text: &str, hovered: bool) -> Widget {
    let mut w = button(id, text, hovered);
    if !hovered {
        w = w.with_theme_style("button-primary");
    }
    w
}

fn build_main_menu(hovered: Option<&str>) -> Widget {
    let is = |id: &str| hovered == Some(id);
    Widget::anchor()
        .with_style(UiLayoutStyle {
            width: UiSizing::Grow(1.0),
            height: UiSizing::Grow(1.0),
            ..Default::default()
        })
        .with_child(
            Widget::column()
                .with_style(UiLayoutStyle {
                    width: UiSizing::Fixed(240.0),
                    height: UiSizing::Fixed(280.0),
                    padding: UiInsets::all(20.0),
                    anchor: UiAnchor::between(Vec2::splat(0.5), Vec2::splat(0.5))
                        .with_offsets(Vec2::new(-120.0, -140.0), Vec2::new(120.0, 140.0)),
                    ..Default::default()
                })
                .with_theme_style("panel")
                .with_gap(12.0)
                .with_child(
                    Widget::leaf(Vec2::new(200.0, 32.0))
                        .with_text("Oxide")
                        .with_style(UiLayoutStyle {
                            width: UiSizing::Fixed(200.0),
                            height: UiSizing::Fixed(32.0),
                            align_horizontal: UiAlign::Center,
                            align_vertical: UiAlign::Center,
                            ..Default::default()
                        })
                        .with_visual(UiVisualStyle {
                            font_size: Some(22.0),
                            ..UiVisualStyle::EMPTY
                        }),
                )
                .with_child(primary_button("play", "Play", is("play")))
                .with_child(button("settings", "Settings", is("settings")))
                .with_child(button("quit", "Quit", is("quit"))),
        )
}

fn build_settings(volume: f32, invert_y: bool, hovered: Option<&str>) -> Widget {
    let is = |id: &str| hovered == Some(id);
    // Fill spans 0..volume of the parent's width via a percentage anchor —
    // scales with whatever the track width ends up being instead of
    // hardcoding pixels.
    let fill_fraction = volume.clamp(0.0, 1.0);
    Widget::anchor()
        .with_style(UiLayoutStyle {
            width: UiSizing::Grow(1.0),
            height: UiSizing::Grow(1.0),
            ..Default::default()
        })
        .with_child(
            Widget::column()
                .with_style(UiLayoutStyle {
                    width: UiSizing::Fixed(320.0),
                    height: UiSizing::Fixed(280.0),
                    padding: UiInsets::all(20.0),
                    anchor: UiAnchor::between(Vec2::splat(0.5), Vec2::splat(0.5))
                        .with_offsets(Vec2::new(-160.0, -140.0), Vec2::new(160.0, 140.0)),
                    ..Default::default()
                })
                .with_theme_style("panel")
                .with_gap(16.0)
                .with_child(
                    Widget::leaf(Vec2::new(200.0, 32.0))
                        .with_text("Settings")
                        .with_style(UiLayoutStyle {
                            width: UiSizing::Fixed(280.0),
                            height: UiSizing::Fixed(32.0),
                            align_horizontal: UiAlign::Center,
                            align_vertical: UiAlign::Center,
                            ..Default::default()
                        })
                        .with_visual(UiVisualStyle {
                            font_size: Some(20.0),
                            ..UiVisualStyle::EMPTY
                        }),
                )
                // Volume row: label + slider track + fill.
                .with_child(
                    Widget::column()
                        .with_style(UiLayoutStyle {
                            width: UiSizing::Fixed(280.0),
                            height: UiSizing::Fixed(48.0),
                            ..Default::default()
                        })
                        .with_gap(4.0)
                        .with_child(
                            Widget::leaf(Vec2::new(200.0, 18.0))
                                .with_text("Volume")
                                .with_style(UiLayoutStyle {
                                    width: UiSizing::Fixed(280.0),
                                    height: UiSizing::Fixed(18.0),
                                    align_vertical: UiAlign::Center,
                                    ..Default::default()
                                }),
                        )
                        .with_child(
                            Widget::anchor()
                                .with_style(UiLayoutStyle {
                                    width: UiSizing::Fixed(280.0),
                                    height: UiSizing::Fixed(20.0),
                                    ..Default::default()
                                })
                                .with_child(
                                    Widget::leaf(Vec2::ZERO)
                                        .with_id("volume")
                                        .with_value(volume)
                                        .with_theme_style("track")
                                        .with_style(UiLayoutStyle {
                                            anchor: UiAnchor::FILL,
                                            ..Default::default()
                                        }),
                                )
                                .with_child(
                                    Widget::leaf(Vec2::ZERO)
                                        .with_theme_style("fill")
                                        .with_style(UiLayoutStyle {
                                            anchor: UiAnchor::between(
                                                Vec2::ZERO,
                                                Vec2::new(fill_fraction, 1.0),
                                            ),
                                            ..Default::default()
                                        }),
                                ),
                        ),
                )
                // Invert-Y row: checkbox box (clickable) + label.
                .with_child(
                    Widget::row()
                        .with_style(UiLayoutStyle {
                            width: UiSizing::Fixed(280.0),
                            height: UiSizing::Fixed(28.0),
                            ..Default::default()
                        })
                        .with_gap(12.0)
                        .with_child(
                            Widget::leaf(Vec2::new(24.0, 24.0))
                                .with_id("invert_y")
                                .with_value(invert_y)
                                .with_theme_style(if invert_y {
                                    "checkbox-on"
                                } else {
                                    "checkbox-off"
                                })
                                .with_style(UiLayoutStyle {
                                    width: UiSizing::Fixed(24.0),
                                    height: UiSizing::Fixed(24.0),
                                    align_vertical: UiAlign::Center,
                                    ..Default::default()
                                }),
                        )
                        .with_child(
                            Widget::leaf(Vec2::new(200.0, 24.0))
                                .with_text("Invert Y axis")
                                .with_style(UiLayoutStyle {
                                    width: UiSizing::Grow(1.0),
                                    height: UiSizing::Fixed(24.0),
                                    align_vertical: UiAlign::Center,
                                    ..Default::default()
                                }),
                        ),
                )
                .with_child(button("back", "Back", is("back"))),
        )
}

impl UiMenu {
    fn current_document(&self) -> Widget {
        let hovered = self.router.hovered().map(|id| id.as_str());
        match self.screen {
            Screen::Main => build_main_menu(hovered),
            Screen::Settings => build_settings(self.volume, self.invert_y, hovered),
        }
    }
}

impl WindowApp for UiMenu {
    fn init(&mut self, ctx: &mut AppCtx<'_>) {
        ctx.set_clear_color(Color::rgb(0.08, 0.09, 0.10));
        log::info!("ui_menu: main menu → click Play/Settings/Quit (Esc quits)");
    }

    fn event(&mut self, ctx: &mut AppCtx<'_>, event: &WindowEvent) {
        if let WindowEvent::KeyboardInput { event: key, .. } = event {
            if key.state == ElementState::Pressed && key.logical_key == Key::Named(NamedKey::Escape)
            {
                ctx.request_exit();
            }
        }
    }

    fn update(&mut self, ctx: &mut AppCtx<'_>) {
        let (w, h) = ctx.size();
        let viewport =
            oxide_engine::math::Rect::from_min_size(Vec2::ZERO, Vec2::new(w as f32, h as f32));
        let document = self.current_document();
        let tree = ui_layout(&document, viewport, 1.0);
        let frame = self.router.process(&tree, ctx.input());

        match self.screen {
            Screen::Main => {
                if frame.clicked_left("play") {
                    log::info!("Play clicked (would start a game)");
                }
                if frame.clicked_left("settings") {
                    log::info!("→ settings");
                    self.screen = Screen::Settings;
                }
                if frame.clicked_left("quit") {
                    ctx.request_exit();
                }
            }
            Screen::Settings => {
                if frame.clicked_left("back") {
                    log::info!("← back to main");
                    self.screen = Screen::Main;
                }
                if frame.clicked_left("invert_y") {
                    self.invert_y = !self.invert_y;
                    log::info!("invert_y = {}", self.invert_y);
                }
                // Volume slider: drag-capture.
                // - Press on the track → start tracking.
                // - While tracking AND mouse held: update volume from
                //   cursor.x clamped to the track rect, regardless of
                //   whether the cursor is still inside it (so dragging
                //   past either edge pins to 0.0 or 1.0).
                // - On release: stop tracking.
                if frame.pressed("volume", MouseButton::Left) {
                    self.dragging_volume = true;
                }
                if !ctx.input().mouse_held(MouseButton::Left) {
                    self.dragging_volume = false;
                }
                if self.dragging_volume {
                    if let Some(cursor) = ctx.input().cursor() {
                        if let Some(node) = tree.find(&"volume".into()) {
                            let new_v = ((cursor.x - node.rect.min.x) / node.rect.width().max(1.0))
                                .clamp(0.0, 1.0);
                            if (new_v - self.volume).abs() > 0.001 {
                                self.volume = new_v;
                                log::debug!("volume = {:.2}", self.volume);
                            }
                        }
                    }
                }
            }
        }
    }

    fn render(&mut self, ctx: &RenderCtx<'_>) {
        let device = ctx.gpu.device();
        let queue = ctx.gpu.queue();

        // Lazy-init GPU resources once the surface format is known.
        if self.gpu.is_none() {
            let mut ui_pass = UiOverlayPass::new(device, ctx.surface_format);
            // Load a system font and register it under the same descriptor
            // the theme uses.
            let font = common_system_font_paths()
                .iter()
                .find_map(|p| Font::from_path(p).ok());
            match font {
                Some(f) => {
                    ui_pass
                        .fonts_mut()
                        .insert_with_descriptor(self.main_font_descriptor.clone(), f);
                }
                None => {
                    log::error!(
                        "No system sans-serif font found in any of {:?}. Install \
                         'liberation-fonts' or 'dejavu-sans' and re-run.",
                        common_system_font_paths()
                    );
                }
            }
            self.gpu = Some(GpuState { ui_pass });
        }
        // Build the document + tree + painted frame before taking the
        // mutable borrow on the pass, so self.theme + self.current_document
        // (which need &self) and gpu.ui_pass (which needs &mut self.gpu)
        // don't clash.
        let (w, h) = ctx.size;
        let viewport =
            oxide_engine::math::Rect::from_min_size(Vec2::ZERO, Vec2::new(w as f32, h as f32));
        let document = self.current_document();
        let tree = ui_layout(&document, viewport, 1.0);
        let painted = ui_paint(
            &document,
            &tree,
            &self.theme,
            self.gpu.as_ref().unwrap().ui_pass.fonts(),
            1.0,
        );

        let gpu = self.gpu.as_mut().unwrap();
        gpu.ui_pass
            .set_batches(vec![UiBatch::screen_space(painted, (w, h))]);

        let camera = Camera::default();
        let view_transform = oxide_engine::math::Transform::default();
        let lighting = Lighting::default();
        let mut frame = FrameContext {
            device,
            queue,
            color: ctx.view,
            size: ctx.size,
            viewport_rect: None,
            clear_color: Color::rgb(0.08, 0.09, 0.10),
            camera: &camera,
            view_transform: &view_transform,
            lighting: &lighting,
            objects: &[],
        };
        // The window runner already cleared the surface to the configured
        // clear color, so we just run the UI overlay on top.
        gpu.ui_pass.run(&mut frame);
    }
}

fn main() -> anyhow::Result<()> {
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
    let config = WindowConfig {
        title: "Oxide — ui_menu".to_string(),
        width: 960,
        height: 600,
        ..Default::default()
    };
    run(config, UiMenu::default())
}
