//! Interactive test harness for `road_parceling`. Click empty space
//! to drop nodes; click two nodes to connect them with a road; drag
//! a node to move it (re-runs the subdivision live). Side panel
//! exposes the [`SubdivisionParams`] knobs.

#![forbid(unsafe_code)]

use eframe::egui;
use glam::DVec2;
use road_parceling::{
    apply_road_edit, subdivide_all_with_stats, NodeId, ParcelSet, RoadEdit, RoadGraph, RoadId,
    SubdivisionParams, SubdivisionStats,
};

fn main() -> eframe::Result {
    env_logger::init();
    let options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default().with_inner_size([1280.0, 800.0]),
        ..Default::default()
    };
    eframe::run_native(
        "road_parceling_studio",
        options,
        Box::new(|_cc| Ok(Box::new(Studio::default()))),
    )
}

#[derive(Clone, Copy, Debug)]
enum Pending {
    Idle,
    /// User clicked a node; the next click on a (possibly-new) node
    /// will close a road from `from` to that node.
    RoadFrom(NodeId),
    /// User is dragging a node around. We commit the move on release.
    DragNode {
        node: NodeId,
        start_pos: DVec2,
    },
}

struct Studio {
    graph: RoadGraph,
    params: SubdivisionParams,
    parcels: ParcelSet,
    stats: Option<SubdivisionStats>,
    pending: Pending,
    /// World-space center of the viewport.
    pan: egui::Vec2,
    /// Pixels per world-meter.
    zoom: f32,
    show_parcels: bool,
    show_grid: bool,
    show_node_ids: bool,
    last_error: Option<String>,
}

impl Default for Studio {
    fn default() -> Self {
        let mut s = Self {
            graph: RoadGraph::new(),
            params: SubdivisionParams::default(),
            parcels: ParcelSet::default(),
            stats: None,
            pending: Pending::Idle,
            pan: egui::Vec2::ZERO,
            zoom: 4.0,
            show_parcels: true,
            show_grid: true,
            show_node_ids: false,
            last_error: None,
        };
        s.load_preset_rectangle();
        s.recompute();
        s
    }
}

impl Studio {
    fn recompute(&mut self) {
        if let Err(e) = self.graph.rebuild_topology() {
            self.last_error = Some(format!("rebuild_topology: {e}"));
            self.parcels = ParcelSet::default();
            self.stats = None;
            return;
        }
        match subdivide_all_with_stats(&self.graph, &self.params) {
            Ok((p, s)) => {
                self.parcels = p;
                self.stats = Some(s);
                self.last_error = None;
            }
            Err(e) => {
                self.last_error = Some(format!("subdivide_all: {e}"));
                self.parcels = ParcelSet::default();
                self.stats = None;
            }
        }
    }

    fn clear(&mut self) {
        self.graph = RoadGraph::new();
        self.pending = Pending::Idle;
        self.recompute();
    }

    fn load_preset_rectangle(&mut self) {
        self.graph = RoadGraph::new();
        let a = self.graph.add_node(DVec2::new(-100.0, -50.0));
        let b = self.graph.add_node(DVec2::new(100.0, -50.0));
        let c = self.graph.add_node(DVec2::new(100.0, 50.0));
        let d = self.graph.add_node(DVec2::new(-100.0, 50.0));
        let _ = self.graph.add_road(a, b);
        let _ = self.graph.add_road(b, c);
        let _ = self.graph.add_road(c, d);
        let _ = self.graph.add_road(d, a);
        self.pending = Pending::Idle;
    }

    fn load_preset_y(&mut self) {
        self.graph = RoadGraph::new();
        let r = 100.0_f64;
        let third = std::f64::consts::TAU / 3.0;
        let center = self.graph.add_node(DVec2::new(0.0, 0.0));
        let p1 = self.graph.add_node(DVec2::new(r, 0.0));
        let p2 = self
            .graph
            .add_node(DVec2::new(r * third.cos(), r * third.sin()));
        let p3 = self.graph.add_node(DVec2::new(
            r * (2.0 * third).cos(),
            r * (2.0 * third).sin(),
        ));
        let _ = self.graph.add_road(center, p1);
        let _ = self.graph.add_road(center, p2);
        let _ = self.graph.add_road(center, p3);
        let _ = self.graph.add_road(p1, p2);
        let _ = self.graph.add_road(p2, p3);
        let _ = self.graph.add_road(p3, p1);
        self.pending = Pending::Idle;
    }

    fn world_to_screen(&self, world: DVec2, rect: egui::Rect) -> egui::Pos2 {
        let center = rect.center();
        egui::pos2(
            center.x + (world.x as f32) * self.zoom + self.pan.x,
            center.y - (world.y as f32) * self.zoom + self.pan.y,
        )
    }

    fn screen_to_world(&self, screen: egui::Pos2, rect: egui::Rect) -> DVec2 {
        let center = rect.center();
        DVec2::new(
            ((screen.x - center.x - self.pan.x) / self.zoom) as f64,
            (-(screen.y - center.y - self.pan.y) / self.zoom) as f64,
        )
    }

    fn hit_node(&self, world: DVec2, pixel_radius: f32) -> Option<NodeId> {
        let r_world = (pixel_radius / self.zoom) as f64;
        let r2 = r_world * r_world;
        let mut best: Option<(NodeId, f64)> = None;
        for (nid, p) in self.graph.nodes() {
            let d2 = (p - world).length_squared();
            if d2 <= r2 && best.is_none_or(|(_, bd)| d2 < bd) {
                best = Some((nid, d2));
            }
        }
        best.map(|(id, _)| id)
    }
}

impl eframe::App for Studio {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::SidePanel::left("controls")
            .resizable(false)
            .default_width(260.0)
            .show(ctx, |ui| {
                ui.heading("road_parceling_studio");
                ui.separator();

                ui.collapsing("Presets", |ui| {
                    if ui.button("Rectangle").clicked() {
                        self.load_preset_rectangle();
                        self.recompute();
                    }
                    if ui.button("Y intersection").clicked() {
                        self.load_preset_y();
                        self.recompute();
                    }
                    if ui.button("Clear").clicked() {
                        self.clear();
                    }
                });

                ui.separator();
                ui.collapsing("Subdivision params", |ui| {
                    let mut changed = false;
                    changed |= ui
                        .add(egui::Slider::new(&mut self.params.frontage_width, 6.0..=60.0).text("frontage R"))
                        .changed();
                    changed |= ui
                        .add(egui::Slider::new(&mut self.params.frontage_variance, 0.0..=20.0).text("frontage var"))
                        .changed();
                    changed |= ui
                        .add(egui::Slider::new(&mut self.params.depth, 6.0..=60.0).text("depth d"))
                        .changed();
                    changed |= ui
                        .add(egui::Slider::new(&mut self.params.depth_variance, 0.0..=20.0).text("depth var"))
                        .changed();
                    changed |= ui
                        .add(egui::Slider::new(&mut self.params.setback, 0.0..=10.0).text("setback"))
                        .changed();
                    changed |= ui
                        .add(egui::Slider::new(&mut self.params.min_frontage, 1.0..=20.0).text("min frontage"))
                        .changed();
                    changed |= ui
                        .add(egui::Slider::new(&mut self.params.min_area, 10.0..=200.0).text("min area"))
                        .changed();
                    changed |= ui
                        .add(egui::Slider::new(&mut self.params.regularity, 0.0..=1.0).text("regularity ρ"))
                        .changed();
                    if changed {
                        self.recompute();
                    }
                });

                ui.separator();
                ui.collapsing("View", |ui| {
                    ui.checkbox(&mut self.show_parcels, "Show parcels");
                    ui.checkbox(&mut self.show_grid, "Show grid");
                    ui.checkbox(&mut self.show_node_ids, "Show node IDs");
                    ui.add(egui::Slider::new(&mut self.zoom, 0.5..=20.0).text("zoom"));
                    if ui.button("Reset view").clicked() {
                        self.pan = egui::Vec2::ZERO;
                        self.zoom = 4.0;
                    }
                });

                ui.separator();
                ui.label("Stats:");
                if let Some(s) = &self.stats {
                    ui.label(format!("blocks: {}", s.block_count));
                    ui.label(format!("parcels: {}", s.parcel_count));
                    ui.label(format!("total: {:?}", s.total));
                    ui.label(format!("per parcel: {:.2} µs", s.time_per_parcel_us()));
                }
                if let Some(e) = &self.last_error {
                    ui.colored_label(egui::Color32::RED, e);
                }

                ui.separator();
                ui.label("Controls:");
                ui.label("• Left click empty: place node");
                ui.label("• Left click two nodes: connect");
                ui.label("• Drag node: move it");
                ui.label("• Right-drag: pan");
                ui.label("• Scroll: zoom");
            });

        egui::CentralPanel::default().show(ctx, |ui| {
            let (response, painter) = ui.allocate_painter(ui.available_size(), egui::Sense::click_and_drag());
            let rect = response.rect;

            // Background.
            painter.rect_filled(rect, 0.0, egui::Color32::from_rgb(248, 248, 248));

            // Grid.
            if self.show_grid {
                let world_step = if self.zoom >= 6.0 { 10.0 } else if self.zoom >= 2.0 { 50.0 } else { 100.0 };
                let tl_world = self.screen_to_world(rect.left_top(), rect);
                let br_world = self.screen_to_world(rect.right_bottom(), rect);
                let x0 = (tl_world.x / world_step).floor() * world_step;
                let x1 = (br_world.x / world_step).ceil() * world_step;
                let y0 = (br_world.y / world_step).floor() * world_step;
                let y1 = (tl_world.y / world_step).ceil() * world_step;
                let grid_stroke = egui::Stroke::new(0.5, egui::Color32::from_rgb(220, 220, 220));
                let mut x = x0;
                while x <= x1 {
                    let s_a = self.world_to_screen(DVec2::new(x, y0), rect);
                    let s_b = self.world_to_screen(DVec2::new(x, y1), rect);
                    painter.line_segment([s_a, s_b], grid_stroke);
                    x += world_step;
                }
                let mut y = y0;
                while y <= y1 {
                    let s_a = self.world_to_screen(DVec2::new(x0, y), rect);
                    let s_b = self.world_to_screen(DVec2::new(x1, y), rect);
                    painter.line_segment([s_a, s_b], grid_stroke);
                    y += world_step;
                }
                // Origin axes.
                let axis_stroke = egui::Stroke::new(1.0, egui::Color32::from_rgb(180, 180, 180));
                let xa = self.world_to_screen(DVec2::new(x0, 0.0), rect);
                let xb = self.world_to_screen(DVec2::new(x1, 0.0), rect);
                painter.line_segment([xa, xb], axis_stroke);
                let ya = self.world_to_screen(DVec2::new(0.0, y0), rect);
                let yb = self.world_to_screen(DVec2::new(0.0, y1), rect);
                painter.line_segment([ya, yb], axis_stroke);
            }

            // Parcels.
            if self.show_parcels {
                for (_, parcel) in self.parcels.iter() {
                    let pts: Vec<egui::Pos2> = parcel
                        .vertices()
                        .iter()
                        .map(|v| self.world_to_screen(*v, rect))
                        .collect();
                    if pts.len() >= 3 {
                        painter.add(egui::Shape::convex_polygon(
                            pts.clone(),
                            egui::Color32::from_rgba_unmultiplied(255, 240, 180, 110),
                            egui::Stroke::new(0.8, egui::Color32::from_rgb(120, 110, 80)),
                        ));
                    }
                }
            }

            // Roads.
            for (_road, a, b) in self.graph.road_endpoints() {
                let (Some(pa), Some(pb)) = (self.graph.node_position(a), self.graph.node_position(b)) else { continue };
                let s_a = self.world_to_screen(pa, rect);
                let s_b = self.world_to_screen(pb, rect);
                painter.line_segment(
                    [s_a, s_b],
                    egui::Stroke::new(2.0, egui::Color32::from_rgb(60, 90, 200)),
                );
            }

            let hover_world = response.hover_pos().map(|p| self.screen_to_world(p, rect));
            let hover_node = hover_world.and_then(|w| self.hit_node(w, 10.0));
            let nodes_snapshot: Vec<(NodeId, DVec2)> = self.graph.nodes().collect();
            for (nid, p) in &nodes_snapshot {
                let s = self.world_to_screen(*p, rect);
                let is_hover = hover_node == Some(*nid);
                let is_pending = matches!(self.pending, Pending::RoadFrom(n) if n == *nid);
                let radius = if is_hover || is_pending { 6.0 } else { 4.0 };
                let color = if is_pending {
                    egui::Color32::from_rgb(220, 30, 30)
                } else if is_hover {
                    egui::Color32::from_rgb(40, 40, 40)
                } else {
                    egui::Color32::from_rgb(20, 20, 20)
                };
                painter.circle_filled(s, radius, color);
                if self.show_node_ids {
                    painter.text(
                        s + egui::vec2(8.0, -8.0),
                        egui::Align2::LEFT_BOTTOM,
                        format!("{nid:?}"),
                        egui::FontId::proportional(10.0),
                        egui::Color32::DARK_GRAY,
                    );
                }
            }

            // Pending-road preview line.
            if let (Pending::RoadFrom(from), Some(world)) = (self.pending, hover_world) {
                if let Some(pa) = self.graph.node_position(from) {
                    let s_a = self.world_to_screen(pa, rect);
                    let s_b = self.world_to_screen(world, rect);
                    painter.line_segment(
                        [s_a, s_b],
                        egui::Stroke::new(1.5, egui::Color32::from_rgb(180, 60, 60)),
                    );
                }
            }

            // ---- Input handling ----
            // Right-drag pan.
            if response.dragged_by(egui::PointerButton::Secondary) {
                self.pan += response.drag_delta();
            }
            // Scroll zoom (zoom toward cursor).
            let scroll = ctx.input(|i| i.raw_scroll_delta.y);
            if scroll.abs() > 0.0 {
                if let Some(cursor) = response.hover_pos() {
                    let world_before = self.screen_to_world(cursor, rect);
                    let factor = (scroll * 0.005).exp();
                    self.zoom = (self.zoom * factor).clamp(0.5, 30.0);
                    let world_after = self.screen_to_world(cursor, rect);
                    let delta = world_after - world_before;
                    self.pan += egui::vec2(
                        (delta.x as f32) * self.zoom,
                        -(delta.y as f32) * self.zoom,
                    );
                }
            }

            // Drag start: if cursor is on a node, begin drag.
            if response.drag_started_by(egui::PointerButton::Primary) {
                if let Some(cursor) = response.interact_pointer_pos() {
                    let world = self.screen_to_world(cursor, rect);
                    if let Some(nid) = self.hit_node(world, 10.0) {
                        if let Some(start) = self.graph.node_position(nid) {
                            self.pending = Pending::DragNode {
                                node: nid,
                                start_pos: start,
                            };
                        }
                    }
                }
            }

            // While dragging a node, follow the cursor.
            if let Pending::DragNode { node, start_pos } = self.pending {
                if response.dragged_by(egui::PointerButton::Primary) {
                    if let Some(cursor) = response.interact_pointer_pos() {
                        let world = self.screen_to_world(cursor, rect);
                        let edit = RoadEdit::MoveNode { node, to: world };
                        match apply_road_edit(&mut self.parcels, &mut self.graph, edit, &self.params) {
                            Ok(_outcome) => {
                                self.last_error = None;
                            }
                            Err(_e) => {
                                // Drag move rejected (would create
                                // self-intersection or planarity
                                // violation). Snap back to the start
                                // and abort the drag.
                                let _ = apply_road_edit(
                                    &mut self.parcels,
                                    &mut self.graph,
                                    RoadEdit::MoveNode {
                                        node,
                                        to: start_pos,
                                    },
                                    &self.params,
                                );
                                self.pending = Pending::Idle;
                            }
                        }
                        // Stats need a fresh subdivide_all_with_stats
                        // since apply_road_edit doesn't return them.
                        if let Ok((_, s)) = subdivide_all_with_stats(&self.graph, &self.params) {
                            self.stats = Some(s);
                        }
                    }
                }
                if response.drag_stopped_by(egui::PointerButton::Primary) {
                    self.pending = Pending::Idle;
                }
            } else if response.clicked_by(egui::PointerButton::Primary) {
                if let Some(cursor) = response.interact_pointer_pos() {
                    let world = self.screen_to_world(cursor, rect);
                    let hit = self.hit_node(world, 10.0);
                    match (self.pending, hit) {
                        (Pending::RoadFrom(from), Some(to)) if from != to => {
                            match self.graph.add_road(from, to) {
                                Ok(_) => self.last_error = None,
                                Err(e) => self.last_error = Some(format!("add_road: {e}")),
                            }
                            self.pending = Pending::Idle;
                            self.recompute();
                        }
                        (Pending::RoadFrom(from), None) => {
                            let new_node = self.graph.add_node(world);
                            match self.graph.add_road(from, new_node) {
                                Ok(_) => self.last_error = None,
                                Err(e) => self.last_error = Some(format!("add_road: {e}")),
                            }
                            self.pending = Pending::Idle;
                            self.recompute();
                        }
                        (Pending::Idle, Some(node)) => {
                            self.pending = Pending::RoadFrom(node);
                        }
                        (Pending::Idle, None) => {
                            let new_node = self.graph.add_node(world);
                            self.pending = Pending::RoadFrom(new_node);
                            self.recompute();
                        }
                        _ => {}
                    }
                }
            }

            // Right-click: cancel pending road.
            if response.clicked_by(egui::PointerButton::Secondary) {
                self.pending = Pending::Idle;
            }

            // ESC also cancels.
            if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
                self.pending = Pending::Idle;
            }
        });
    }
}

// Silence the dead-code warning for RoadId import (kept for future
// road-deletion mode).
#[allow(dead_code)]
fn _phantom_road(_: RoadId) {}
