use nalgebra_glm::{Vec2, Vec4};
use winit::keyboard::KeyCode;

use crate::ecs::world::World;

use super::InteractionSnapshot;
use super::text_cursor::{
    byte_index_at_x, line_col_from_char_position, line_count, line_start_char_index, line_text,
    measure_text_width,
};
use super::text_edit::EditBuffer;
use crate::ecs::ui::components::{CharStyle, RichTextSnapshot};

pub(super) struct RichTextEditorContext<'a> {
    pub(super) entity: freecs::Entity,
    pub(super) interaction: &'a InteractionSnapshot,
    pub(super) data: &'a crate::ecs::ui::components::UiRichTextEditorData,
    pub(super) focused_entity: Option<freecs::Entity>,
    pub(super) frame_chars: &'a [char],
    pub(super) frame_keys: &'a [(KeyCode, bool)],
    pub(super) ctrl_held: bool,
    pub(super) shift_held: bool,
    pub(super) mouse_position: Vec2,
    pub(super) current_time: f64,
    pub(super) dpi_scale: f32,
}

pub(super) fn handle_rich_text_editor(world: &mut World, context: RichTextEditorContext<'_>) {
    let entity = context.entity;
    let is_focused = context.focused_entity == Some(entity);
    let data = context.data;
    let ctrl_held = context.ctrl_held;
    let shift_held = context.shift_held;
    let current_time = context.current_time;
    let mut current_style = data.current_style.clone();
    let mut changed = false;
    let mut cursor_blink_timer = data.cursor_blink_timer;
    let mut scroll_offset_y = data.scroll_offset_y;
    let mut clear_focus = false;
    let line_height = data.line_height;
    let mut undo_stack = data.undo_stack.clone();

    let mut buffer = EditBuffer::styled(
        data.text.clone(),
        data.char_styles.clone(),
        data.cursor_position,
        data.selection_start,
        true,
    );

    if is_focused {
        let mut needs_snapshot = false;
        for character in context.frame_chars {
            if *character >= ' ' {
                if !needs_snapshot {
                    undo_stack.push_initial(snapshot(&buffer));
                    needs_snapshot = true;
                }
                buffer.insert_char(*character, current_style.clone());
                changed = true;
                cursor_blink_timer = current_time;
            }
        }

        if needs_snapshot {
            undo_stack.push(snapshot(&buffer), current_time);
        }

        for (key, is_pressed) in context.frame_keys {
            if !is_pressed {
                continue;
            }
            match key {
                KeyCode::Backspace => {
                    undo_stack.push_initial(snapshot(&buffer));
                    if buffer.backspace(ctrl_held) {
                        undo_stack.push(snapshot(&buffer), current_time);
                        changed = true;
                    }
                    cursor_blink_timer = current_time;
                }
                KeyCode::Delete => {
                    undo_stack.push_initial(snapshot(&buffer));
                    if buffer.delete_forward(ctrl_held) {
                        undo_stack.push(snapshot(&buffer), current_time);
                        changed = true;
                    }
                    cursor_blink_timer = current_time;
                }
                KeyCode::Enter => {
                    if ctrl_held {
                        clear_focus = true;
                    } else {
                        undo_stack.push_initial(snapshot(&buffer));
                        buffer.insert_newline(current_style.clone());
                        undo_stack.push(snapshot(&buffer), current_time);
                        changed = true;
                        cursor_blink_timer = current_time;
                    }
                }
                KeyCode::ArrowLeft => {
                    buffer.move_left(ctrl_held, shift_held);
                    cursor_blink_timer = current_time;
                }
                KeyCode::ArrowRight => {
                    buffer.move_right(ctrl_held, shift_held);
                    cursor_blink_timer = current_time;
                }
                KeyCode::ArrowUp => {
                    buffer.move_up(shift_held);
                    cursor_blink_timer = current_time;
                }
                KeyCode::ArrowDown => {
                    buffer.move_down(shift_held);
                    cursor_blink_timer = current_time;
                }
                KeyCode::Home => {
                    buffer.move_home(shift_held);
                    cursor_blink_timer = current_time;
                }
                KeyCode::End => {
                    buffer.move_end(shift_held);
                    cursor_blink_timer = current_time;
                }
                KeyCode::KeyA if ctrl_held => {
                    buffer.select_all();
                    cursor_blink_timer = current_time;
                }
                KeyCode::KeyB if ctrl_held => {
                    toggle_selection_style(&mut buffer, &mut current_style, |style| {
                        &mut style.bold
                    });
                    changed = true;
                }
                KeyCode::KeyI if ctrl_held => {
                    toggle_selection_style(&mut buffer, &mut current_style, |style| {
                        &mut style.italic
                    });
                    changed = true;
                }
                KeyCode::KeyU if ctrl_held => {
                    toggle_selection_style(&mut buffer, &mut current_style, |style| {
                        &mut style.underline
                    });
                    changed = true;
                }
                KeyCode::KeyZ if ctrl_held => {
                    if let Some(restored) = undo_stack.undo() {
                        buffer.text = restored.text.clone();
                        buffer.styles = restored.char_styles.clone();
                        buffer.cursor = restored.cursor_position;
                        buffer.selection = restored.selection_start;
                        changed = true;
                        cursor_blink_timer = current_time;
                    }
                }
                KeyCode::KeyY if ctrl_held => {
                    if let Some(restored) = undo_stack.redo() {
                        buffer.text = restored.text.clone();
                        buffer.styles = restored.char_styles.clone();
                        buffer.cursor = restored.cursor_position;
                        buffer.selection = restored.selection_start;
                        changed = true;
                        cursor_blink_timer = current_time;
                    }
                }
                KeyCode::Escape => {
                    buffer.selection = None;
                    clear_focus = true;
                }
                _ => {}
            }
        }

        if context.interaction.clicked
            && let Some(rect) = world.ui.get_ui_layout_node(entity).map(|n| n.computed_rect)
        {
            let font_size = world
                .resources
                .retained_ui
                .theme_state
                .active_theme()
                .font_size;
            let local_y =
                (context.mouse_position.y - rect.min.y) / context.dpi_scale - 8.0 + scroll_offset_y;
            let clicked_line = (local_y / line_height).max(0.0) as usize;
            let total_lines = line_count(&buffer.text);
            let target_line = clicked_line.min(total_lines.saturating_sub(1));
            let target_line_text = line_text(&buffer.text, target_line).to_string();
            let local_x = (context.mouse_position.x - rect.min.x) / context.dpi_scale - 8.0;
            let column = byte_index_at_x(
                &mut world.resources.text.font_engine,
                &target_line_text,
                font_size,
                local_x,
            );
            let line_start = line_start_char_index(&buffer.text, target_line);
            if shift_held {
                if buffer.selection.is_none() {
                    buffer.selection = Some(buffer.cursor);
                }
            } else {
                buffer.selection = None;
            }
            buffer.cursor = line_start + column;
            cursor_blink_timer = current_time;
        }

        if context.interaction.double_clicked {
            buffer.select_word_at_cursor();
        }
    } else {
        buffer.selection = None;
    }

    let text = buffer.text;
    let char_styles = buffer.styles;
    let cursor_position = buffer.cursor;
    let selection_start = buffer.selection;

    if clear_focus {
        world
            .resources
            .retained_ui
            .interaction_for_active_mut()
            .focused_entity = None;
    }

    if changed {
        world.resources.text.cache.set_text(data.text_slot, &text);

        let has_colors = char_styles.iter().any(|s| s.color.is_some());
        if has_colors {
            let colors: Vec<Option<Vec4>> = char_styles.iter().map(|s| s.color).collect();
            world
                .resources
                .retained_ui
                .text_cache
                .character_colors
                .insert(data.text_slot, colors);
        } else {
            world
                .resources
                .retained_ui
                .text_cache
                .character_colors
                .remove(&data.text_slot);
        }
    }

    let cursor_visible = is_focused && ((current_time - cursor_blink_timer) % 1.0) < 0.5;
    if let Some(cursor_node) = world.ui.get_ui_layout_node_mut(data.cursor_entity) {
        cursor_node.visible = cursor_visible;
    }

    let (cur_line, cur_col) = line_col_from_char_position(&text, cursor_position);
    let visible_rows = data.visible_rows;
    let cursor_y = cur_line as f32 * line_height;
    let visible_height = visible_rows as f32 * line_height;
    if cursor_y - scroll_offset_y >= visible_height {
        scroll_offset_y = cursor_y - visible_height + line_height;
    } else if cursor_y < scroll_offset_y {
        scroll_offset_y = cursor_y;
    }
    let total_lines = line_count(&text);
    let max_scroll = ((total_lines as f32 * line_height) - visible_height).max(0.0);
    scroll_offset_y = scroll_offset_y.clamp(0.0, max_scroll);

    if world.ui.get_ui_layout_node(entity).is_some() {
        let font_size = world
            .resources
            .retained_ui
            .theme_state
            .active_theme()
            .font_size;
        {
            let cur_line_text = line_text(&text, cur_line).to_string();
            let text_before_cursor: String = cur_line_text.chars().take(cur_col).collect();
            let cursor_x = measure_text_width(
                &mut world.resources.text.font_engine,
                &text_before_cursor,
                font_size,
            );
            let cursor_screen_y = cur_line as f32 * line_height - scroll_offset_y;

            let has_selection =
                is_focused && selection_start.is_some() && selection_start != Some(cursor_position);

            let sel_positions: Vec<(f32, f32, f32)> = if has_selection {
                let sel_start = selection_start.unwrap();
                let sel_min = sel_start.min(cursor_position);
                let sel_max = sel_start.max(cursor_position);
                let (min_line, min_col) = line_col_from_char_position(&text, sel_min);
                let (max_line, max_col) = line_col_from_char_position(&text, sel_max);

                let mut positions = Vec::new();
                for line_idx in min_line..=max_line {
                    let line_segment = line_text(&text, line_idx).to_string();
                    let start_col = if line_idx == min_line { min_col } else { 0 };
                    let end_col = if line_idx == max_line {
                        max_col
                    } else {
                        line_segment.chars().count()
                    };
                    let start_text: String = line_segment.chars().take(start_col).collect();
                    let end_text: String = line_segment.chars().take(end_col).collect();
                    let sx = measure_text_width(
                        &mut world.resources.text.font_engine,
                        &start_text,
                        font_size,
                    );
                    let ex = measure_text_width(
                        &mut world.resources.text.font_engine,
                        &end_text,
                        font_size,
                    );
                    let sy = line_idx as f32 * line_height - scroll_offset_y;
                    positions.push((sx, ex - sx, sy));
                }
                positions
            } else {
                Vec::new()
            };

            if let Some(cursor_node) = world.ui.get_ui_layout_node_mut(data.cursor_entity)
                && let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
                    cursor_node.base_layout.as_mut()
            {
                window.position =
                    crate::ecs::ui::units::Ab(Vec2::new(8.0 + cursor_x, 8.0 + cursor_screen_y))
                        .into();
            }

            for (pool_index, sel_entity) in data.selection_pool.iter().enumerate() {
                if pool_index < sel_positions.len() {
                    let (sx, width, sy) = sel_positions[pool_index];
                    if let Some(sel_node) = world.ui.get_ui_layout_node_mut(*sel_entity) {
                        sel_node.visible = true;
                        if let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
                            sel_node.base_layout.as_mut()
                        {
                            window.position =
                                crate::ecs::ui::units::Ab(Vec2::new(8.0 + sx, 8.0 + sy)).into();
                            window.size =
                                crate::ecs::ui::units::Ab(Vec2::new(width.max(2.0), line_height))
                                    .into();
                        }
                    }
                } else if let Some(sel_node) = world.ui.get_ui_layout_node_mut(*sel_entity) {
                    sel_node.visible = false;
                }
            }
        }
    }

    if changed {
        world.resources.retained_ui.events_for_active_mut().push(
            crate::ecs::ui::resources::UiEvent::RichTextEditorChanged {
                entity,
                text: text.clone(),
            },
        );
    }
    if clear_focus {
        world.resources.retained_ui.events_for_active_mut().push(
            crate::ecs::ui::resources::UiEvent::TextInputSubmitted {
                entity,
                text: text.clone(),
            },
        );
    }
    let placeholder_update = if let Some(widget_data) = world.ui.get_ui_rich_text_editor_mut(entity)
    {
        let text_is_empty = text.is_empty();
        widget_data.text = text;
        widget_data.char_styles = char_styles;
        widget_data.current_style = current_style;
        widget_data.cursor_position = cursor_position;
        widget_data.selection_start = selection_start;
        widget_data.changed = changed;
        widget_data.cursor_blink_timer = cursor_blink_timer;
        widget_data.scroll_offset_y = scroll_offset_y;
        widget_data.undo_stack = undo_stack;
        widget_data
            .placeholder_entity
            .filter(|_| changed)
            .map(|ph| (ph, text_is_empty))
    } else {
        None
    };

    if let Some((ph_entity, text_is_empty)) = placeholder_update
        && let Some(node) = world.ui.get_ui_layout_node_mut(ph_entity)
    {
        node.visible = text_is_empty;
    }
}

fn snapshot(buffer: &EditBuffer) -> RichTextSnapshot {
    RichTextSnapshot {
        text: buffer.text.clone(),
        char_styles: buffer.styles.clone(),
        cursor_position: buffer.cursor,
        selection_start: buffer.selection,
    }
}

fn toggle_selection_style(
    buffer: &mut EditBuffer,
    current_style: &mut CharStyle,
    accessor: fn(&mut CharStyle) -> &mut bool,
) {
    if let Some((start, end)) = buffer.selection_range() {
        let all_set = (start..end).all(|index| {
            buffer
                .styles
                .get(index)
                .map(|style| *accessor(&mut style.clone()))
                .unwrap_or(false)
        });
        let new_value = !all_set;
        for index in start..end {
            if index < buffer.styles.len() {
                *accessor(&mut buffer.styles[index]) = new_value;
            }
        }
        *accessor(current_style) = new_value;
    } else {
        let field = accessor(current_style);
        *field = !*field;
    }
}
