use super::edit::TextEditState;
use super::range::{spawn_range_fill, spawn_range_thumb, spawn_range_track};
use super::text::{
    apply_check_input_shape, default_input_node,
    default_textarea_node, input_text_bundle, input_text_marker, input_text_node,
};
use super::{
    AddInput, CheckboxMark, DisabledInput, InputCaret, InputClickState, InputField,
    InputScrollOffset, InputSelection, InputType, InputViewport, RangeState, ToggleFill,
    ToggleIndicator, UndoStack,
};
use crate::build_pending::UiBuildPending;
use crate::focus::{UiFocusable, hidden_outline};
use crate::interaction_style::UiDisabled;
use crate::style::{
    apply_utility_patch, checkbox_border_color, input_selection_color,
    resolve_classes_with_fallback, root_visual_styles_from_patch,
};
use crate::text::{AddText, control_typography, typography_from_patch};
use bevy::picking::Pickable;
use bevy::prelude::*;
use core::f32::consts::PI;

const DEFAULT_INPUT_CLASS: &str = "input-root";
const DEFAULT_TEXTAREA_CLASS: &str = "textarea-root";
const DEFAULT_RANGE_CLASS: &str = "input-range-root";
const DEFAULT_CHECKBOX_CLASS: &str = "checkbox-root";
const DEFAULT_RADIO_CLASS: &str = "radio-root";

fn checkbox_mark_stroke(
    width: f32,
    height: f32,
    left: f32,
    top: f32,
    rotation: f32,
) -> impl Bundle {
    (
        CheckboxMark,
        Pickable::IGNORE,
        Node {
            position_type: PositionType::Absolute,
            width: Val::Px(width),
            height: Val::Px(height),
            left: Val::Px(left),
            top: Val::Px(top),
            ..default()
        },
        UiTransform::from_rotation(Rot2::radians(rotation)),
        BackgroundColor(crate::style::text_primary_color()),
    )
}

pub(super) fn add_input(mut commands: Commands, query: Query<(Entity, &AddInput)>) {
    for (entity, add_input) in query {
        let add_input = add_input.clone();
        let default_root_class = match add_input.input_type {
            InputType::Range => DEFAULT_RANGE_CLASS,
            InputType::Textarea => DEFAULT_TEXTAREA_CLASS,
            InputType::Checkbox => DEFAULT_CHECKBOX_CLASS,
            InputType::Radio => DEFAULT_RADIO_CLASS,
            _ => DEFAULT_INPUT_CLASS,
        };
        let root_patch = resolve_classes_with_fallback(
            default_root_class,
            add_input.class.as_deref(),
            "input root",
        );
        let root_styles = root_visual_styles_from_patch(&root_patch);
        let mut root_node = match add_input.input_type {
            InputType::Range => Node {
                min_width: Val::Px(120.0),
                flex_grow: 1.0,
                padding: UiRect::ZERO,
                border: UiRect::ZERO,
                ..default()
            },
            InputType::Textarea => default_textarea_node(add_input.size_chars, add_input.rows),
            InputType::Checkbox | InputType::Radio => Node {
                width: Val::Px(20.0),
                height: Val::Px(20.0),
                min_width: Val::Px(20.0),
                min_height: Val::Px(20.0),
                align_items: AlignItems::Center,
                justify_content: JustifyContent::Center,
                ..default()
            },
            _ => default_input_node(add_input.size_chars),
        };
        if matches!(add_input.input_type, InputType::Checkbox | InputType::Radio) {
            apply_check_input_shape(&mut root_node, add_input.input_type);
        }
        apply_utility_patch(&mut root_node, &root_patch);

        commands
            .entity(entity)
            .queue_silenced(move |mut entity_commands: EntityWorldMut| {
                let input_entity = entity_commands.id();
                let normalized_value = if add_input.input_type == InputType::Range {
                    super::value::normalize_numeric_value(
                        &add_input.value,
                        add_input.min,
                        add_input.max,
                        add_input.step,
                    )
                } else {
                    add_input.value.clone()
                };

                entity_commands.insert((
                    Name::new(add_input.name.clone()),
                    root_node,
                    Visibility::Visible,
                    BackgroundColor(if matches!(add_input.input_type, InputType::Checkbox | InputType::Radio) {
                        Color::WHITE
                    } else {
                        Color::NONE
                    }),
                    BorderColor::all(if matches!(add_input.input_type, InputType::Checkbox | InputType::Radio) {
                        checkbox_border_color()
                    } else {
                        Color::NONE
                    }),
                    InputField {
                        name: add_input.name.clone(),
                        input_type: add_input.input_type,
                        checked: add_input.checked,
                        placeholder: add_input.placeholder.clone(),
                        viewport_entity: None,
                        text_entity: None,
                        selection_entity: None,
                        caret_entity: None,
                        edit_state: TextEditState::with_text(normalized_value.clone()),
                        initial_value: normalized_value.clone(),
                        initial_checked: add_input.checked,
                        min: add_input.min,
                        max: add_input.max,
                        step: add_input.step,
                        caret_blink_resume_at: 0.0,
                        preferred_caret_x: None,
                        undo_stack: UndoStack::default(),
                    },
                    InputClickState::default(),
                ));
                if add_input.input_type != InputType::Range {
                    entity_commands.insert(hidden_outline());
                }
                if let Some(styles) = root_styles.clone() {
                    entity_commands.insert(styles);
                }

                if add_input.input_type == InputType::Range {
                    entity_commands.world_scope(|world| {
                        let track = spawn_range_track(world, input_entity);
                        let fill = spawn_range_fill(world);
                        let thumb = spawn_range_thumb(world);
                        world.entity_mut(track).add_children(&[fill, thumb]);
                        world.entity_mut(input_entity).add_child(track);
                        world.entity_mut(input_entity).insert(RangeState {
                            track,
                            fill,
                            thumb,
                            drag_start_value: 0.0,
                        });
                    });
                } else if matches!(add_input.input_type, InputType::Checkbox | InputType::Radio) {
                    entity_commands.world_scope(|world| {
                        let indicator = world
                            .spawn((
                                ToggleIndicator,
                                Pickable::IGNORE,
                                Node {
                                    width: Val::Percent(100.0),
                                    height: Val::Percent(100.0),
                                    align_items: AlignItems::Center,
                                    justify_content: JustifyContent::Center,
                                    ..default()
                                },
                                BackgroundColor(Color::NONE),
                            ))
                            .id();
                        if add_input.input_type == InputType::Radio {
                            let fill = world
                                .spawn((
                                    ToggleFill,
                                    Pickable::IGNORE,
                                    Node {
                                        width: Val::Px(8.0),
                                        height: Val::Px(8.0),
                                        border_radius: BorderRadius::all(Val::Px(999.0)),
                                        ..default()
                                    },
                                    Visibility::Hidden,
                                    BackgroundColor(crate::style::text_primary_color()),
                                ))
                                .id();
                            world.entity_mut(indicator).add_child(fill);
                        } else {
                            let short_stroke = world
                                .spawn((
                                    checkbox_mark_stroke(3.0, 8.0, 4.5, 8.5, -PI / 4.0),
                                    Visibility::Hidden,
                                ))
                                .id();
                            let long_stroke = world
                                .spawn((
                                    checkbox_mark_stroke(3.0, 13.0, 9.5, 4.5, PI / 4.0),
                                    Visibility::Hidden,
                                ))
                                .id();
                            world.entity_mut(indicator).add_children(&[short_stroke, long_stroke]);
                        }
                        world.entity_mut(input_entity).add_child(indicator);
                    });
                } else {
                    let text_patch = resolve_classes_with_fallback(
                        "input-text",
                        add_input.text_class.as_deref(),
                        "input text",
                    );
                    let mut text_node = input_text_node();
                    apply_utility_patch(&mut text_node, &text_patch);
                    let text_add_input = AddInput {
                        value: add_input.value.clone(),
                        ..add_input.clone()
                    };
                    entity_commands.world_scope(|world| {
                        let selection_color = input_selection_color();
                        let viewport_entity = world
                            .spawn((
                                InputViewport,
                                Pickable::IGNORE,
                                Node {
                                    position_type: PositionType::Relative,
                                    width: Val::Percent(100.0),
                                    height: Val::Percent(100.0),
                                    min_width: Val::Px(0.0),
                                    overflow: Overflow::clip(),
                                    ..default()
                                },
                            ))
                            .id();
                        let selection_entity = world
                            .spawn((
                                InputSelection,
                                Pickable::IGNORE,
                                Visibility::Hidden,
                                Node {
                                    position_type: PositionType::Absolute,
                                    width: Val::Px(0.0),
                                    height: Val::Px(0.0),
                                    ..default()
                                },
                                BackgroundColor(selection_color),
                            ))
                            .id();
                        world.entity_mut(selection_entity).with_children(|_parent| {
                            // Segments are spawned dynamically in sync_input_edit_visuals
                        });
                        let text_entity = world
                            .spawn((
                                input_text_marker(),
                                Pickable::IGNORE,
                                AddText {
                                    size: text_patch
                                        .font_size
                                        .unwrap_or_else(crate::style::font_size_control),
                                    ..input_text_bundle(&text_add_input)
                                }
                                .typography(typography_from_patch(
                                    &text_patch,
                                    control_typography(),
                                )),
                                InputScrollOffset::default(),
                                text_node,
                            ))
                            .id();
                        let caret_entity = world
                            .spawn((
                                InputCaret,
                                Pickable::IGNORE,
                                Visibility::Hidden,
                                Node {
                                    position_type: PositionType::Absolute,
                                    width: Val::Px(crate::style::input_caret_width()),
                                    height: Val::Px(0.0),
                                    ..default()
                                },
                                BackgroundColor(crate::style::input_caret_color()),
                            ))
                            .id();
                        world.entity_mut(viewport_entity).add_children(&[
                            selection_entity,
                            text_entity,
                            caret_entity,
                        ]);
                        world.entity_mut(input_entity).add_child(viewport_entity);
                        let mut input = world
                            .get_mut::<InputField>(input_entity)
                            .expect("input just inserted");
                        input.viewport_entity = Some(viewport_entity);
                        input.text_entity = Some(text_entity);
                        input.selection_entity = Some(selection_entity);
                        input.caret_entity = Some(caret_entity);
                    });
                }

                if add_input.disabled {
                    entity_commands.insert((DisabledInput, UiDisabled));
                } else {
                    if add_input.input_type != InputType::Range {
                        entity_commands.insert(UiFocusable);
                    }
                }

                entity_commands.observe(super::state::input_click);
                entity_commands.observe(super::state::input_drag_start);
                entity_commands.observe(super::state::input_drag);
                entity_commands.observe(super::state::input_drag_end);

                entity_commands
                    .remove::<AddInput>()
                    .remove::<UiBuildPending>();
            });
    }
}

pub(super) fn sync_toggle_visuals(
    fields: Query<(&InputField, &Children), Or<(Changed<InputField>, Added<InputField>)>>,
    indicators: Query<&Children, With<ToggleIndicator>>,
    mut fills: Query<
        (&mut Visibility, &mut Node, &mut BackgroundColor),
        (With<ToggleFill>, Without<CheckboxMark>),
    >,
    mut marks: Query<
        (&mut Visibility, &mut BackgroundColor),
        (With<CheckboxMark>, Without<ToggleFill>),
    >,
) {
    for (field, children) in &fields {
        if !field.is_toggle() {
            continue;
        }
        for child in children.iter() {
            let Ok(fill_children) = indicators.get(child) else {
                continue;
            };
            for fill in fill_children.iter() {
                if let Ok((mut visibility, _node, mut color)) = fills.get_mut(fill) {
                    *visibility = if field.checked {
                        Visibility::Visible
                    } else {
                        Visibility::Hidden
                    };
                    color.0 = crate::style::text_primary_color();
                    continue;
                }
                let Ok((mut visibility, mut color)) = marks.get_mut(fill) else {
                    continue;
                };
                *visibility = if field.checked {
                    Visibility::Visible
                } else {
                    Visibility::Hidden
                };
                color.0 = crate::style::text_primary_color();
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn checkbox_builds_checkmark_strokes_instead_of_fill_dot() {
        let mut app = App::new();
        app.add_systems(Update, add_input);

        let entity = app
            .world_mut()
            .spawn(AddInput {
                input_type: InputType::Checkbox,
                checked: true,
                ..Default::default()
            })
            .id();

        app.update();

        let children = app
            .world()
            .entity(entity)
            .get::<Children>()
            .expect("checkbox should spawn indicator child");
        let indicator = children[0];
        let indicator_children = app
            .world()
            .entity(indicator)
            .get::<Children>()
            .expect("indicator should have visual children");

        let mark_count = indicator_children
            .iter()
            .filter(|child| app.world().entity(*child).contains::<CheckboxMark>())
            .count();
        let fill_count = indicator_children
            .iter()
            .filter(|child| app.world().entity(*child).contains::<ToggleFill>())
            .count();

        assert_eq!(mark_count, 2);
        assert_eq!(fill_count, 0);
    }

    #[test]
    fn radio_builds_round_fill_dot() {
        let mut app = App::new();
        app.add_systems(Update, add_input);

        let entity = app
            .world_mut()
            .spawn(AddInput {
                input_type: InputType::Radio,
                checked: true,
                ..Default::default()
            })
            .id();

        app.update();

        let children = app
            .world()
            .entity(entity)
            .get::<Children>()
            .expect("radio should spawn indicator child");
        let indicator = children[0];
        let indicator_children = app
            .world()
            .entity(indicator)
            .get::<Children>()
            .expect("indicator should have visual children");
        let fill = indicator_children[0];
        let node = app
            .world()
            .entity(fill)
            .get::<Node>()
            .expect("radio fill should have node");

        assert!(app.world().entity(fill).contains::<ToggleFill>());
        assert_eq!(node.border_radius, BorderRadius::all(Val::Px(999.0)));
    }
}
