use crate::editor::{DOCKMANAGER, PALETTE, RUSTERIX, SCENEMANAGER};
use crate::prelude::*;
use MapEvent::*;
use ToolEvent::*;
use organicgraph::{OrganicBrushGraph, OrganicNodeKind};
use rusterix::PixelSource;
use rusterix::Surface;
use rusterix::map::organic::OrganicGrowthShape;
use scenevm::GeoId;
use std::collections::HashSet;

#[derive(Clone)]
struct OrganicBrushEval {
    valid: bool,
    output_mode: OrganicOutputMode,
    radius: f32,
    flow: f32,
    jitter: f32,
    depth: f32,
    cell_size: f32,
    circle_radius: f32,
    circle_softness: f32,
    canopy_lobes: i32,
    canopy_spread: f32,
    use_canopy_shape: bool,
    bush_height: f32,
    bush_layers: i32,
    bush_taper: f32,
    bush_breakup: f32,
    use_bush_shape: bool,
    line_length: f32,
    line_width: f32,
    line_softness: f32,
    use_line_shape: bool,
    scatter_count: i32,
    scatter_jitter: f32,
    height_depth: f32,
    height_falloff: f32,
    noise_scale: f32,
    noise_strength: f32,
    noise_seed: i32,
    channel: i32,
    material_source: Option<PixelSource>,
    palette_indices: Vec<u16>,
    palette_start: i32,
    palette_count: i32,
    palette_mode: i32,
    stroke_seed: i32,
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum OrganicOutputMode {
    Paint,
    Growth,
    Path,
}

impl OrganicBrushEval {
    fn uses_growth_output(&self) -> bool {
        matches!(self.output_mode, OrganicOutputMode::Growth)
    }

    fn uses_path_output(&self) -> bool {
        matches!(self.output_mode, OrganicOutputMode::Path)
    }

    fn supports_line_paint(&self) -> bool {
        self.use_line_shape && !self.uses_growth_output() && !self.uses_path_output()
    }

    fn supports_path_paint(&self) -> bool {
        self.use_line_shape && self.uses_path_output()
    }

    fn supports_growth_spawn(&self) -> bool {
        self.uses_growth_output() && (self.use_bush_shape || self.use_canopy_shape)
    }
}

pub struct OrganicTool {
    id: TheId,
    previous_dock: Option<String>,
    stroke_active: bool,
    stroke_changed: bool,
    stroke_prev_map: Option<Map>,
    stroke_work_map: Option<Map>,
    dirty_chunks: HashSet<(i32, i32)>,
    last_stroke_hit_pos: Option<Vec3<f32>>,
    stroke_seed: i32,
    vine_seq: i32,
}

impl Tool for OrganicTool {
    fn new() -> Self
    where
        Self: Sized,
    {
        Self {
            id: TheId::named("Organic Paint Tool"),
            previous_dock: None,
            stroke_active: false,
            stroke_changed: false,
            stroke_prev_map: None,
            stroke_work_map: None,
            dirty_chunks: HashSet::default(),
            last_stroke_hit_pos: None,
            stroke_seed: 0,
            vine_seq: 0,
        }
    }

    fn id(&self) -> TheId {
        self.id.clone()
    }

    fn info(&self) -> String {
        fl!("tool_organic")
    }

    fn icon_name(&self) -> String {
        str!("tree")
    }

    fn accel(&self) -> Option<char> {
        Some('O')
    }

    fn help_url(&self) -> Option<String> {
        Some("docs/creator/tools/overview".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::General;
                server_ctx.hover_cursor = None;
                server_ctx.hover_cursor_3d = None;
                self.cancel_stroke(map_from_project(project, server_ctx));
                let palette = project.palette.clone();
                if let Some(map) = project.get_map_mut(server_ctx) {
                    Self::normalize_default_graph_palette(map, &palette);
                }

                let current_dock = DOCKMANAGER.read().unwrap().dock.clone();
                if current_dock != "Organic" {
                    self.previous_dock = if current_dock.is_empty() {
                        None
                    } else {
                        Some(current_dock)
                    };
                }
                DOCKMANAGER.write().unwrap().set_dock(
                    "Organic".into(),
                    ui,
                    ctx,
                    project,
                    server_ctx,
                );
                Self::sync_active_graph_to_map(map_from_project(project, server_ctx));
                true
            }
            DeActivate => {
                server_ctx.curr_map_tool_type = MapToolType::General;
                server_ctx.hover_cursor = None;
                server_ctx.hover_cursor_3d = None;
                self.cancel_stroke(map_from_project(project, server_ctx));
                if DOCKMANAGER.read().unwrap().dock == "Organic"
                    && let Some(prev) = self.previous_dock.take()
                {
                    DOCKMANAGER
                        .write()
                        .unwrap()
                        .set_dock(prev, ui, ctx, project, server_ctx);
                }
                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> {
        if server_ctx.editor_view_mode == EditorViewMode::D2 {
            if matches!(map_event, MapUp(_) | MapEscape) {
                self.cancel_stroke(map);
            }
            return None;
        }

        match map_event {
            MapHover(_pos) => {
                server_ctx.hover_cursor = None;
            }
            MapClicked(_coord) => {
                let erase = ui.shift;
                self.begin_stroke_if_needed(map);
                eprintln!("{}", Self::hover_debug_text(map, server_ctx));
                if let Some(work_map) = self.stroke_work_map.as_mut() {
                    let changed = Self::apply_stroke(
                        work_map,
                        server_ctx,
                        erase,
                        self.stroke_seed,
                        &mut self.vine_seq,
                        &mut self.dirty_chunks,
                        &mut self.last_stroke_hit_pos,
                    );
                    if changed {
                        self.stroke_changed = true;
                        *map = work_map.clone();
                        return None;
                    }
                }
            }
            MapDragged(_coord) => {
                let erase = ui.shift;
                self.begin_stroke_if_needed(map);
                eprintln!("{}", Self::hover_debug_text(map, server_ctx));
                if let Some(work_map) = self.stroke_work_map.as_mut() {
                    let changed = Self::apply_stroke(
                        work_map,
                        server_ctx,
                        erase,
                        self.stroke_seed,
                        &mut self.vine_seq,
                        &mut self.dirty_chunks,
                        &mut self.last_stroke_hit_pos,
                    );
                    if changed {
                        self.stroke_changed = true;
                        *map = work_map.clone();
                        return None;
                    }
                }
            }
            MapUp(_) => {
                server_ctx.hover_cursor = None;
                return self.finish_stroke(map, server_ctx);
            }
            MapEscape => {
                server_ctx.hover_cursor = None;
                self.cancel_stroke(map);
            }
            MapDelete | MapKey(_) => {}
        }
        None
    }
}

impl OrganicTool {
    fn normalize_default_graph_palette(map: &mut Map, palette: &ThePalette) {
        let active = match map.properties.get("organic_brush_active_graph") {
            Some(Value::Id(id)) => Some(*id),
            _ => map.organic_brush_graphs.first().map(|(id, _)| *id),
        };
        let Some(id) = active else {
            return;
        };
        let Some(graph) = map.organic_brush_graphs.get_mut(&id) else {
            return;
        };
        if graph.name != "Default Paint Brush" {
            return;
        }

        let targets = [
            TheColor::from("#415835"),
            TheColor::from("#567b43"),
            TheColor::from("#7ea25a"),
            TheColor::from("#b2c776"),
        ];

        for node in &mut graph.nodes {
            if let OrganicNodeKind::PaletteColors {
                color_1,
                color_2,
                color_3,
                color_4,
                mode,
            } = &mut node.kind
            {
                *color_1 = palette.find_closest_color_index(&targets[0]).unwrap_or(0) as i32;
                *color_2 = palette.find_closest_color_index(&targets[1]).unwrap_or(0) as i32;
                *color_3 = palette.find_closest_color_index(&targets[2]).unwrap_or(0) as i32;
                *color_4 = palette.find_closest_color_index(&targets[3]).unwrap_or(0) as i32;
                *mode = 0;
            }
        }
    }

    fn preset_default_indices(name: &str) -> Option<[u16; 4]> {
        match name {
            "Default Paint Brush" | "Moss Brush" => Some([4, 8, 10, 12]),
            "Mud Brush" => Some([17, 18, 19, 13]),
            "Grass Brush" => Some([20, 21, 22, 11]),
            "Bush Brush" => Some([21, 23, 24, 25]),
            "Tree Brush" => Some([23, 21, 20, 25]),
            _ => None,
        }
    }

    fn preset_palette_targets(name: &str) -> Option<[TheColor; 4]> {
        match name {
            "Default Paint Brush" | "Moss Brush" => Some([
                TheColor::from("#415835"),
                TheColor::from("#567b43"),
                TheColor::from("#7ea25a"),
                TheColor::from("#b2c776"),
            ]),
            "Mud Brush" => Some([
                TheColor::from("#4f382a"),
                TheColor::from("#6e4f38"),
                TheColor::from("#8e7050"),
                TheColor::from("#b59a73"),
            ]),
            "Grass Brush" => Some([
                TheColor::from("#4f7a39"),
                TheColor::from("#4f7a39"),
                TheColor::from("#7ea653"),
                TheColor::from("#b2c776"),
            ]),
            "Bush Brush" => Some([
                TheColor::from("#3f6a43"),
                TheColor::from("#3f6a43"),
                TheColor::from("#638b55"),
                TheColor::from("#9fb36d"),
            ]),
            "Tree Brush" => Some([
                TheColor::from("#3f6a43"),
                TheColor::from("#638b55"),
                TheColor::from("#9fb36d"),
                TheColor::from("#b2c776"),
            ]),
            _ => None,
        }
    }

    fn maybe_normalize_preset_palette(
        graph_name: &str,
        palette_indices: &mut Vec<u16>,
        palette_mode: &mut i32,
    ) {
        let Some(defaults) = Self::preset_default_indices(graph_name) else {
            return;
        };
        if palette_indices.as_slice() != defaults {
            return;
        }
        let Some(targets) = Self::preset_palette_targets(graph_name) else {
            return;
        };
        let palette = PALETTE.read().unwrap().clone();
        *palette_indices = targets
            .iter()
            .map(|color| palette.find_closest_color_index(color).unwrap_or(0) as u16)
            .collect();
        *palette_mode = 0;
    }

    fn begin_stroke_if_needed(&mut self, map: &Map) {
        if self.stroke_active {
            return;
        }
        self.stroke_active = true;
        self.stroke_changed = false;
        self.stroke_prev_map = Some(map.clone());
        self.stroke_work_map = Some(map.clone());
        self.dirty_chunks.clear();
        self.last_stroke_hit_pos = None;
        self.stroke_seed = (map.changed as i32)
            .wrapping_mul(1_664_525)
            .wrapping_add(1_013_904_223);
        self.vine_seq = 0;
    }

    fn finish_stroke(
        &mut self,
        map: &mut Map,
        server_ctx: &ServerContext,
    ) -> Option<ProjectUndoAtom> {
        if !self.stroke_active {
            return None;
        }

        self.stroke_active = false;
        let prev = self.stroke_prev_map.take();
        let work = self.stroke_work_map.take();
        let changed = self.stroke_changed;
        self.stroke_changed = false;

        if changed && let (Some(prev), Some(mut work)) = (prev, work) {
            Self::finalize_vine_stroke_caps(&mut work, self.stroke_seed);
            if !self.dirty_chunks.is_empty() {
                let mut sm = SCENEMANAGER.write().unwrap();
                sm.update_map(work.clone());
                sm.add_dirty(self.dirty_chunks.iter().copied().collect());
                RUSTERIX.write().unwrap().set_dirty();
            }
            *map = work.clone();
            return Some(ProjectUndoAtom::MapEdit(
                server_ctx.pc,
                Box::new(prev),
                Box::new(work),
            ));
        }

        None
    }

    fn cancel_stroke(&mut self, map: &mut Map) {
        if self.stroke_active
            && let Some(prev) = self.stroke_prev_map.take()
        {
            *map = prev;
        }
        self.stroke_active = false;
        self.stroke_changed = false;
        self.stroke_work_map = None;
        self.dirty_chunks.clear();
        self.last_stroke_hit_pos = None;
    }

    fn apply_stroke(
        map: &mut Map,
        server_ctx: &ServerContext,
        erase: bool,
        stroke_seed: i32,
        vine_seq: &mut i32,
        dirty_chunks: &mut HashSet<(i32, i32)>,
        last_stroke_hit_pos: &mut Option<Vec3<f32>>,
    ) -> bool {
        let mut brush = Self::evaluate_brush(map);
        brush.stroke_seed = stroke_seed;
        let hit_pos = server_ctx.hover_cursor_3d.unwrap_or(server_ctx.geo_hit_pos);
        if (brush.supports_line_paint() || brush.supports_path_paint())
            && last_stroke_hit_pos.is_none()
        {
            *last_stroke_hit_pos = Some(hit_pos);
            return false;
        }
        let step = (brush.radius * brush.circle_radius * 0.45)
            .max(brush.cell_size * 0.75)
            .max(0.05);
        let start = last_stroke_hit_pos.unwrap_or(hit_pos);
        let delta = hit_pos - start;
        let dist = delta.magnitude();
        if (brush.supports_line_paint() || brush.supports_path_paint()) && dist > 0.0001 {
            Self::mark_dirty_chunks(dirty_chunks, map, start, brush.radius.max(brush.depth));
            Self::mark_dirty_chunks(dirty_chunks, map, hit_pos, brush.radius.max(brush.depth));
            let changed =
                Self::apply_line_segment(map, server_ctx, start, hit_pos, &brush, vine_seq, erase);
            *last_stroke_hit_pos = Some(hit_pos);
            return changed;
        }
        let steps = (dist / step).ceil().max(1.0) as usize;
        let mut changed = false;

        for i in 0..=steps {
            let t = if steps == 0 {
                1.0
            } else {
                i as f32 / steps as f32
            };
            let sample = start + delta * t;
            Self::mark_dirty_chunks(dirty_chunks, map, sample, brush.radius.max(brush.depth));
            let stroke_dir_world = (dist > 0.0001).then_some(delta / dist);
            changed |=
                Self::apply_stroke_at(map, server_ctx, sample, &brush, stroke_dir_world, erase);
        }

        *last_stroke_hit_pos = Some(hit_pos);
        changed
    }

    fn apply_line_segment(
        map: &mut Map,
        server_ctx: &ServerContext,
        start_pos: Vec3<f32>,
        end_pos: Vec3<f32>,
        brush: &OrganicBrushEval,
        vine_seq: &mut i32,
        erase: bool,
    ) -> bool {
        if let Some(surface) = server_ctx
            .active_detail_surface
            .as_ref()
            .or(server_ctx.hover_surface.as_ref())
        {
            if let Some(surface_ref) = map.surfaces.get(&surface.id)
                && Self::should_redirect_surface_to_terrain(map, surface_ref)
            {
                return false;
            }
            if brush.supports_path_paint() {
                return Self::paint_surface_path(
                    map,
                    server_ctx,
                    surface.id,
                    surface.sector_id,
                    start_pos,
                    end_pos,
                    brush.clone(),
                    vine_seq,
                    erase,
                );
            }
            return Self::paint_surface_line(
                map,
                server_ctx,
                surface.id,
                surface.sector_id,
                start_pos,
                end_pos,
                brush.clone(),
                erase,
            );
        }

        match server_ctx.geo_hit {
            Some(GeoId::Terrain(_, _)) => false,
            Some(GeoId::Sector(_)) => false,
            _ => false,
        }
    }

    fn apply_stroke_at(
        map: &mut Map,
        server_ctx: &ServerContext,
        hit_pos: Vec3<f32>,
        brush: &OrganicBrushEval,
        stroke_dir_world: Option<Vec3<f32>>,
        erase: bool,
    ) -> bool {
        if (brush.supports_line_paint() || brush.supports_path_paint())
            && stroke_dir_world.is_none()
        {
            return false;
        }
        if let Some(surface) = server_ctx
            .active_detail_surface
            .as_ref()
            .or(server_ctx.hover_surface.as_ref())
        {
            if let Some(surface_ref) = map.surfaces.get(&surface.id)
                && Self::should_redirect_surface_to_terrain(map, surface_ref)
            {
                return false;
            }
            return Self::paint_surface(
                map,
                server_ctx,
                surface.id,
                surface.sector_id,
                hit_pos,
                brush.clone(),
                stroke_dir_world,
                erase,
            );
        }

        match server_ctx.geo_hit {
            Some(GeoId::Terrain(_, _)) => false,
            Some(GeoId::Sector(_)) => false,
            _ => false,
        }
    }

    fn evaluate_brush(map: &Map) -> OrganicBrushEval {
        let mut out = OrganicBrushEval {
            valid: true,
            output_mode: OrganicOutputMode::Paint,
            radius: Self::output_radius(map),
            flow: Self::output_flow(map),
            jitter: Self::output_jitter(map),
            depth: Self::output_depth(map),
            cell_size: Self::output_cell_size(map),
            circle_radius: 1.0,
            circle_softness: 0.35,
            canopy_lobes: 6,
            canopy_spread: 0.5,
            use_canopy_shape: false,
            bush_height: 1.0,
            bush_layers: 4,
            bush_taper: 0.5,
            bush_breakup: 0.2,
            use_bush_shape: false,
            line_length: 1.2,
            line_width: 0.18,
            line_softness: 0.35,
            use_line_shape: false,
            scatter_count: 1,
            scatter_jitter: 0.0,
            height_depth: 1.0,
            height_falloff: 0.5,
            noise_scale: 0.3,
            noise_strength: 0.0,
            noise_seed: 1,
            channel: 0,
            material_source: None,
            palette_indices: vec![0],
            palette_start: 0,
            palette_count: 1,
            palette_mode: 0,
            stroke_seed: 0,
        };

        if let Some(id) = Self::active_graph_id(map)
            && let Some(graph) = map.organic_brush_graphs.get(&id)
        {
            let graph_name = graph.name.clone();
            if let Some(output_index) = Self::active_output_node_index(graph) {
                out.output_mode = if graph.nodes[output_index].kind.is_growth_output() {
                    OrganicOutputMode::Growth
                } else if graph.nodes[output_index].kind.is_path_output() {
                    OrganicOutputMode::Path
                } else {
                    OrganicOutputMode::Paint
                };
                if let Some(params) = graph.nodes[output_index].kind.output_params() {
                    out.radius = params.radius.max(0.05);
                    out.flow = params.flow.clamp(0.05, 1.0);
                    out.jitter = params.jitter.clamp(0.0, 1.0);
                    out.depth = params.depth.max(0.01);
                    out.cell_size = params.cell_size.max(0.05);
                    out.channel = params.channel.clamp(0, 3);
                    out.palette_start = params.palette_start.clamp(0, 255);
                    out.palette_count = params.palette_count.clamp(1, 16);
                    out.palette_mode = params.palette_mode.clamp(0, 2);
                }

                let shape_nodes = Self::collect_branch_nodes(graph, output_index, 0, |kind| {
                    matches!(
                        kind,
                        OrganicNodeKind::CircleMask { .. }
                            | OrganicNodeKind::BushShape { .. }
                            | OrganicNodeKind::CanopyShape { .. }
                            | OrganicNodeKind::LineShape { .. }
                            | OrganicNodeKind::Noise { .. }
                            | OrganicNodeKind::Scatter { .. }
                    )
                });
                let material_nodes = Self::collect_branch_nodes(graph, output_index, 1, |kind| {
                    matches!(
                        kind,
                        OrganicNodeKind::Material { .. }
                            | OrganicNodeKind::PaletteRange { .. }
                            | OrganicNodeKind::PaletteColors { .. }
                    )
                });
                let growth_nodes = Self::collect_branch_nodes(graph, output_index, 2, |kind| {
                    matches!(kind, OrganicNodeKind::HeightProfile { .. })
                });

                out.valid = !shape_nodes.is_empty();

                for node_index in shape_nodes {
                    match graph.nodes[node_index].kind {
                        OrganicNodeKind::CircleMask { radius, softness } => {
                            out.circle_radius = radius.max(0.05);
                            out.circle_softness = softness.clamp(0.0, 1.0);
                        }
                        OrganicNodeKind::BushShape {
                            radius,
                            height,
                            layers,
                            taper,
                            breakup,
                            softness,
                        } => {
                            out.use_bush_shape = true;
                            out.circle_radius = radius.max(0.05);
                            out.circle_softness = softness.clamp(0.0, 1.0);
                            out.bush_height = height.max(0.1);
                            out.bush_layers = layers.max(2);
                            out.bush_taper = taper.clamp(0.0, 1.0);
                            out.bush_breakup = breakup.clamp(0.0, 1.0);
                        }
                        OrganicNodeKind::CanopyShape {
                            radius,
                            lobes,
                            spread,
                            softness,
                        } => {
                            out.use_canopy_shape = true;
                            out.circle_radius = radius.max(0.05);
                            out.circle_softness = softness.clamp(0.0, 1.0);
                            out.canopy_lobes = lobes.max(3);
                            out.canopy_spread = spread.clamp(0.0, 1.0);
                        }
                        OrganicNodeKind::LineShape {
                            length,
                            width,
                            softness,
                        } => {
                            out.use_line_shape = true;
                            out.line_length = length.max(0.1);
                            out.line_width = width.max(0.02);
                            out.line_softness = softness.clamp(0.0, 1.0);
                        }
                        OrganicNodeKind::Noise {
                            scale,
                            strength,
                            seed,
                        } => {
                            out.noise_scale = scale.max(0.01);
                            out.noise_strength = strength.clamp(0.0, 1.0);
                            out.noise_seed = seed;
                        }
                        OrganicNodeKind::Scatter { count, jitter } => {
                            out.scatter_count = count.max(1);
                            out.scatter_jitter = jitter.clamp(0.0, 1.0);
                        }
                        _ => {}
                    }
                }
                for node_index in growth_nodes {
                    if let OrganicNodeKind::HeightProfile { depth, falloff } =
                        graph.nodes[node_index].kind
                    {
                        out.height_depth = depth.max(0.05);
                        out.height_falloff = falloff.clamp(0.0, 1.0);
                    }
                }
                for node_index in material_nodes {
                    match &graph.nodes[node_index].kind {
                        OrganicNodeKind::Material { channel } => {
                            out.channel = (*channel).clamp(0, 3);
                        }
                        OrganicNodeKind::PaletteRange { start, count, mode } => {
                            out.palette_start = (*start).clamp(0, 255);
                            out.palette_count = (*count).clamp(1, 16);
                            out.palette_mode = (*mode).clamp(0, 2);
                        }
                        OrganicNodeKind::PaletteColors {
                            color_1,
                            color_2,
                            color_3,
                            color_4,
                            mode,
                        } => {
                            out.palette_indices = vec![
                                (*color_1).clamp(0, 255) as u16,
                                (*color_2).clamp(0, 255) as u16,
                                (*color_3).clamp(0, 255) as u16,
                                (*color_4).clamp(0, 255) as u16,
                            ];
                            out.palette_mode = (*mode).clamp(0, 2);
                        }
                        _ => {}
                    }
                }
                if out.palette_indices.is_empty() {
                    out.palette_indices = if out.palette_count <= 1 {
                        vec![out.palette_start.clamp(0, 255) as u16]
                    } else {
                        (0..out.palette_count.clamp(1, 16))
                            .map(|i| (out.palette_start + i).clamp(0, 255) as u16)
                            .collect()
                    };
                }
                Self::maybe_normalize_preset_palette(
                    &graph_name,
                    &mut out.palette_indices,
                    &mut out.palette_mode,
                );
                out.material_source = Some(PixelSource::PaletteIndex(out.palette_indices[0]));

                if out.uses_growth_output() {
                    out.valid = out.supports_growth_spawn();
                }
            }
        }
        out
    }

    fn active_output_node_index(graph: &OrganicBrushGraph) -> Option<usize> {
        if let Some(index) = graph.selected_node
            && matches!(
                graph.nodes.get(index).map(|node| &node.kind),
                Some(kind) if kind.is_output()
            )
        {
            return Some(index);
        }

        graph
            .nodes
            .iter()
            .enumerate()
            .rev()
            .find_map(|(index, node)| node.kind.is_output().then_some(index))
    }

    fn collect_branch_nodes<F>(
        graph: &OrganicBrushGraph,
        dst_node: usize,
        dst_terminal: u8,
        allow: F,
    ) -> HashSet<usize>
    where
        F: Fn(&OrganicNodeKind) -> bool + Copy,
    {
        let mut visited = HashSet::default();
        Self::collect_upstream_nodes_filtered(graph, dst_node, dst_terminal, allow, &mut visited);
        visited
    }

    fn collect_upstream_nodes_filtered<F>(
        graph: &OrganicBrushGraph,
        dst_node: usize,
        dst_terminal: u8,
        allow: F,
        visited: &mut HashSet<usize>,
    ) where
        F: Fn(&OrganicNodeKind) -> bool + Copy,
    {
        for (from_node, _, to_node, to_terminal) in &graph.connections {
            if *to_node as usize != dst_node || *to_terminal != dst_terminal {
                continue;
            }
            let from_index = *from_node as usize;
            let Some(node) = graph.nodes.get(from_index) else {
                continue;
            };
            if !allow(&node.kind) || !visited.insert(from_index) {
                continue;
            }
            for input_index in 0..Self::organic_node_input_count(&node.kind) {
                Self::collect_upstream_nodes_filtered(
                    graph,
                    from_index,
                    input_index as u8,
                    allow,
                    visited,
                );
            }
        }
    }

    fn organic_node_input_count(kind: &OrganicNodeKind) -> usize {
        match kind {
            OrganicNodeKind::SurfaceInput => 0,
            OrganicNodeKind::CircleMask { .. } => 0,
            OrganicNodeKind::BushShape { .. } => 0,
            OrganicNodeKind::CanopyShape { .. } => 0,
            OrganicNodeKind::LineShape { .. } => 0,
            OrganicNodeKind::Noise { .. } => 1,
            OrganicNodeKind::Scatter { .. } => 2,
            OrganicNodeKind::HeightProfile { .. } => 1,
            OrganicNodeKind::PaletteRange { .. } => 0,
            OrganicNodeKind::PaletteColors { .. } => 0,
            OrganicNodeKind::Material { .. } => 1,
            OrganicNodeKind::OutputPaint { .. }
            | OrganicNodeKind::OutputGrowth { .. }
            | OrganicNodeKind::OutputPath { .. } => 3,
        }
    }

    fn hover_debug_text(map: &Map, server_ctx: &ServerContext) -> String {
        let hit_pos = server_ctx.hover_cursor_3d.unwrap_or(server_ctx.geo_hit_pos);
        if let Some(surface) = server_ctx
            .active_detail_surface
            .as_ref()
            .or(server_ctx.hover_surface.as_ref())
            && let Some(surface_ref) = map.surfaces.get(&surface.id)
        {
            let surface_normal = surface_ref.normal();
            if Self::should_redirect_surface_to_terrain(map, surface_ref) {
                return format!(
                    "Organic debug: legacy terrain redirect disabled for sector={} hit=({:.2},{:.2},{:.2})",
                    surface_ref.sector_id, hit_pos.x, hit_pos.y, hit_pos.z
                );
            }
            let signed_dist = (hit_pos - surface_ref.plane.origin).dot(surface_normal);
            let grow_positive = Self::resolve_surface_growth_side(
                signed_dist,
                surface_normal,
                server_ctx.hover_ray_dir_3d,
            );
            let side = if grow_positive { "+" } else { "-" };
            return format!(
                "Organic debug: surface sector={} n=({:.2},{:.2},{:.2}) d={:.3} side={} anchor={:.3} depth={:.3}",
                surface_ref.sector_id,
                surface_normal.x,
                surface_normal.y,
                surface_normal.z,
                signed_dist,
                side,
                signed_dist,
                Self::output_depth(map)
            );
        }

        match server_ctx.geo_hit {
            Some(GeoId::Terrain(_, _)) | None => format!(
                "Organic debug: no active surface hit=({:.2},{:.2},{:.2})",
                hit_pos.x, hit_pos.y, hit_pos.z
            ),
            Some(geo) => format!("Organic debug: hit {:?}", geo),
        }
    }

    fn should_redirect_surface_to_terrain(_map: &Map, _surface: &Surface) -> bool {
        false
    }

    fn resolve_surface_growth_side(
        signed_dist: f32,
        surface_normal: Vec3<f32>,
        hover_ray_dir: Option<Vec3<f32>>,
    ) -> bool {
        if signed_dist.abs() > 0.01 {
            signed_dist >= 0.0
        } else if let Some(ray_dir) = hover_ray_dir {
            surface_normal.dot(-ray_dir) >= 0.0
        } else {
            true
        }
    }

    fn paint_surface(
        map: &mut Map,
        server_ctx: &ServerContext,
        surface_id: Uuid,
        sector_id: u32,
        hit_pos: Vec3<f32>,
        brush: OrganicBrushEval,
        stroke_dir_world: Option<Vec3<f32>>,
        erase: bool,
    ) -> bool {
        let source = map
            .find_sector(sector_id)
            .and_then(|sector| sector.properties.get_default_source().cloned());
        let Some(local) = map
            .surfaces
            .get(&surface_id)
            .map(|surface| surface.uv_to_tile_local(surface.world_to_uv(hit_pos), map))
        else {
            return false;
        };
        let local_dir = stroke_dir_world.and_then(|world_dir| {
            map.surfaces.get(&surface_id).and_then(|surface| {
                let p0 = surface.uv_to_tile_local(surface.world_to_uv(hit_pos), map);
                let p1 = surface.uv_to_tile_local(surface.world_to_uv(hit_pos + world_dir), map);
                let delta = p1 - p0;
                let mag = delta.magnitude();
                (mag > 0.0001).then_some(delta / mag)
            })
        });
        let Some(surface) = map.surfaces.get_mut(&surface_id) else {
            return false;
        };
        let surface_normal = surface.normal();
        let signed_dist = (hit_pos - surface.plane.origin).dot(surface_normal);
        let grow_positive = Self::resolve_surface_growth_side(
            signed_dist,
            surface_normal,
            server_ctx.hover_ray_dir_3d,
        );
        let anchor_offset = signed_dist;
        if brush.supports_growth_spawn() {
            let changed = Self::paint_surface_bush(
                surface,
                local,
                anchor_offset,
                &brush,
                source,
                grow_positive,
                erase,
            );
            if changed {
                map.changed += 1;
            }
            return changed;
        }
        let layer = surface.organic_layer_for_cell_size_mut(brush.cell_size);
        let changed = Self::apply_graph_dabs(
            layer,
            local,
            local_dir,
            anchor_offset,
            &brush,
            source,
            grow_positive,
            erase,
        );
        if changed {
            map.changed += 1;
        }
        changed
    }

    fn paint_surface_line(
        map: &mut Map,
        server_ctx: &ServerContext,
        surface_id: Uuid,
        sector_id: u32,
        start_pos: Vec3<f32>,
        end_pos: Vec3<f32>,
        brush: OrganicBrushEval,
        erase: bool,
    ) -> bool {
        let source = map
            .find_sector(sector_id)
            .and_then(|sector| sector.properties.get_default_source().cloned());
        let Some((start_local, end_local)) = map.surfaces.get(&surface_id).map(|surface| {
            (
                surface.uv_to_tile_local(surface.world_to_uv(start_pos), map),
                surface.uv_to_tile_local(surface.world_to_uv(end_pos), map),
            )
        }) else {
            return false;
        };
        let Some(surface) = map.surfaces.get_mut(&surface_id) else {
            return false;
        };
        let surface_normal = surface.normal();
        let signed_dist = (end_pos - surface.plane.origin).dot(surface_normal);
        let grow_positive = Self::resolve_surface_growth_side(
            signed_dist,
            surface_normal,
            server_ctx.hover_ray_dir_3d,
        );
        let anchor_offset = signed_dist;
        if brush.uses_growth_output() {
            return false;
        }
        let layer = surface.organic_layer_for_cell_size_mut(brush.cell_size);
        let changed = Self::apply_graph_line(
            layer,
            start_local,
            end_local,
            anchor_offset,
            &brush,
            source,
            grow_positive,
            erase,
        );
        if changed {
            map.changed += 1;
        }
        changed
    }

    fn paint_surface_path(
        map: &mut Map,
        server_ctx: &ServerContext,
        surface_id: Uuid,
        sector_id: u32,
        start_pos: Vec3<f32>,
        end_pos: Vec3<f32>,
        brush: OrganicBrushEval,
        vine_seq: &mut i32,
        erase: bool,
    ) -> bool {
        let source = map
            .find_sector(sector_id)
            .and_then(|sector| sector.properties.get_default_source().cloned());
        let Some((start_local, end_local)) = map.surfaces.get(&surface_id).map(|surface| {
            (
                surface.uv_to_tile_local(surface.world_to_uv(start_pos), map),
                surface.uv_to_tile_local(surface.world_to_uv(end_pos), map),
            )
        }) else {
            return false;
        };
        let Some(surface) = map.surfaces.get_mut(&surface_id) else {
            return false;
        };
        let surface_normal = surface.normal();
        let signed_dist = (end_pos - surface.plane.origin).dot(surface_normal);
        let grow_positive = Self::resolve_surface_growth_side(
            signed_dist,
            surface_normal,
            server_ctx.hover_ray_dir_3d,
        );
        let changed = Self::paint_vine_segments(
            &mut surface.organic_vine_strokes,
            start_local,
            end_local,
            signed_dist,
            &brush,
            source,
            vine_seq,
            grow_positive,
            erase,
        );
        if changed {
            map.changed += 1;
        }
        changed
    }

    fn paint_vine_segments(
        strokes: &mut Vec<rusterix::OrganicVineStroke>,
        start: Vec2<f32>,
        end: Vec2<f32>,
        anchor_offset: f32,
        brush: &OrganicBrushEval,
        source: Option<PixelSource>,
        vine_seq: &mut i32,
        grow_positive: bool,
        erase: bool,
    ) -> bool {
        if (end - start).magnitude() <= 0.0001 {
            return false;
        }
        let width = (brush.line_width * brush.radius).max(0.05);
        let depth = (brush.depth * brush.height_depth).max(0.02);
        if erase {
            let before = strokes.len();
            let threshold = width * 1.5;
            strokes.retain(|stroke| {
                let a = Self::point_segment_distance(stroke.start, start, end);
                let b = Self::point_segment_distance(stroke.end, start, end);
                let c = Self::point_segment_distance(start, stroke.start, stroke.end);
                let d = Self::point_segment_distance(end, stroke.start, stroke.end);
                a.max(b).min(c.max(d)) > threshold
            });
            return before != strokes.len();
        }
        let delta = end - start;
        let dist = delta.magnitude();
        let dir = delta / dist;
        let stroke_source =
            Self::resolve_brush_source(brush, source.clone(), Some(brush.stroke_seed), start);
        strokes.push(rusterix::OrganicVineStroke {
            stroke_id: brush.stroke_seed,
            seq: *vine_seq,
            start,
            end: start + dir * dist,
            anchor_offset,
            width,
            depth,
            channel: brush.channel,
            source: stroke_source,
            grow_positive,
            cap_start: false,
            cap_end: false,
        });
        *vine_seq += 1;
        true
    }

    fn finalize_vine_stroke_caps(map: &mut Map, stroke_seed: i32) {
        enum VineLoc {
            Surface(Uuid, usize),
        }

        let mut first: Option<(VineLoc, i32)> = None;
        let mut last: Option<(VineLoc, i32)> = None;

        for (surface_id, surface) in &map.surfaces {
            for (idx, stroke) in surface.organic_vine_strokes.iter().enumerate() {
                if stroke.stroke_id != stroke_seed {
                    continue;
                }
                if first.as_ref().is_none_or(|(_, seq)| stroke.seq < *seq) {
                    first = Some((VineLoc::Surface(*surface_id, idx), stroke.seq));
                }
                if last.as_ref().is_none_or(|(_, seq)| stroke.seq > *seq) {
                    last = Some((VineLoc::Surface(*surface_id, idx), stroke.seq));
                }
            }
        }
        for surface in map.surfaces.values_mut() {
            for stroke in &mut surface.organic_vine_strokes {
                if stroke.stroke_id == stroke_seed {
                    stroke.cap_start = false;
                    stroke.cap_end = false;
                }
            }
        }

        if let Some((loc, _)) = first {
            match loc {
                VineLoc::Surface(id, idx) => {
                    if let Some(surface) = map.surfaces.get_mut(&id)
                        && let Some(stroke) = surface.organic_vine_strokes.get_mut(idx)
                    {
                        stroke.cap_start = true;
                    }
                }
            }
        }
        if let Some((loc, _)) = last {
            match loc {
                VineLoc::Surface(id, idx) => {
                    if let Some(surface) = map.surfaces.get_mut(&id)
                        && let Some(stroke) = surface.organic_vine_strokes.get_mut(idx)
                    {
                        stroke.cap_end = true;
                    }
                }
            }
        }
    }

    fn point_segment_distance(p: Vec2<f32>, a: Vec2<f32>, b: Vec2<f32>) -> f32 {
        let ab = b - a;
        let len_sq = ab.magnitude_squared();
        if len_sq <= f32::EPSILON {
            return (p - a).magnitude();
        }
        let t = ((p - a).dot(ab) / len_sq).clamp(0.0, 1.0);
        (p - (a + ab * t)).magnitude()
    }

    fn paint_surface_bush(
        surface: &mut Surface,
        center: Vec2<f32>,
        anchor_offset: f32,
        brush: &OrganicBrushEval,
        source: Option<PixelSource>,
        grow_positive: bool,
        erase: bool,
    ) -> bool {
        Self::paint_bush_clusters(
            &mut surface.organic_bush_clusters,
            center,
            anchor_offset,
            brush,
            source,
            grow_positive,
            erase,
        )
    }

    fn paint_bush_clusters(
        clusters: &mut Vec<rusterix::OrganicBushCluster>,
        center: Vec2<f32>,
        anchor_offset: f32,
        brush: &OrganicBrushEval,
        source: Option<PixelSource>,
        grow_positive: bool,
        erase: bool,
    ) -> bool {
        let radius = (brush.radius * brush.circle_radius).max(0.08);
        let height = (brush.depth * brush.bush_height).max(0.12);
        if erase {
            let before = clusters.len();
            let threshold = radius * 1.25;
            clusters.retain(|cluster| (cluster.center - center).magnitude() > threshold);
            return before != clusters.len();
        }

        let resolved_source =
            Self::resolve_brush_source(brush, source, Some(clusters.len() as i32), center);
        clusters.push(rusterix::OrganicBushCluster {
            center,
            anchor_offset,
            radius,
            height,
            layers: brush.bush_layers.max(2),
            taper: brush.bush_taper,
            breakup: brush.bush_breakup,
            channel: brush.channel,
            source: resolved_source,
            grow_positive,
            shape: if brush.use_canopy_shape {
                OrganicGrowthShape::Tree
            } else {
                OrganicGrowthShape::Bush
            },
            canopy_lobes: brush.canopy_lobes.max(3),
            canopy_spread: brush.canopy_spread.clamp(0.0, 1.0),
            trunk_height: if brush.use_canopy_shape {
                (height * (0.52 + brush.canopy_spread * 0.12)).max(0.18)
            } else {
                0.0
            },
            trunk_radius: if brush.use_canopy_shape {
                (radius * (0.12 + (1.0 - brush.canopy_spread) * 0.05)).max(0.03)
            } else {
                0.0
            },
        });
        true
    }

    fn output_depth(map: &Map) -> f32 {
        if let Some(id) = Self::active_graph_id(map)
            && let Some(graph) = map.organic_brush_graphs.get(&id)
        {
            for node in graph.nodes.iter().rev() {
                if let Some(params) = node.kind.output_params() {
                    return params.depth.max(0.01);
                }
            }
        }
        map.properties
            .get_float_default("organic_brush_depth", 0.45)
    }

    fn output_radius(map: &Map) -> f32 {
        if let Some(id) = Self::active_graph_id(map)
            && let Some(graph) = map.organic_brush_graphs.get(&id)
        {
            for node in graph.nodes.iter().rev() {
                if let Some(params) = node.kind.output_params() {
                    return params.radius.max(0.05);
                }
            }
        }
        map.properties
            .get_float_default("organic_brush_radius", 0.6)
    }

    fn output_flow(map: &Map) -> f32 {
        if let Some(id) = Self::active_graph_id(map)
            && let Some(graph) = map.organic_brush_graphs.get(&id)
        {
            for node in graph.nodes.iter().rev() {
                if let Some(params) = node.kind.output_params() {
                    return params.flow.clamp(0.05, 1.0);
                }
            }
        }
        map.properties.get_float_default("organic_brush_flow", 1.0)
    }

    fn output_jitter(map: &Map) -> f32 {
        if let Some(id) = Self::active_graph_id(map)
            && let Some(graph) = map.organic_brush_graphs.get(&id)
        {
            for node in graph.nodes.iter().rev() {
                if let Some(params) = node.kind.output_params() {
                    return params.jitter.clamp(0.0, 1.0);
                }
            }
        }
        map.properties
            .get_float_default("organic_brush_jitter", 0.15)
    }

    fn resolve_brush_source(
        brush: &OrganicBrushEval,
        host_source: Option<PixelSource>,
        variant: Option<i32>,
        pos: Vec2<f32>,
    ) -> Option<PixelSource> {
        if brush.palette_indices.len() <= 1 {
            return brush.material_source.clone().or(host_source);
        }
        let count = brush.palette_indices.len() as i32;
        let pick = match brush.palette_mode {
            1 => {
                if count <= 1 {
                    0
                } else {
                    let px = (pos.x * 10.0).round() as i32;
                    let py = (pos.y * 10.0).round() as i32;
                    let seed = px.wrapping_mul(374_761_393)
                        ^ py.wrapping_mul(668_265_263)
                        ^ brush.noise_seed;
                    if seed.rem_euclid(5) == 0 {
                        1 + seed.rem_euclid((count - 1).max(1))
                    } else {
                        0
                    }
                }
            }
            2 => {
                let seed = variant.unwrap_or(0) ^ brush.noise_seed;
                seed.rem_euclid(count)
            }
            _ => 0,
        };
        Some(PixelSource::PaletteIndex(
            brush.palette_indices[pick as usize],
        ))
    }

    fn output_cell_size(map: &Map) -> f32 {
        let active_id = match map.properties.get("organic_brush_active_graph") {
            Some(Value::Id(id)) => Some(*id),
            _ => None,
        };
        if let Some(id) = active_id
            && let Some(graph) = map.organic_brush_graphs.get(&id)
        {
            for node in graph.nodes.iter().rev() {
                if let Some(params) = node.kind.output_params() {
                    return params.cell_size.max(0.05);
                }
            }
        }
        0.25
    }

    fn active_graph_id(map: &Map) -> Option<Uuid> {
        match map.properties.get("organic_brush_active_graph") {
            Some(Value::Id(id)) => Some(*id),
            _ => None,
        }
    }

    fn apply_graph_dabs(
        layer: &mut rusterix::OrganicVolumeLayer,
        center: Vec2<f32>,
        stroke_dir_local: Option<Vec2<f32>>,
        anchor_offset: f32,
        brush: &OrganicBrushEval,
        host_source: Option<PixelSource>,
        grow_positive: bool,
        erase: bool,
    ) -> bool {
        if !brush.valid {
            return false;
        }
        let scatter_count = brush.scatter_count.max(1) as usize;
        let base_radius = (brush.radius * brush.circle_radius).max(layer.cell_size * 0.5);
        let base_depth = (brush.depth * brush.height_depth).max(layer.cell_size * 0.28);
        let shallow_spread = base_depth <= layer.cell_size * 0.95 && scatter_count > 1;
        let dab_radius = if scatter_count > 1 {
            let scale = if shallow_spread { 0.96 } else { 0.82 };
            (base_radius * scale).max(layer.cell_size * 0.5)
        } else {
            base_radius
        };
        let mut changed = false;
        let line_dir = stroke_dir_local.unwrap_or(Vec2::new(1.0, 0.0)).normalized();

        if brush.use_bush_shape && !brush.use_line_shape {
            let bush_radius = base_radius;
            let bush_height = (brush.depth * brush.bush_height).max(layer.cell_size * 0.6);
            let layer_count = brush.bush_layers.max(2) as usize;
            for index in 0..layer_count {
                let t = if layer_count <= 1 {
                    0.0
                } else {
                    index as f32 / (layer_count - 1) as f32
                };
                let noise_seed = center + Vec2::new(index as f32 * 0.37, index as f32 * 0.23);
                let noise = Self::organic_noise(noise_seed, brush);
                let source = Self::resolve_brush_source(
                    brush,
                    host_source.clone(),
                    Some(index as i32),
                    center,
                );
                let radius_scale = (1.0 - t * brush.bush_taper * 0.65).max(0.28);
                let lateral = bush_radius * brush.bush_breakup * 0.32;
                let lateral_offset = Vec2::new(
                    ((index as f32 * 1.618) + brush.jitter).cos(),
                    ((index as f32 * 1.618) + brush.jitter).sin(),
                ) * lateral
                    * (0.6 + noise * 0.4);
                let layer_center = center + lateral_offset;
                let layer_radius =
                    (bush_radius * radius_scale * (1.0 + noise * 0.10)).max(layer.cell_size * 0.65);
                let layer_depth = (base_depth * (0.98 - t * 0.16)).max(layer.cell_size * 0.28);
                let layer_anchor = anchor_offset + bush_height * t * 0.72;
                let layer_flow = (brush.flow / layer_count as f32).clamp(0.12, 0.55);
                changed |= if erase {
                    layer.erase_sphere(
                        layer_center,
                        layer_radius,
                        layer_anchor,
                        layer_depth,
                        brush.circle_softness,
                        brush.height_falloff,
                        grow_positive,
                    )
                } else {
                    layer.paint_sphere(
                        layer_center,
                        layer_radius,
                        layer_anchor,
                        layer_depth,
                        brush.circle_softness,
                        brush.height_falloff,
                        layer_flow,
                        brush.channel,
                        source,
                        grow_positive,
                    )
                };
            }
            return changed;
        }

        if brush.use_bush_shape && !brush.use_line_shape {
            let source = Self::resolve_brush_source(brush, host_source.clone(), Some(0), center);
            let bush_radius = base_radius;
            let bush_height = (brush.depth * brush.bush_height).max(layer.cell_size * 0.8);
            changed |= if erase {
                layer.erase_sphere(
                    center,
                    bush_radius,
                    anchor_offset,
                    bush_height,
                    brush.circle_softness,
                    brush.height_falloff,
                    grow_positive,
                )
            } else {
                layer.paint_bush_cluster(
                    center,
                    bush_radius,
                    bush_height,
                    anchor_offset,
                    brush.bush_layers,
                    brush.bush_taper,
                    brush.bush_breakup,
                    brush.circle_softness,
                    brush.flow.clamp(0.1, 1.0),
                    brush.channel,
                    source,
                    grow_positive,
                )
            };
            return changed;
        }

        if brush.use_canopy_shape && !brush.use_line_shape {
            let canopy_radius = base_radius;
            let center_source =
                Self::resolve_brush_source(brush, host_source.clone(), Some(-1), center);
            let center_depth = (base_depth * 0.95).max(layer.cell_size * 0.25);
            changed |= if erase {
                layer.erase_sphere(
                    center,
                    canopy_radius * 0.95,
                    anchor_offset,
                    center_depth,
                    brush.circle_softness,
                    brush.height_falloff,
                    grow_positive,
                )
            } else {
                layer.paint_sphere(
                    center,
                    canopy_radius * 0.95,
                    anchor_offset,
                    center_depth,
                    brush.circle_softness,
                    brush.height_falloff,
                    brush.flow.clamp(0.12, 1.0),
                    brush.channel,
                    center_source,
                    grow_positive,
                )
            };

            let lobe_count = brush.canopy_lobes.max(3) as usize;
            let lobe_ring = canopy_radius * (0.28 + brush.canopy_spread * 0.34);
            let lobe_radius =
                (canopy_radius * (0.72 - brush.canopy_spread * 0.16)).max(layer.cell_size * 0.65);
            for index in 0..lobe_count {
                let angle = index as f32 / lobe_count as f32 * std::f32::consts::TAU
                    + brush.jitter * std::f32::consts::PI;
                let offset = Vec2::new(angle.cos(), angle.sin()) * lobe_ring
                    + Self::scatter_offset(index, lobe_count, brush, canopy_radius * 0.35);
                let lobe_center = center + offset;
                let noise = Self::organic_noise(lobe_center, brush);
                let source = Self::resolve_brush_source(
                    brush,
                    host_source.clone(),
                    Some(index as i32),
                    lobe_center,
                );
                let lobe_depth = (base_depth * (0.82 + noise * 0.18)).max(layer.cell_size * 0.22);
                let lobe_size = (lobe_radius * (1.0 + noise * 0.12)).max(layer.cell_size * 0.55);
                changed |= if erase {
                    layer.erase_sphere(
                        lobe_center,
                        lobe_size,
                        anchor_offset,
                        lobe_depth,
                        brush.circle_softness,
                        brush.height_falloff,
                        grow_positive,
                    )
                } else {
                    layer.paint_sphere(
                        lobe_center,
                        lobe_size,
                        anchor_offset,
                        lobe_depth,
                        brush.circle_softness,
                        brush.height_falloff,
                        (brush.flow / lobe_count as f32).clamp(0.06, 0.4),
                        brush.channel,
                        source,
                        grow_positive,
                    )
                };
            }
            return changed;
        }

        for index in 0..scatter_count {
            let offset = if brush.use_line_shape {
                Vec2::zero()
            } else {
                Self::scatter_offset(index, scatter_count, brush, base_radius)
            };
            let dab_center = center + offset;
            let noise = Self::organic_noise(dab_center, brush);
            let source = Self::resolve_brush_source(
                brush,
                host_source.clone(),
                Some(index as i32),
                dab_center,
            );
            let dab_flow = if scatter_count > 1 {
                if shallow_spread {
                    (brush.flow / (scatter_count as f32).sqrt()).clamp(0.08, 1.0)
                } else {
                    (brush.flow / scatter_count as f32).clamp(0.06, 1.0)
                }
            } else {
                brush.flow
            };
            let dab_depth = if scatter_count > 1 {
                let scale = if shallow_spread { 1.0 } else { 0.92 };
                (base_depth * scale).max(layer.cell_size * 0.24)
            } else {
                base_depth
            } * (1.0 + noise * 0.42);
            let dab_radius = (dab_radius * (1.0 + noise * 0.32)).max(layer.cell_size * 0.5);

            if brush.use_line_shape {
                let half_len = (brush.line_length * brush.radius * 0.5).max(layer.cell_size * 0.6);
                let width = (brush.line_width * brush.radius).max(layer.cell_size * 0.55);
                let steps = ((half_len * 2.0) / (width * 0.65).max(layer.cell_size * 0.45))
                    .ceil()
                    .max(1.0) as usize;
                for step_index in 0..=steps {
                    let t = if steps == 0 {
                        0.0
                    } else {
                        step_index as f32 / steps as f32
                    };
                    let along = -half_len + t * (half_len * 2.0);
                    let line_center = dab_center + line_dir * along;
                    let line_changed = if erase {
                        layer.erase_sphere(
                            line_center,
                            width,
                            anchor_offset,
                            dab_depth,
                            brush.line_softness,
                            brush.height_falloff,
                            grow_positive,
                        )
                    } else {
                        layer.paint_sphere(
                            line_center,
                            width,
                            anchor_offset,
                            dab_depth,
                            brush.line_softness,
                            brush.height_falloff,
                            (dab_flow * 1.35 / (steps as f32 + 1.0)).clamp(0.04, 0.4),
                            brush.channel,
                            source.clone(),
                            grow_positive,
                        )
                    };
                    changed |= line_changed;
                }
            } else {
                let dab_changed = if erase {
                    layer.erase_sphere(
                        dab_center,
                        dab_radius,
                        anchor_offset,
                        dab_depth,
                        brush.circle_softness,
                        brush.height_falloff,
                        grow_positive,
                    )
                } else {
                    layer.paint_sphere(
                        dab_center,
                        dab_radius,
                        anchor_offset,
                        dab_depth,
                        brush.circle_softness,
                        brush.height_falloff,
                        dab_flow,
                        brush.channel,
                        source,
                        grow_positive,
                    )
                };
                changed |= dab_changed;
            }
        }

        changed
    }

    fn apply_graph_line(
        layer: &mut rusterix::OrganicVolumeLayer,
        start: Vec2<f32>,
        end: Vec2<f32>,
        anchor_offset: f32,
        brush: &OrganicBrushEval,
        host_source: Option<PixelSource>,
        grow_positive: bool,
        erase: bool,
    ) -> bool {
        if !brush.valid {
            return false;
        }
        let delta = end - start;
        let dist = delta.magnitude();
        if dist <= 0.0001 {
            return Self::apply_graph_dabs(
                layer,
                end,
                None,
                anchor_offset,
                brush,
                host_source,
                grow_positive,
                erase,
            );
        }

        let width = (brush.line_width * brush.radius).max(layer.cell_size * 0.55);
        let line_start = start;
        let line_end = end;
        let total = (line_end - line_start).magnitude();
        if total <= 0.0001 {
            return Self::apply_graph_dabs(
                layer,
                end,
                Some(delta / dist),
                anchor_offset,
                brush,
                host_source,
                grow_positive,
                erase,
            );
        }
        let midpoint = line_start + (line_end - line_start) * 0.5;
        let noise = Self::organic_noise(midpoint, brush);
        let source = Self::resolve_brush_source(brush, host_source, Some(0), midpoint);
        let depth =
            (brush.depth * brush.height_depth).max(layer.cell_size * 0.26) * (1.0 + noise * 0.28);
        let radius = (width * (1.0 + noise * 0.14)).max(layer.cell_size * 0.55);

        if erase {
            layer.erase_capsule(
                line_start,
                line_end,
                radius,
                anchor_offset,
                depth,
                brush.line_softness,
                brush.height_falloff,
                grow_positive,
            )
        } else {
            layer.paint_capsule(
                line_start,
                line_end,
                radius,
                anchor_offset,
                depth,
                brush.line_softness,
                brush.height_falloff,
                brush.flow.clamp(0.08, 1.0),
                brush.channel,
                source,
                grow_positive,
            )
        }
    }

    fn sync_active_graph_to_map(map: &mut Map) {
        let active = match map.properties.get("organic_brush_active_graph") {
            Some(Value::Id(id)) if map.organic_brush_graphs.contains_key(id) => Some(*id),
            _ => map.organic_brush_graphs.first().map(|(id, _)| *id),
        };
        if let Some(id) = active {
            map.properties
                .set("organic_brush_active_graph", Value::Id(id));
        }
    }

    fn organic_noise(pos: Vec2<f32>, brush: &OrganicBrushEval) -> f32 {
        if brush.noise_strength <= 0.001 {
            return 0.0;
        }
        let scale = brush.noise_scale.max(0.01);
        let seed = brush.noise_seed as f32 * 0.137;
        let value =
            ((pos.x * scale + seed).sin() * 12.9898 + (pos.y * scale - seed).cos() * 78.233).sin();
        value * brush.noise_strength.clamp(0.0, 1.0)
    }

    fn scatter_offset(
        index: usize,
        count: usize,
        brush: &OrganicBrushEval,
        base_radius: f32,
    ) -> Vec2<f32> {
        if count <= 1 {
            return Vec2::zero();
        }
        let angle = (index as f32 * 2.3999632) + brush.jitter * 3.14159;
        let ring = ((index + 1) as f32 / count as f32).sqrt();
        let amount = base_radius * brush.scatter_jitter * (0.15 + brush.jitter * 0.35);
        Vec2::new(angle.cos(), angle.sin()) * (ring * amount)
    }

    fn mark_dirty_chunks(
        dirty_chunks: &mut HashSet<(i32, i32)>,
        _map: &Map,
        hit_pos: Vec3<f32>,
        radius: f32,
    ) {
        let chunk_size = 32;
        let reach = radius.max(1.0) + 1.0;
        let min_x = ((hit_pos.x - reach).floor() as i32).div_euclid(chunk_size) * chunk_size;
        let max_x = ((hit_pos.x + reach).ceil() as i32).div_euclid(chunk_size) * chunk_size;
        let min_z = ((hit_pos.z - reach).floor() as i32).div_euclid(chunk_size) * chunk_size;
        let max_z = ((hit_pos.z + reach).ceil() as i32).div_euclid(chunk_size) * chunk_size;

        let mut cz = min_z;
        while cz <= max_z {
            let mut cx = min_x;
            while cx <= max_x {
                dirty_chunks.insert((cx, cz));
                cx += chunk_size;
            }
            cz += chunk_size;
        }
    }
}

fn map_from_project<'a>(project: &'a mut Project, server_ctx: &ServerContext) -> &'a mut Map {
    project
        .get_map_mut(server_ctx)
        .expect("organic tool requires an active map")
}
