use crate::editor::RUSTERIX;
use crate::hud::{Hud, HudMode};
use crate::prelude::*;
use MapEvent::*;
use ToolEvent::*;
use rusterix::prelude::*;
use std::time::{Duration, Instant};
use vek::{Vec2, Vec3};

const LIVE_PAINT_SYNC_INTERVAL: Duration = Duration::from_millis(75);

pub struct RectTool {
    id: TheId,

    hovered_vertices: Option<[Vec2<f32>; 4]>,
    mode: i32,
    hud: Hud,

    processed: FxHashSet<RectPaintKey>,

    stroke_active: bool,
    stroke_changed: bool,
    stroke_prev_map: Option<Map>,
    stroke_work_map: Option<Map>,
    last_2d_cell: Option<Vec2<i32>>,
    line_start_2d_cell: Option<Vec2<i32>>,
    line_axis_horizontal: Option<bool>,
    last_live_scene_sync_at: Option<Instant>,
    stroke_dirty_chunks: FxHashSet<(i32, i32)>,
}

#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
enum RectPaintKey {
    Cell(i32, i32),
    Terrain(i32, i32),
    Sector(u32, i32, i32),
    Geometry(Uuid, usize, i32, i32),
}

impl Tool for RectTool {
    fn new() -> Self
    where
        Self: Sized,
    {
        Self {
            id: TheId::named("Rect Tool"),

            hovered_vertices: None,
            mode: 0,
            hud: Hud::new(HudMode::Rect),

            processed: FxHashSet::default(),

            stroke_active: false,
            stroke_changed: false,
            stroke_prev_map: None,
            stroke_work_map: None,
            last_2d_cell: None,
            line_start_2d_cell: None,
            line_axis_horizontal: None,
            last_live_scene_sync_at: None,
            stroke_dirty_chunks: FxHashSet::default(),
        }
    }

    fn id(&self) -> TheId {
        self.id.clone()
    }
    fn info(&self) -> String {
        fl!("tool_rect")
    }
    fn icon_name(&self) -> String {
        str!("square")
    }
    fn accel(&self) -> Option<char> {
        Some('R')
    }

    fn help_url(&self) -> Option<String> {
        Some("docs/creator/tools/rect".to_string())
    }

    fn tool_event(
        &mut self,
        tool_event: ToolEvent,
        _ui: &mut TheUI,
        _ctx: &mut TheContext,
        project: &mut Project,
        server_ctx: &mut ServerContext,
    ) -> bool {
        match tool_event {
            Activate => {
                server_ctx.curr_map_tool_type = MapToolType::Rect;

                if let Some(region) = project.get_region_mut(&server_ctx.curr_region) {
                    region.map.selected_vertices.clear();
                    region.map.selected_linedefs.clear();
                    region.map.selected_sectors.clear();
                }

                // self.activate_map_tool_helper(ui, ctx, project, server_ctx);

                return true;
            }
            DeActivate => {
                server_ctx.curr_map_tool_type = MapToolType::General;
                server_ctx.hover_cursor = None;
                self.reset_stroke();
                if let Some(region) = project.get_region_mut(&server_ctx.curr_region) {
                    region.map.clear_temp();
                }
                return true;
            }
            _ => {}
        };

        false
    }

    fn map_event(
        &mut self,
        map_event: MapEvent,
        ui: &mut TheUI,
        ctx: &mut TheContext,
        map: &mut Map,
        server_ctx: &mut ServerContext,
    ) -> Option<ProjectUndoAtom> {
        let mut undo_atom: Option<ProjectUndoAtom> = None;

        /// Add a tile at the current hover position
        fn add_tile(
            ui: &mut TheUI,
            _ctx: &mut TheContext,
            map: &mut Map,
            server_ctx: &mut ServerContext,
            hovered_vertices: Option<[Vec2<f32>; 4]>,
            mode: i32,
        ) -> Option<ProjectUndoAtom> {
            let mut undo_atom: Option<ProjectUndoAtom> = None;
            // let size = 1.0 / map.subdivisions;

            if let Some(vertices) = hovered_vertices {
                let mut add_it = true;
                let mut layer: u8 = 0;

                if ui.shift {
                    // Delete the top tile at the given position if shift is pressed
                    if let Some(ev0) = map.find_vertex_at(vertices[0].x, vertices[0].y) {
                        if let Some(ev1) = map.find_vertex_at(vertices[1].x, vertices[1].y) {
                            if let Some(ev2) = map.find_vertex_at(vertices[2].x, vertices[2].y) {
                                if let Some(ev3) = map.find_vertex_at(vertices[3].x, vertices[3].y)
                                {
                                    let sectors =
                                        map.find_sectors_with_vertex_indices(&[ev0, ev1, ev2, ev3]);

                                    if let Some(sector_id) = sectors.last() {
                                        let prev = map.clone();
                                        let mut lines = vec![];
                                        if let Some(s) = map.find_sector(*sector_id) {
                                            lines = s.linedefs.clone();
                                        }
                                        map.delete_elements(&[], &lines, &[*sector_id]);
                                        undo_atom = Some(ProjectUndoAtom::MapEdit(
                                            server_ctx.pc,
                                            Box::new(prev),
                                            Box::new(map.clone()),
                                        ));
                                    }
                                }
                            }
                        }
                    }
                } else if let Some(source) = get_source(ui, server_ctx) {
                    // Add mode
                    // Check if tile already exists with same source
                    if let Some(ev0) = map.find_vertex_at(vertices[0].x, vertices[0].y) {
                        if let Some(ev1) = map.find_vertex_at(vertices[1].x, vertices[1].y) {
                            if let Some(ev2) = map.find_vertex_at(vertices[2].x, vertices[2].y) {
                                if let Some(ev3) = map.find_vertex_at(vertices[3].x, vertices[3].y)
                                {
                                    let sectors =
                                        map.find_sectors_with_vertex_indices(&[ev0, ev1, ev2, ev3]);

                                    let prev = map.clone();
                                    for sector_id in sectors {
                                        if let Some(sector) = map.find_sector_mut(sector_id) {
                                            if let Some(sector_floor_source) =
                                                sector.properties.get_default_source()
                                            {
                                                // Assign id to the higher current layer id (+1).
                                                if let Some(l) = &sector.layer {
                                                    if *l > layer {
                                                        layer = *l;
                                                    }
                                                }

                                                if source == *sector_floor_source {
                                                    // A tile with the same floor_source exists, do not add.
                                                    add_it = false;
                                                } else if mode == 0 {
                                                    // In overlay mode we just overwrite the source
                                                    sector.properties.set(
                                                        "source",
                                                        Value::Source(source.clone()),
                                                    );

                                                    undo_atom = Some(ProjectUndoAtom::MapEdit(
                                                        server_ctx.pc,
                                                        Box::new(prev),
                                                        Box::new(map.clone()),
                                                    ));

                                                    add_it = false;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    if add_it {
                        let v0 = map.add_vertex_at(vertices[0].x, vertices[0].y);
                        let v1 = map.add_vertex_at(vertices[1].x, vertices[1].y);
                        let v2 = map.add_vertex_at(vertices[2].x, vertices[2].y);
                        let v3 = map.add_vertex_at(vertices[3].x, vertices[3].y);

                        map.possible_polygon = vec![];
                        let _ = map.create_linedef_manual(v0, v1);
                        let _ = map.create_linedef_manual(v1, v2);
                        let _ = map.create_linedef_manual(v2, v3);
                        let _ = map.create_linedef_manual(v3, v0);
                        let sid = map.close_polygon_manual();

                        if let Some(sector_id) = sid {
                            // Add the info for correct box rendering
                            // if let Some(l) = map.find_linedef_mut(l0.0) {
                            //     l.properties.set("row1_source", source.clone());
                            //     l.properties.set("wall_height", Value::Float(size));
                            // }
                            // if let Some(l) = map.find_linedef_mut(l1.0) {
                            //     l.properties.set("row1_source", source.clone());
                            //     l.properties.set("wall_height", Value::Float(size));
                            // }
                            // if let Some(l) = map.find_linedef_mut(l2.0) {
                            //     l.properties.set("row1_source", source.clone());
                            //     l.properties.set("wall_height", Value::Float(size));
                            // }
                            // if let Some(l) = map.find_linedef_mut(id.0) {
                            //     l.properties.set("row1_source", source.clone());
                            //     l.properties.set("wall_height", Value::Float(size));
                            // }

                            let prev = map.clone();
                            if let Some(sector) = map.find_sector_mut(sector_id) {
                                sector.properties.set("rect", Value::Bool(true));

                                sector.properties.set("source", Value::Source(source));
                                sector.layer = Some(layer + 1);
                            }

                            undo_atom = Some(ProjectUndoAtom::MapEdit(
                                server_ctx.pc,
                                Box::new(prev),
                                Box::new(map.clone()),
                            ));

                            map.selected_vertices.clear();
                            map.selected_linedefs.clear();
                            map.selected_sectors = vec![sector_id];
                        }
                    }
                }
            }

            undo_atom
        }

        fn apply_hover(
            coord: Vec2<i32>,
            ui: &mut TheUI,
            _ctx: &mut TheContext,
            map: &mut Map,
            server_ctx: &mut ServerContext,
        ) -> Option<[Vec2<f32>; 4]> {
            let mut hovered_vertices: Option<[Vec2<f32>; 4]> = None;

            if let Some(render_view) = ui.get_render_view("PolyView") {
                let dim = *render_view.dim();
                server_ctx.hover = (None, None, None);
                let cp = server_ctx.local_to_map_cell(
                    Vec2::new(dim.width as f32, dim.height as f32),
                    Vec2::new(coord.x as f32, coord.y as f32),
                    map,
                    1.0,
                );
                let step = 1.0;
                map.curr_rectangle = Some((cp, cp + step));
                hovered_vertices = Some([
                    cp,
                    cp + Vec2::new(0.0, step),
                    cp + Vec2::new(step, step),
                    cp + Vec2::new(step, 0.0),
                ]);
                server_ctx.hover_cursor = Some(cp);
                if map.properties.get_bool_default("terrain_enabled", false) {
                    server_ctx.rect_terrain_id = Some((cp.x.floor() as i32, cp.y.floor() as i32));
                } else {
                    server_ctx.rect_terrain_id = None;
                }
            }

            hovered_vertices
        }

        match map_event {
            MapKey(_c) => {}
            MapClicked(coord) => {
                if self.hud.clicked(coord.x, coord.y, map, ui, ctx, server_ctx) {
                    crate::editor::RUSTERIX.write().unwrap().set_dirty();
                    return None;
                }

                self.processed.clear();
                if ui.alt {
                    if server_ctx.editor_view_mode == EditorViewMode::D2 {
                        self.hovered_vertices = apply_hover(coord, ui, ctx, map, server_ctx);
                    } else {
                        self.compute_3d_tile(coord, map, ui, server_ctx);
                    }

                    if let Some(source) = Self::sample_source_at_current_target(map, server_ctx) {
                        ctx.ui.send(TheEvent::Custom(
                            TheId::named("Pick Tile Source"),
                            Self::encode_tile_source(source),
                        ));
                    }
                    return None;
                }

                if server_ctx.editor_view_mode == EditorViewMode::D2 {
                    let use_terrain_paint =
                        map.properties.get_bool_default("terrain_enabled", false);
                    if let Some(cp) = server_ctx.hover_cursor {
                        self.begin_stroke_if_needed(map);
                        let k = if use_terrain_paint {
                            Vec2::new(cp.x.floor() as i32, cp.y.floor() as i32)
                        } else {
                            Vec2::new(cp.x.floor() as i32, cp.y.floor() as i32)
                        };
                        if ui.ctrl && !use_terrain_paint {
                            self.line_start_2d_cell = Some(k);
                            self.line_axis_horizontal = None;
                        }
                        if let Some(work_map) = self.stroke_work_map.as_mut() {
                            let changed = if use_terrain_paint {
                                server_ctx.rect_terrain_id = Some((k.x, k.y));
                                Self::apply_3d_paint_at_current_target(work_map, ui, server_ctx)
                                    .is_some()
                            } else {
                                let step = 1.0;
                                let x = k.x as f32 * step;
                                let y = k.y as f32 * step;
                                let verts = Some([
                                    Vec2::new(x, y),
                                    Vec2::new(x, y + step),
                                    Vec2::new(x + step, y + step),
                                    Vec2::new(x + step, y),
                                ]);
                                add_tile(ui, ctx, work_map, server_ctx, verts, self.mode).is_some()
                            };
                            if changed {
                                self.stroke_changed = true;
                                Self::sync_live_stroke_throttled(
                                    work_map,
                                    server_ctx,
                                    &mut self.last_live_scene_sync_at,
                                    &mut self.stroke_dirty_chunks,
                                    false,
                                );
                            }
                            self.processed.insert(RectPaintKey::Cell(k.x, k.y));
                            self.last_2d_cell = Some(k);
                        }
                    }
                } else {
                    self.compute_3d_tile(coord, map, ui, server_ctx);
                    self.begin_stroke_if_needed(map);
                    if let Some(key) = Self::current_3d_paint_key(server_ctx)
                        && !self.processed.contains(&key)
                        && let Some(work_map) = self.stroke_work_map.as_mut()
                        && Self::apply_3d_paint_at_current_target(work_map, ui, server_ctx)
                            .is_some()
                    {
                        self.stroke_changed = true;
                        Self::sync_live_stroke_throttled(
                            work_map,
                            server_ctx,
                            &mut self.last_live_scene_sync_at,
                            &mut self.stroke_dirty_chunks,
                            false,
                        );
                        self.processed.insert(key);
                    }
                }
            }
            MapDragged(coord) => {
                if self.hud.dragged(coord.x, coord.y, map, ui, ctx, server_ctx) {
                    crate::editor::RUSTERIX.write().unwrap().set_dirty();
                    return None;
                }
                if ui.alt {
                    self.reset_stroke();
                    if server_ctx.editor_view_mode == EditorViewMode::D2 {
                        self.hovered_vertices = apply_hover(coord, ui, ctx, map, server_ctx);
                    } else {
                        self.compute_3d_tile(coord, map, ui, server_ctx);
                    }
                    return None;
                }
                if server_ctx.editor_view_mode == EditorViewMode::D2 {
                    let use_terrain_paint =
                        map.properties.get_bool_default("terrain_enabled", false);
                    self.hovered_vertices = apply_hover(coord, ui, ctx, map, server_ctx);
                    if let Some(cp) = server_ctx.hover_cursor {
                        self.begin_stroke_if_needed(map);
                        let k = if use_terrain_paint {
                            Vec2::new(cp.x.floor() as i32, cp.y.floor() as i32)
                        } else {
                            Vec2::new(cp.x.floor() as i32, cp.y.floor() as i32)
                        };
                        if let Some(work_map) = self.stroke_work_map.as_mut() {
                            let line_anchor = self.line_start_2d_cell.unwrap_or(k);
                            let mut to = k;
                            if ui.ctrl && !use_terrain_paint {
                                if self.line_axis_horizontal.is_none() {
                                    let dx = (to.x - line_anchor.x).abs();
                                    let dy = (to.y - line_anchor.y).abs();
                                    if dx > 0 || dy > 0 {
                                        self.line_axis_horizontal = Some(dx >= dy);
                                    }
                                }
                                if let Some(horizontal) = self.line_axis_horizontal {
                                    if horizontal {
                                        to.y = line_anchor.y;
                                    } else {
                                        to.x = line_anchor.x;
                                    }
                                }
                            }

                            let from = self.last_2d_cell.unwrap_or(to);
                            let step = 1.0;
                            let between = Self::cells_between(from, to);
                            for cell in between.iter().copied() {
                                let paint_key = RectPaintKey::Cell(cell.x, cell.y);
                                if self.processed.contains(&paint_key) {
                                    continue;
                                }
                                let changed = if use_terrain_paint {
                                    server_ctx.rect_terrain_id = Some((cell.x, cell.y));
                                    Self::apply_3d_paint_at_current_target(work_map, ui, server_ctx)
                                        .is_some()
                                } else {
                                    let x = cell.x as f32 * step;
                                    let y = cell.y as f32 * step;
                                    let verts = Some([
                                        Vec2::new(x, y),
                                        Vec2::new(x, y + step),
                                        Vec2::new(x + step, y + step),
                                        Vec2::new(x + step, y),
                                    ]);
                                    add_tile(ui, ctx, work_map, server_ctx, verts, self.mode)
                                        .is_some()
                                };
                                if changed {
                                    self.stroke_changed = true;
                                    Self::sync_live_stroke_throttled(
                                        work_map,
                                        server_ctx,
                                        &mut self.last_live_scene_sync_at,
                                        &mut self.stroke_dirty_chunks,
                                        false,
                                    );
                                }
                                self.processed.insert(paint_key);
                            }
                            self.last_2d_cell = Some(to);
                        }
                    }
                } else {
                    self.compute_3d_tile(coord, map, ui, server_ctx);
                    self.begin_stroke_if_needed(map);
                    if let Some(key) = Self::current_3d_paint_key(server_ctx)
                        && !self.processed.contains(&key)
                        && let Some(work_map) = self.stroke_work_map.as_mut()
                        && Self::apply_3d_paint_at_current_target(work_map, ui, server_ctx)
                            .is_some()
                    {
                        self.stroke_changed = true;
                        Self::sync_live_stroke_throttled(
                            work_map,
                            server_ctx,
                            &mut self.last_live_scene_sync_at,
                            &mut self.stroke_dirty_chunks,
                            false,
                        );
                        self.processed.insert(key);
                    }
                }
            }
            MapUp(_) => {
                if self.stroke_active {
                    if self.stroke_changed
                        && let (Some(prev), Some(new_map)) =
                            (self.stroke_prev_map.take(), self.stroke_work_map.take())
                    {
                        Self::sync_live_stroke_throttled(
                            &new_map,
                            server_ctx,
                            &mut self.last_live_scene_sync_at,
                            &mut self.stroke_dirty_chunks,
                            true,
                        );
                        *map = new_map;
                        undo_atom = Some(ProjectUndoAtom::MapEdit(
                            server_ctx.pc,
                            Box::new(prev),
                            Box::new(map.clone()),
                        ));
                    }
                    self.reset_stroke();
                }
            }
            MapHover(coord) => {
                if server_ctx.editor_view_mode == EditorViewMode::D2 {
                    self.hovered_vertices = apply_hover(coord, ui, ctx, map, server_ctx);
                } else {
                    self.compute_3d_tile(coord, map, ui, server_ctx);
                }
            }
            MapDelete => {}
            MapEscape => {
                self.reset_stroke();
                map.clear_temp();
                crate::editor::RUSTERIX.write().unwrap().set_dirty();
            }
        }
        undo_atom
    }

    fn draw_hud(
        &mut self,
        buffer: &mut TheRGBABuffer,
        map: &mut Map,
        ctx: &mut TheContext,
        server_ctx: &mut ServerContext,
        assets: &Assets,
    ) {
        if server_ctx.editor_view_mode == EditorViewMode::D2
            && let Some((top_left, bottom_right)) = map.curr_rectangle
        {
            let screen_size = Vec2::new(buffer.dim().width as f32, buffer.dim().height as f32);
            let a = ServerContext::map_grid_to_local(screen_size, top_left, map);
            let b = ServerContext::map_grid_to_local(screen_size, bottom_right, map);
            let min_x = a.x.min(b.x).round() as i32;
            let min_y = a.y.min(b.y).round() as i32;
            let max_x = a.x.max(b.x).round() as i32;
            let max_y = a.y.max(b.y).round() as i32;
            let rect = TheDim::new(
                min_x,
                min_y,
                (max_x - min_x).max(1) + 1,
                (max_y - min_y).max(1) + 1,
            );
            buffer.draw_rect_outline(&rect, &[255, 255, 255, 255]);
        }

        let id = if !map.selected_linedefs.is_empty() {
            Some(map.selected_linedefs[0])
        } else {
            None
        };
        self.hud.draw(buffer, map, ctx, server_ctx, id, assets);
    }

    fn handle_event(
        &mut self,
        event: &TheEvent,
        _ui: &mut TheUI,
        _ctx: &mut TheContext,
        project: &mut Project,
        server_ctx: &mut ServerContext,
    ) -> bool {
        let redraw = false;
        #[allow(clippy::single_match)]
        match event {
            TheEvent::StateChanged(id, state) => {
                #[allow(clippy::collapsible_if)]
                if id.name == "Apply Map Properties" && *state == TheWidgetState::Clicked {
                    let source = crate::utils::get_source(_ui, server_ctx).map(Value::Source);

                    if let Some(source) = source {
                        if let Some(map) = project.get_map_mut(server_ctx) {
                            let _prev = map.clone();

                            for linedef_id in map.selected_linedefs.clone() {
                                if let Some(linedef) = map.find_linedef_mut(linedef_id) {
                                    if self.hud.selected_icon_index == 0 {
                                        linedef.properties.set("row1_source", source.clone());
                                    } else if self.hud.selected_icon_index == 1 {
                                        linedef.properties.set("row2_source", source.clone());
                                    } else if self.hud.selected_icon_index == 2 {
                                        linedef.properties.set("row3_source", source.clone());
                                    } else if self.hud.selected_icon_index == 3 {
                                        linedef.properties.set("row4_source", source.clone());
                                    }
                                    crate::editor::RUSTERIX.write().unwrap().set_dirty();
                                }
                            }

                            crate::editor::RUSTERIX.write().unwrap().set_dirty();
                        }
                    }
                } else if id.name == "Remove Map Properties" && *state == TheWidgetState::Clicked {
                    if let Some(map) = project.get_map_mut(server_ctx) {
                        let _prev = map.clone();

                        for linedef_id in map.selected_linedefs.clone() {
                            if let Some(linedef) = map.find_linedef_mut(linedef_id) {
                                if self.hud.selected_icon_index == 0 {
                                    linedef
                                        .properties
                                        .set("row1_source", Value::Source(PixelSource::Off));
                                } else if self.hud.selected_icon_index == 1 {
                                    linedef
                                        .properties
                                        .set("row2_source", Value::Source(PixelSource::Off));
                                } else if self.hud.selected_icon_index == 2 {
                                    linedef
                                        .properties
                                        .set("row3_source", Value::Source(PixelSource::Off));
                                } else if self.hud.selected_icon_index == 3 {
                                    linedef
                                        .properties
                                        .set("row4_source", Value::Source(PixelSource::Off));
                                }
                                crate::editor::RUSTERIX.write().unwrap().set_dirty();
                            }
                        }

                        crate::editor::RUSTERIX.write().unwrap().set_dirty();
                    }
                }
            }
            _ => {}
        }
        redraw
    }
}

impl RectTool {
    fn encode_tile_source(source: TileSource) -> TheValue {
        match source {
            TileSource::SingleTile(id) => {
                TheValue::List(vec![TheValue::Text("single".to_string()), TheValue::Id(id)])
            }
            TileSource::TileGroup(id) => {
                TheValue::List(vec![TheValue::Text("group".to_string()), TheValue::Id(id)])
            }
            TileSource::TileGroupMember {
                group_id,
                member_index,
            } => TheValue::List(vec![
                TheValue::Text("group_member".to_string()),
                TheValue::Id(group_id),
                TheValue::Int(member_index as i32),
            ]),
            TileSource::Procedural(id) => TheValue::List(vec![
                TheValue::Text("procedural".to_string()),
                TheValue::Id(id),
            ]),
        }
    }

    fn tile_source_from_pixel_source(source: &PixelSource) -> Option<TileSource> {
        match source {
            PixelSource::TileId(id) => Some(TileSource::SingleTile(*id)),
            PixelSource::TileGroup(id) => Some(TileSource::TileGroup(*id)),
            PixelSource::TileGroupMember {
                group_id,
                member_index,
            } => Some(TileSource::TileGroupMember {
                group_id: *group_id,
                member_index: *member_index,
            }),
            PixelSource::ProceduralTile(id) => Some(TileSource::Procedural(*id)),
            _ => None,
        }
    }

    fn sample_terrain_source(map: &Map, key: (i32, i32)) -> Option<TileSource> {
        if let Some(Value::TileOverrides(tiles)) = map.properties.get("tiles")
            && let Some(source) = tiles.get(&key)
            && let Some(source) = Self::tile_source_from_pixel_source(source)
        {
            return Some(source);
        }

        if let Some(Value::BlendOverrides(blend_tiles)) = map.properties.get("blend_tiles")
            && let Some((_, source)) = blend_tiles.get(&key)
        {
            return Self::tile_source_from_pixel_source(source);
        }

        map.properties
            .get("default_terrain_tile")
            .and_then(|value| {
                if let Value::Source(source) = value {
                    Self::tile_source_from_pixel_source(source)
                } else {
                    None
                }
            })
    }

    fn sample_sector_source_at_cell(map: &Map, cell: Vec2<i32>) -> Option<TileSource> {
        let x = cell.x as f32;
        let y = cell.y as f32;

        let exact_sector = (|| {
            let ev0 = map.find_vertex_at(x, y)?;
            let ev1 = map.find_vertex_at(x, y + 1.0)?;
            let ev2 = map.find_vertex_at(x + 1.0, y + 1.0)?;
            let ev3 = map.find_vertex_at(x + 1.0, y)?;
            let sectors = map.find_sectors_with_vertex_indices(&[ev0, ev1, ev2, ev3]);
            sectors.last().copied()
        })();

        if let Some(sector_id) = exact_sector
            && let Some(sector) = map.find_sector(sector_id)
            && let Some(source) = sector.properties.get_default_source()
            && let Some(source) = Self::tile_source_from_pixel_source(source)
        {
            return Some(source);
        }

        map.find_sector_at(Vec2::new(x + 0.5, y + 0.5))
            .and_then(|sector| sector.properties.get_default_source())
            .and_then(Self::tile_source_from_pixel_source)
    }

    fn sample_sector_override_source(
        map: &Map,
        sector_id: u32,
        key: (i32, i32),
    ) -> Option<TileSource> {
        let sector = map.find_sector(sector_id)?;

        if let Some(Value::TileOverrides(tiles)) = sector.properties.get("tiles")
            && let Some(source) = tiles.get(&key)
            && let Some(source) = Self::tile_source_from_pixel_source(source)
        {
            return Some(source);
        }

        if let Some(Value::BlendOverrides(blend_tiles)) = sector.properties.get("blend_tiles")
            && let Some((_, source)) = blend_tiles.get(&key)
            && let Some(source) = Self::tile_source_from_pixel_source(source)
        {
            return Some(source);
        }

        sector
            .properties
            .get_default_source()
            .and_then(Self::tile_source_from_pixel_source)
    }

    fn sample_source_at_current_target(
        map: &Map,
        server_ctx: &ServerContext,
    ) -> Option<TileSource> {
        if server_ctx.editor_view_mode == EditorViewMode::D2 {
            if map.properties.get_bool_default("terrain_enabled", false) {
                return server_ctx
                    .rect_terrain_id
                    .and_then(|key| Self::sample_terrain_source(map, key));
            }

            return server_ctx.hover_cursor.and_then(|cp| {
                let cell = Vec2::new(cp.x.floor() as i32, cp.y.floor() as i32);
                Self::sample_sector_source_at_cell(map, cell)
            });
        }

        if let Some(key) = server_ctx.rect_terrain_id {
            return Self::sample_terrain_source(map, key);
        }

        if let Some(sector_id) = server_ctx.rect_sector_id_3d {
            return Self::sample_sector_override_source(map, sector_id, server_ctx.rect_tile_id_3d);
        }

        if let Some((object_id, face_index)) = server_ctx.rect_geometry_face_3d {
            let object = map
                .geometry_objects
                .iter()
                .find(|object| object.id == object_id)?;
            let face = object.faces.get(face_index)?;
            if let Some(source) = face.tiles.get(&server_ctx.rect_tile_id_3d) {
                return Self::tile_source_from_pixel_source(source);
            }
            return face
                .tile
                .as_ref()
                .and_then(Self::tile_source_from_pixel_source);
        }

        None
    }

    fn geometry_object_local_from_world(
        object: &rusterix::GeometryObject,
        world: Vec3<f32>,
    ) -> Vec3<f32> {
        let m = &object.transform;
        let sx = if m[0][0].abs() > 1e-6 { m[0][0] } else { 1.0 };
        let sy = if m[1][1].abs() > 1e-6 { m[1][1] } else { 1.0 };
        let sz = if m[2][2].abs() > 1e-6 { m[2][2] } else { 1.0 };
        Vec3::new(
            (world.x - m[3][0]) / sx,
            (world.y - m[3][1]) / sy,
            (world.z - m[3][2]) / sz,
        )
    }

    fn geometry_face_uv_basis(points: &[Vec3<f32>]) -> Option<Vec3<f32>> {
        if points.len() < 3 {
            return None;
        }
        let mut normal = Vec3::<f32>::zero();
        for index in 1..points.len() - 1 {
            normal += (points[index] - points[0]).cross(points[index + 1] - points[0]);
        }
        (normal.magnitude() > 1e-6).then_some(normal)
    }

    fn geometry_point_auto_uv(point: Vec3<f32>, normal: Vec3<f32>) -> Vec2<f32> {
        let abs = Vec3::new(normal.x.abs(), normal.y.abs(), normal.z.abs());
        if abs.y >= abs.x && abs.y >= abs.z {
            Vec2::new(point.x, point.z)
        } else if abs.x >= abs.z {
            Vec2::new(point.z, point.y)
        } else {
            Vec2::new(point.x, point.y)
        }
    }

    fn barycentric_2d(
        p: Vec2<f32>,
        a: Vec2<f32>,
        b: Vec2<f32>,
        c: Vec2<f32>,
    ) -> Option<(f32, f32, f32)> {
        let v0 = b - a;
        let v1 = c - a;
        let v2 = p - a;
        let d00 = v0.dot(v0);
        let d01 = v0.dot(v1);
        let d11 = v1.dot(v1);
        let d20 = v2.dot(v0);
        let d21 = v2.dot(v1);
        let denom = d00 * d11 - d01 * d01;
        if denom.abs() <= 1e-6 {
            return None;
        }
        let v = (d11 * d20 - d01 * d21) / denom;
        let w = (d00 * d21 - d01 * d20) / denom;
        let u = 1.0 - v - w;
        (u >= -1e-4 && v >= -1e-4 && w >= -1e-4).then_some((u, v, w))
    }

    fn uv_inside_face(uv: Vec2<f32>, face_uvs: &[Vec2<f32>]) -> bool {
        if face_uvs.len() < 3 {
            return false;
        }
        (1..face_uvs.len() - 1).any(|index| {
            Self::barycentric_2d(uv, face_uvs[0], face_uvs[index], face_uvs[index + 1]).is_some()
        })
    }

    fn geometry_face_target_at_world(
        map: &Map,
        object_id: Uuid,
        world_hit: Vec3<f32>,
    ) -> Option<(usize, (i32, i32))> {
        let object = map
            .geometry_objects
            .iter()
            .find(|object| object.id == object_id)?;
        let local_hit = Self::geometry_object_local_from_world(object, world_hit);

        let mut best: Option<(usize, (i32, i32), f32)> = None;
        for (face_index, face) in object.faces.iter().enumerate() {
            if face.indices.len() < 3 {
                continue;
            }
            let points = face
                .indices
                .iter()
                .filter_map(|vertex_index| object.vertices.get(*vertex_index).copied())
                .collect::<Vec<_>>();
            if points.len() != face.indices.len() {
                continue;
            }
            let Some(normal) = Self::geometry_face_uv_basis(&points) else {
                continue;
            };
            let normal_unit = normal / normal.magnitude();
            let plane_dist = (local_hit - points[0]).dot(normal_unit).abs();
            let face_uvs = points
                .iter()
                .map(|point| Self::geometry_point_auto_uv(*point, normal))
                .collect::<Vec<_>>();
            let hit_uv = Self::geometry_point_auto_uv(local_hit, normal);
            if !Self::uv_inside_face(hit_uv, &face_uvs) {
                continue;
            }
            let min_uv = face_uvs
                .iter()
                .fold(Vec2::broadcast(f32::INFINITY), |acc, uv| {
                    Vec2::new(acc.x.min(uv.x), acc.y.min(uv.y))
                });
            let tile = (
                (hit_uv.x - min_uv.x).floor() as i32,
                (hit_uv.y - min_uv.y).floor() as i32,
            );
            if best
                .as_ref()
                .map(|(_, _, best_dist)| plane_dist < *best_dist)
                .unwrap_or(true)
            {
                best = Some((face_index, tile, plane_dist));
            }
        }

        best.map(|(face_index, tile, _)| (face_index, tile))
    }

    fn begin_stroke_if_needed(&mut self, map: &Map) {
        if !self.stroke_active {
            self.stroke_active = true;
            self.stroke_changed = false;
            self.stroke_prev_map = Some(map.clone());
            self.stroke_work_map = Some(map.clone());
            self.processed.clear();
            self.last_live_scene_sync_at = None;
            self.stroke_dirty_chunks.clear();
        }
    }

    fn add_bbox_dirty_chunks(bbox: rusterix::BBox, chunks: &mut Vec<(i32, i32)>) {
        if !bbox.min.x.is_finite()
            || !bbox.min.y.is_finite()
            || !bbox.max.x.is_finite()
            || !bbox.max.y.is_finite()
        {
            return;
        }

        let chunk_size = 32;
        let min_cx = (bbox.min.x / chunk_size as f32).floor() as i32;
        let min_cy = (bbox.min.y / chunk_size as f32).floor() as i32;
        let max_cx = (bbox.max.x / chunk_size as f32).ceil() as i32;
        let max_cy = (bbox.max.y / chunk_size as f32).ceil() as i32;
        for cy in min_cy..max_cy.max(min_cy + 1) {
            for cx in min_cx..max_cx.max(min_cx + 1) {
                chunks.push((cx * chunk_size, cy * chunk_size));
            }
        }
    }

    fn add_surface_tile_dirty_chunks(
        work_map: &Map,
        sector_id: u32,
        tile: (i32, i32),
        chunks: &mut Vec<(i32, i32)>,
    ) -> bool {
        let mut added = false;
        for surface in work_map
            .surfaces
            .values()
            .filter(|surface| surface.sector_id == sector_id)
        {
            let points = surface.tile_outline_world_local(tile, work_map);
            if points.is_empty() {
                continue;
            }

            let first = Vec2::new(points[0].x, points[0].z);
            let mut bbox = rusterix::BBox::new(first, first);
            for p in points.iter().skip(1) {
                let p = Vec2::new(p.x, p.z);
                bbox.min.x = bbox.min.x.min(p.x);
                bbox.min.y = bbox.min.y.min(p.y);
                bbox.max.x = bbox.max.x.max(p.x);
                bbox.max.y = bbox.max.y.max(p.y);
            }
            bbox.expand(Vec2::broadcast(0.05));
            Self::add_bbox_dirty_chunks(bbox, chunks);
            added = true;
        }
        added
    }

    fn live_stroke_dirty_chunks(work_map: &Map, server_ctx: &ServerContext) -> Vec<(i32, i32)> {
        if server_ctx.editor_view_mode == EditorViewMode::D2 {
            return Vec::new();
        }

        let mut dirty_chunks = Vec::new();
        if let Some((x, z)) = server_ctx.rect_terrain_id {
            let chunk_size = 32;
            dirty_chunks.push((
                x.div_euclid(chunk_size) * chunk_size,
                z.div_euclid(chunk_size) * chunk_size,
            ));
        } else if let Some((object_id, _)) = server_ctx.rect_geometry_face_3d {
            let rusterix = crate::editor::RUSTERIX.read().unwrap();
            dirty_chunks.extend(
                rusterix
                    .scene_handler
                    .build_index
                    .chunks_for_owner(scenevm::GeoId::GeometryObject(object_id)),
            );
        } else if let Some(sector_id) = server_ctx.rect_sector_id_3d
            && let Some(sector) = work_map.find_sector(sector_id)
        {
            {
                let rusterix = crate::editor::RUSTERIX.read().unwrap();
                dirty_chunks.extend(
                    rusterix
                        .scene_handler
                        .build_index
                        .chunks_for_owner(scenevm::GeoId::Sector(sector_id)),
                );
            }
            if !Self::add_surface_tile_dirty_chunks(
                work_map,
                sector_id,
                server_ctx.rect_tile_id_3d,
                &mut dirty_chunks,
            ) {
                Self::add_bbox_dirty_chunks(sector.bounding_box(work_map), &mut dirty_chunks);
            }
        }

        dirty_chunks
    }

    fn sync_live_stroke_throttled(
        work_map: &Map,
        server_ctx: &ServerContext,
        last_live_scene_sync_at: &mut Option<Instant>,
        stroke_dirty_chunks: &mut FxHashSet<(i32, i32)>,
        force: bool,
    ) {
        stroke_dirty_chunks.extend(Self::live_stroke_dirty_chunks(work_map, server_ctx));
        let now = Instant::now();
        if force
            || last_live_scene_sync_at
                .map(|last| now.duration_since(last) >= LIVE_PAINT_SYNC_INTERVAL)
                .unwrap_or(true)
        {
            let dirty_chunks = stroke_dirty_chunks.iter().copied().collect::<Vec<_>>();
            if !dirty_chunks.is_empty() {
                crate::utils::editor_scene_replace_incremental_map_update(
                    work_map.clone(),
                    dirty_chunks,
                );
            }
            *last_live_scene_sync_at = Some(now);
        }
    }

    fn reset_stroke(&mut self) {
        self.stroke_active = false;
        self.stroke_changed = false;
        self.stroke_prev_map = None;
        self.stroke_work_map = None;
        self.last_2d_cell = None;
        self.line_start_2d_cell = None;
        self.line_axis_horizontal = None;
        self.processed.clear();
        self.last_live_scene_sync_at = None;
        self.stroke_dirty_chunks.clear();
    }

    fn cells_between(a: Vec2<i32>, b: Vec2<i32>) -> Vec<Vec2<i32>> {
        let mut out = Vec::new();
        let mut x0 = a.x;
        let mut y0 = a.y;
        let x1 = b.x;
        let y1 = b.y;

        let dx = (x1 - x0).abs();
        let sx = if x0 < x1 { 1 } else { -1 };
        let dy = -(y1 - y0).abs();
        let sy = if y0 < y1 { 1 } else { -1 };
        let mut err = dx + dy;

        loop {
            out.push(Vec2::new(x0, y0));
            if x0 == x1 && y0 == y1 {
                break;
            }
            let e2 = 2 * err;
            if e2 >= dy {
                err += dy;
                x0 += sx;
            }
            if e2 <= dx {
                err += dx;
                y0 += sy;
            }
        }

        out
    }

    fn current_3d_paint_key(server_ctx: &ServerContext) -> Option<RectPaintKey> {
        if let Some((x, z)) = server_ctx.rect_terrain_id {
            return Some(RectPaintKey::Terrain(x, z));
        }
        if let Some((object_id, face_index)) = server_ctx.rect_geometry_face_3d {
            let (x, y) = server_ctx.rect_tile_id_3d;
            return Some(RectPaintKey::Geometry(object_id, face_index, x, y));
        }
        if let Some(sector_id) = server_ctx.rect_sector_id_3d {
            let (x, y) = server_ctx.rect_tile_id_3d;
            return Some(RectPaintKey::Sector(sector_id, x, y));
        }
        None
    }

    fn apply_3d_paint_at_current_target(
        map: &mut Map,
        ui: &mut TheUI,
        server_ctx: &ServerContext,
    ) -> Option<Vec2<i32>> {
        let source = crate::utils::get_source(ui, server_ctx);

        if let Some((x, z)) = server_ctx.rect_terrain_id {
            let mut tiles = match map.properties.get("tiles") {
                Some(Value::TileOverrides(existing)) => existing.clone(),
                _ => FxHashMap::default(),
            };

            let mut blend_tiles = match map.properties.get("blend_tiles") {
                Some(Value::BlendOverrides(existing)) => existing.clone(),
                _ => FxHashMap::default(),
            };

            if ui.shift {
                tiles.remove(&(x, z));
                blend_tiles.remove(&(x, z));
            } else if let Some(source) = source.clone() {
                if server_ctx.rect_blend_preset == VertexBlendPreset::Solid {
                    tiles.insert((x, z), source);
                    blend_tiles.remove(&(x, z));
                } else {
                    blend_tiles.insert((x, z), (server_ctx.rect_blend_preset, source));
                    tiles.remove(&(x, z));
                }
            } else {
                return None;
            }

            if tiles.is_empty() {
                map.properties.remove("tiles");
            } else {
                map.properties.set("tiles", Value::TileOverrides(tiles));
            }
            if blend_tiles.is_empty() {
                map.properties.remove("blend_tiles");
            } else {
                map.properties
                    .set("blend_tiles", Value::BlendOverrides(blend_tiles));
            }

            return Some(Vec2::new(x, z));
        }

        if let Some((object_id, face_index)) = server_ctx.rect_geometry_face_3d {
            let key = server_ctx.rect_tile_id_3d;
            let object = map
                .geometry_objects
                .iter_mut()
                .find(|object| object.id == object_id)?;
            let face = object.faces.get_mut(face_index)?;

            if ui.shift {
                face.tiles.remove(&key);
            } else if let Some(source) = source.clone() {
                face.tiles.insert(key, source);
            } else {
                return None;
            }

            return Some(Vec2::new(key.0, key.1));
        }

        if let Some(sector_id) = server_ctx.rect_sector_id_3d
            && let Some(sector) = map.find_sector_mut(sector_id)
        {
            let key = Vec2::new(server_ctx.rect_tile_id_3d.0, server_ctx.rect_tile_id_3d.1);

            let mut tiles = match sector.properties.get("tiles") {
                Some(Value::TileOverrides(existing)) => existing.clone(),
                _ => FxHashMap::default(),
            };
            let mut blend_tiles = match sector.properties.get("blend_tiles") {
                Some(Value::BlendOverrides(existing)) => existing.clone(),
                _ => FxHashMap::default(),
            };

            if ui.shift {
                tiles.remove(&server_ctx.rect_tile_id_3d);
                blend_tiles.remove(&server_ctx.rect_tile_id_3d);
            } else if let Some(source) = source {
                if server_ctx.rect_blend_preset == VertexBlendPreset::Solid {
                    tiles.insert(server_ctx.rect_tile_id_3d, source);
                    blend_tiles.remove(&server_ctx.rect_tile_id_3d);
                } else {
                    blend_tiles.insert(
                        server_ctx.rect_tile_id_3d,
                        (server_ctx.rect_blend_preset, source),
                    );
                    tiles.remove(&server_ctx.rect_tile_id_3d);
                }
            } else {
                return None;
            }

            if tiles.is_empty() {
                sector.properties.remove("tiles");
            } else {
                sector.properties.set("tiles", Value::TileOverrides(tiles));
            }
            if blend_tiles.is_empty() {
                sector.properties.remove("blend_tiles");
            } else {
                sector
                    .properties
                    .set("blend_tiles", Value::BlendOverrides(blend_tiles));
            }

            return Some(key);
        }

        None
    }

    fn compute_3d_tile(
        &mut self,
        coord: Vec2<i32>,
        map: &Map,
        ui: &mut TheUI,
        server_ctx: &mut ServerContext,
    ) {
        if let Some(render_view) = ui.get_render_view("PolyView") {
            let dim = *render_view.dim();

            let screen_uv = [
                coord.x as f32 / dim.width as f32,
                coord.y as f32 / dim.height as f32,
            ];

            let rusterix = RUSTERIX.read().unwrap();

            let rc = rusterix.scene_handler.vm.pick_geo_id_at_uv(
                dim.width as u32,
                dim.height as u32,
                screen_uv,
                false,
                false,
            );

            let mut found = false;
            server_ctx.rect_geometry_face_3d = None;

            if let Some((scenevm::GeoId::Sector(id), world_hit, _)) = rc {
                let mut best: Option<((i32, i32), f32)> = None;

                for surface in map.surfaces.values() {
                    if surface.sector_id != id {
                        continue;
                    }

                    let n = surface.plane.normal;
                    let n_len = n.magnitude();
                    if n_len <= 1e-6 {
                        continue;
                    }

                    // Choose the surface plane closest to the picked world hit.
                    // This avoids random sector-surface iteration order affecting tile coordinates.
                    let signed_dist = (world_hit - surface.plane.origin).dot(n / n_len);
                    let dist = signed_dist.abs();
                    let tile = surface.world_to_tile_local(world_hit, map);

                    if best
                        .as_ref()
                        .map(|(_, best_dist)| dist < *best_dist)
                        .unwrap_or(true)
                    {
                        best = Some((tile, dist));
                    }
                }

                if let Some((tile, _)) = best {
                    server_ctx.rect_tile_id_3d = tile;
                    server_ctx.rect_sector_id_3d = Some(id);
                    found = true;
                }
            }

            if !found
                && let Some((scenevm::GeoId::GeometryObject(id), world_hit, _)) = rc
                && let Some((face_index, tile)) =
                    Self::geometry_face_target_at_world(map, id, world_hit)
            {
                server_ctx.rect_tile_id_3d = tile;
                server_ctx.rect_geometry_face_3d = Some((id, face_index));
                server_ctx.rect_sector_id_3d = None;
                server_ctx.rect_terrain_id = None;
                found = true;
            }

            if let Some((scenevm::GeoId::Terrain(x, z), _, _)) = rc {
                server_ctx.rect_terrain_id = Some((x, z));
                server_ctx.rect_geometry_face_3d = None;
            } else {
                server_ctx.rect_terrain_id = None;
            }

            if !found {
                server_ctx.rect_sector_id_3d = None;
                server_ctx.rect_geometry_face_3d = None;
            }
        }
    }
}
