use crate::ecs::{EditorMode, EditorWorld};
use crate::systems::input;
use crate::systems::mode::is_descendant_of;
use nightshade::ecs::input::resources::MouseState;
use nightshade::ecs::transform::queries::{chain_from_root_to_leaf, find_group_root};
use nightshade::ecs::ui::types::Rect;
use nightshade::prelude::*;

const CLICK_DRAG_THRESHOLD: f32 = 4.0;

pub fn update(editor_world: &mut EditorWorld, world: &mut World) {
    handle_selection_shortcuts(editor_world, world);

    let mouse = *nightshade::ecs::input::access::mouse_for_active(world);
    let just_pressed = mouse.state.contains(MouseState::LEFT_JUST_PRESSED);
    let just_released = mouse.state.contains(MouseState::LEFT_JUST_RELEASED);
    let held = mouse.state.contains(MouseState::LEFT_CLICKED);
    let in_viewport = input::mouse_in_active_viewport(world);
    let ui_block = input::ui_capturing(world);
    let hud_block = world.resources.user_interface.hud_wants_pointer;

    world.resources.user_interface.gizmos.selection_marquee = None;

    if just_pressed {
        editor_world.resources.picking.press_position = if in_viewport && !ui_block && !hud_block {
            Some(mouse.position)
        } else {
            None
        };
        editor_world.resources.picking.is_dragging = false;
    }

    if held
        && let Some(start) = editor_world.resources.picking.press_position
        && nalgebra_glm::distance(&start, &mouse.position) > CLICK_DRAG_THRESHOLD
    {
        editor_world.resources.picking.is_dragging = true;
    }

    let shift_held = world
        .resources
        .input
        .keyboard
        .is_key_pressed(KeyCode::ShiftLeft)
        || world
            .resources
            .input
            .keyboard
            .is_key_pressed(KeyCode::ShiftRight);
    if held
        && shift_held
        && editor_world.resources.picking.is_dragging
        && !ui_block
        && !gizmo_drag_active(world)
        && let Some(start) = editor_world.resources.picking.press_position
    {
        marquee_select(editor_world, world, start, mouse.position);
    }

    let mut pending_alt = false;
    let mut pending_shift = false;
    let mut requested_pick = false;
    if just_released
        && let Some(start) = editor_world.resources.picking.press_position.take()
        && !editor_world.resources.picking.is_dragging
        && nalgebra_glm::distance(&start, &mouse.position) <= CLICK_DRAG_THRESHOLD
    {
        let render_scale = world
            .resources
            .render_settings
            .render_scale
            .clamp(0.25, 4.0);
        let pick_pos = world
            .resources
            .window
            .active_viewport_rect
            .and_then(|rect| {
                let texture_width = ((rect.width * render_scale).round() as u32).max(1);
                let texture_height = ((rect.height * render_scale).round() as u32).max(1);
                surface_pick_coords(mouse.position, Some(rect), (texture_width, texture_height))
            });
        if let Some((x, y)) = pick_pos {
            world.resources.gpu_picking.request_pick(x, y);
            let keyboard = &world.resources.input.keyboard;
            pending_alt = keyboard.is_key_pressed(KeyCode::AltLeft)
                || keyboard.is_key_pressed(KeyCode::AltRight);
            pending_shift = keyboard.is_key_pressed(KeyCode::ShiftLeft)
                || keyboard.is_key_pressed(KeyCode::ShiftRight);
            requested_pick = true;
        }
    }

    if just_released {
        editor_world.resources.picking.is_dragging = false;
    }

    if requested_pick {
        editor_world.resources.picking.pending_alt = pending_alt;
        editor_world.resources.picking.pending_shift = pending_shift;
    }

    if let Some(result) = world.resources.gpu_picking.take_result() {
        let alt_held = std::mem::take(&mut editor_world.resources.picking.pending_alt);
        let shift_held = std::mem::take(&mut editor_world.resources.picking.pending_shift);
        let picked = result
            .entity_id
            .and_then(|id| {
                world
                    .core
                    .query_entities(nightshade::ecs::world::RENDER_MESH)
                    .find(|entity| entity.id == id)
            })
            .map(|entity| {
                editor_world
                    .resources
                    .camera_gizmos
                    .camera_for_proxy(entity)
                    .unwrap_or(entity)
            })
            .filter(|entity| !editor_world.resources.editor_scene.is_scaffolding(*entity));
        let (new_selection, expand_tree) = match picked {
            None => {
                reset_cycle(editor_world);
                (None, false)
            }
            Some(leaf) => resolve_selection(editor_world, world, leaf, alt_held),
        };
        if shift_held {
            if let Some(entity) = new_selection {
                crate::systems::selection::toggle(editor_world, entity);
            }
        } else {
            crate::systems::selection::set_primary(editor_world, new_selection);
        }
        if expand_tree {
            let focus_target = editor_world.resources.ui.selected_entity;
            focus_tree_on(editor_world, world, focus_target);
        }
    }

    sync_outline(editor_world, world);
}

fn handle_selection_shortcuts(editor_world: &mut EditorWorld, world: &mut World) {
    let keyboard = &world.resources.input.keyboard;
    let ctrl = keyboard.is_key_pressed(KeyCode::ControlLeft)
        || keyboard.is_key_pressed(KeyCode::ControlRight);
    let typing = world
        .resources
        .retained_ui
        .interaction
        .focused_entity
        .is_some_and(|entity| {
            world.ui.get_ui_text_input(entity).is_some()
                || world.ui.get_ui_text_area(entity).is_some()
        });
    let select_all = !typing && !ctrl && keyboard.is_key_pressed(KeyCode::KeyA);
    let deselect = keyboard.is_key_pressed(KeyCode::Escape);

    if select_all && !editor_world.resources.picking.select_all_was_pressed {
        let mut roots: Vec<Entity> = Vec::new();
        for entity in world
            .core
            .query_entities(nightshade::ecs::world::RENDER_MESH)
        {
            if editor_world.resources.editor_scene.is_scaffolding(entity) {
                continue;
            }
            let root = find_group_root(world, entity);
            if !roots.contains(&root) {
                roots.push(root);
            }
        }
        crate::systems::selection::set_many(editor_world, roots);
    }
    editor_world.resources.picking.select_all_was_pressed = select_all;

    if deselect && !editor_world.resources.picking.deselect_was_pressed {
        crate::systems::selection::clear(editor_world);
    }
    editor_world.resources.picking.deselect_was_pressed = deselect;
}

pub fn reset_cycle(editor_world: &mut EditorWorld) {
    editor_world.resources.picking.last_pick_leaf = None;
    editor_world.resources.picking.last_pick_root = None;
    editor_world.resources.picking.cycle_depth = 0;
}

fn resolve_selection(
    editor_world: &mut EditorWorld,
    world: &World,
    leaf: Entity,
    alt_held: bool,
) -> (Option<Entity>, bool) {
    if alt_held {
        editor_world.resources.picking.last_pick_leaf = Some(leaf);
        editor_world.resources.picking.last_pick_root = Some(leaf);
        editor_world.resources.picking.cycle_depth = 0;
        return (Some(leaf), true);
    }
    match editor_world.resources.mode.mode {
        EditorMode::Edit { target } => {
            reset_cycle(editor_world);
            if is_descendant_of(world, leaf, target) {
                (Some(leaf), true)
            } else {
                (None, false)
            }
        }
        EditorMode::Object => {
            let root = find_group_root(world, leaf);
            let chain = chain_from_root_to_leaf(world, root, leaf);
            let last_root = editor_world.resources.picking.last_pick_root;
            let last_leaf = editor_world.resources.picking.last_pick_leaf;
            let current_selection = editor_world.resources.ui.selected_entity;
            let on_same_chain = current_selection.is_some_and(|selected| chain.contains(&selected));
            let cycling = last_leaf == Some(leaf) && last_root == Some(root) && on_same_chain;
            let depth = if cycling {
                let next = editor_world.resources.picking.cycle_depth + 1;
                if next >= chain.len() { 0 } else { next }
            } else {
                0
            };
            editor_world.resources.picking.last_pick_leaf = Some(leaf);
            editor_world.resources.picking.last_pick_root = Some(root);
            editor_world.resources.picking.cycle_depth = depth;
            (Some(chain[depth]), !cycling)
        }
    }
}

fn focus_tree_on(editor_world: &mut EditorWorld, world: &mut World, selection: Option<Entity>) {
    let Some(entity) = selection else {
        return;
    };
    let tree = &mut editor_world.resources.ui_handles.tree;
    let row_index = tree.all_rows.iter().position(|row| row.entity == entity);
    let Some(row_index) = row_index else {
        return;
    };
    let target_depth = tree.all_rows[row_index].depth;
    if target_depth > 0 {
        let mut depth_to_find = target_depth;
        for row in tree.all_rows[..row_index].iter().rev() {
            if row.depth < depth_to_find && row.has_children {
                tree.expanded.insert(row.entity.id);
                if depth_to_find == 1 {
                    break;
                }
                depth_to_find = row.depth;
            }
        }
    }
    tree.last_visible_start = usize::MAX;

    let scroll_entity = world
        .ui
        .get_ui_virtual_list(tree.list)
        .map(|d| d.scroll_entity);
    if let Some(scroll_entity) = scroll_entity {
        let list_height = world
            .ui
            .get_ui_layout_node(tree.list)
            .map(|n| n.computed_rect.height())
            .unwrap_or(0.0);
        let dpi = world.resources.window.cached_scale_factor.max(1.0);
        let logical_height = list_height / dpi;
        let mut visible_position = 0usize;
        let mut skip_until_depth: Option<usize> = None;
        let filter_active = !tree.filter_lower.is_empty();
        for (idx, row) in tree.all_rows.iter().enumerate() {
            if let Some(depth) = skip_until_depth {
                if row.depth > depth {
                    continue;
                } else {
                    skip_until_depth = None;
                }
            }
            let collapsed =
                !tree.cameras_only && row.has_children && !tree.expanded.contains(&row.entity.id);
            let matches_filter =
                !filter_active || row.label.to_lowercase().contains(&tree.filter_lower);
            if matches_filter {
                if idx == row_index {
                    break;
                }
                visible_position += 1;
            }
            if collapsed && !filter_active {
                skip_until_depth = Some(row.depth);
            }
        }
        let row_top = visible_position as f32 * 22.0;
        let row_bottom = row_top + 22.0;
        let current_offset = world
            .ui
            .get_ui_scroll_area(scroll_entity)
            .map(|d| d.scroll_offset)
            .unwrap_or(0.0);
        let mut new_offset = current_offset;
        if row_top < current_offset {
            new_offset = row_top;
        } else if row_bottom > current_offset + logical_height && logical_height > 0.0 {
            new_offset = row_bottom - logical_height;
        }
        if (new_offset - current_offset).abs() > 0.01 {
            ui_scroll_area_set_offset(world, scroll_entity, new_offset);
        }
    }
}

fn sync_outline(editor_world: &EditorWorld, world: &mut World) {
    crate::systems::selection::sync_to_engine(editor_world, world);
}

fn gizmo_drag_active(world: &World) -> bool {
    let gizmos = &world.resources.user_interface.gizmos;
    gizmos.translation_drag.is_some()
        || gizmos.scale_drag.is_some()
        || gizmos.rotation_drag.is_some()
        || gizmos.planar_scale_drag.is_some()
        || gizmos.planar_translation_drag.is_some()
        || gizmos.nav_gizmo_drag.is_some()
}

fn marquee_select(
    editor_world: &mut EditorWorld,
    world: &mut World,
    start: nalgebra_glm::Vec2,
    current: nalgebra_glm::Vec2,
) {
    let Some(camera_entity) = world.resources.active_camera else {
        return;
    };
    let Some(viewport) = world.resources.window.active_viewport_rect else {
        return;
    };
    let min_x = start.x.min(current.x);
    let max_x = start.x.max(current.x);
    let min_y = start.y.min(current.y);
    let max_y = start.y.max(current.y);

    let mut roots: Vec<Entity> = Vec::new();
    let candidates: Vec<Entity> = world
        .core
        .query_entities(
            nightshade::ecs::world::RENDER_MESH | nightshade::ecs::world::GLOBAL_TRANSFORM,
        )
        .collect();
    for entity in candidates {
        if editor_world.resources.editor_scene.is_scaffolding(entity) {
            continue;
        }
        let Some(global) = world.core.get_global_transform(entity) else {
            continue;
        };
        let world_position = global.translation();
        let Some(screen) = project_to_screen(
            world,
            camera_entity,
            world_position,
            viewport.x,
            viewport.y,
            viewport.width,
            viewport.height,
        ) else {
            continue;
        };
        if screen.x >= min_x && screen.x <= max_x && screen.y >= min_y && screen.y <= max_y {
            let root = find_group_root(world, entity);
            if !roots.contains(&root) {
                roots.push(root);
            }
        }
    }

    crate::systems::selection::set_many(editor_world, roots);

    world.resources.user_interface.gizmos.selection_marquee = Some(Rect {
        min: vec2(min_x, min_y),
        max: vec2(max_x, max_y),
    });
}

fn project_to_screen(
    world: &World,
    camera_entity: Entity,
    world_position: Vec3,
    viewport_x: f32,
    viewport_y: f32,
    viewport_width: f32,
    viewport_height: f32,
) -> Option<Vec2> {
    if viewport_height <= 0.0 {
        return None;
    }
    let camera = world.core.get_camera(camera_entity).copied()?;
    let camera_transform = world.core.get_global_transform(camera_entity)?;
    let view = camera_transform
        .0
        .try_inverse()
        .unwrap_or_else(Mat4::identity);
    let aspect = viewport_width / viewport_height;
    let view_projection = camera.projection.matrix_with_aspect(aspect) * view;
    let clip = view_projection
        * nalgebra_glm::Vec4::new(world_position.x, world_position.y, world_position.z, 1.0);
    if clip.w <= 1.0e-6 {
        return None;
    }
    let ndc_x = clip.x / clip.w;
    let ndc_y = clip.y / clip.w;
    let screen_x = viewport_x + (ndc_x * 0.5 + 0.5) * viewport_width;
    let screen_y = viewport_y + (1.0 - (ndc_y * 0.5 + 0.5)) * viewport_height;
    Some(vec2(screen_x, screen_y))
}
