use bevy::core::FrameCount;

use crate::prelude::*;

pub(crate) struct IntroPlugin;

impl Plugin for IntroPlugin {
    fn build(&self, app: &mut App) {
        app.init_resource::<CurrentIntroParagraph>()
            .add_systems(
                OnExit(GameState::Loading),
                init_intro_text
                    .run_if(resource_exists::<tweak::GameTweaks>())
                    .run_if(run_once()),
            )
            .add_systems(OnEnter(GameState::Intro), manage_intro)
            .add_systems(
                OnExit(GameState::Intro),
                (deactivate::<Intro>, cleanup_intro),
            )
            // All of these run during GameState::Intro
            .add_systems(
                Update,
                (
                    menu::exit_to_menu,
                    manage_intro.run_if(any_component_removed::<ui::TextScrollAnimation>()),
                    // Started when the TextScrollAnimation component is added to the parent entity
                    // Updated for as long as there is scrolling text
                    manage_scroll_text_animation.run_if(
                        any_component_added::<ui::TextScrollAnimation>.or_else(
                            |keys: Res<Input<KeyCode>>| -> bool {
                                keys.just_pressed(KeyCode::Return)
                            },
                        ),
                    ),
                    // Play intro manages playing the intro of each individual paragraph
                    // Runs every time the TextScroll component (managed by manage_scroll_text_animation) is updated
                    scroll_text.run_if(any_with_component::<ui::TextScroll>()),
                )
                    .run_if(in_state(GameState::Intro)),
            );
    }
}

#[derive(Debug, Component)]
struct Intro;

#[derive(Debug, Resource)]
struct IntroPlayed;

// Draw the intro text (invisible) on startup
// Requires the Tweakfile to be loaded
fn init_intro_text(
    tweaks_file: Res<tweak::GameTweaks>,
    tweaks: Res<Assets<tweak::Tweaks>>,
    ui_font: Res<ui::UiFont>,
    mut commands: Commands,
) {
    let tweak = tweaks.get(tweaks_file.handle.clone()).expect("Load tweaks");

    let texts = tweak.get::<Vec<String>>("intro_text").expect("Intro text");

    let background_hex = tweak.get::<String>("intro_rgba_background").unwrap();
    let text_hidden_hex = tweak.get::<String>("intro_rgba_hidden").unwrap();

    commands
        .spawn((
            Intro,
            NodeBundle {
                style: Style {
                    width: Val::Percent(100.0),
                    height: Val::Percent(100.0),
                    justify_content: JustifyContent::Center,
                    align_items: AlignItems::Center,
                    position_type: PositionType::Absolute,
                    padding: UiRect::all(Val::Px(50.0)),
                    ..default()
                },
                background_color: Color::NONE.into(),
                visibility: Visibility::Hidden,
                ..default()
            },
        ))
        .with_children(|parent| {
            texts.iter().for_each(|text| {
                parent
                    .spawn((
                        Intro,
                        NodeBundle {
                            style: Style {
                                position_type: PositionType::Absolute,
                                padding: UiRect::all(Val::Px(25.0)),
                                ..default()
                            },
                            visibility: Visibility::Hidden,
                            background_color: Color::hex(&background_hex).unwrap().into(),
                            ..default()
                        },
                    ))
                    .with_children(|parent| {
                        parent.spawn((
                            Intro,
                            TextBundle {
                                text: Text {
                                    sections: text
                                        .chars()
                                        .into_iter()
                                        .map(|c| TextSection {
                                            value: c.to_string(),
                                            style: TextStyle {
                                                font_size: 16.0,
                                                color: Color::hex(&text_hidden_hex).unwrap(),
                                                font: ui_font.handle.clone(),
                                            },
                                        })
                                        .collect(),
                                    alignment: TextAlignment::Center,
                                    ..default()
                                },
                                ..default()
                            },
                        ));
                    });
            });
        });
}

fn manage_intro(
    // Hack, this way of "finding" the root Node containing our paragraphs is precarious
    query: Query<Entity, (With<Node>, With<Intro>, Without<Parent>)>,
    mut commands: Commands,
) {
    info!("Managing intro");
    query.iter().for_each(|e| {
        commands
            .entity(e)
            .insert(ui::TextScrollAnimation)
            .insert(Visibility::Visible);
    });
}

fn cleanup_intro(
    query: Query<Entity, With<Intro>>,
    mut texts: Query<&mut Text, With<Intro>>,
    mut curr: ResMut<CurrentIntroParagraph>,
    tweaks_file: Res<tweak::GameTweaks>,
    tweaks: Res<Assets<tweak::Tweaks>>,
    mut commands: Commands,
) {
    info!("Cleaning up intro");

    query.iter().for_each(|e| {
        commands
            .entity(e)
            .remove::<ui::TextScrollAnimation>()
            .remove::<ui::TextScroll>()
            .insert(Visibility::Hidden);
    });

    {
        // Reset text colors
        let tweak = tweaks.get(tweaks_file.handle.clone()).expect("Load tweaks");
        let text_hidden_hex = tweak.get::<String>("intro_rgba_hidden").unwrap();
        let text_hidden_color = Color::hex(text_hidden_hex).unwrap();
        texts.iter_mut().for_each(|mut text| {
            text.sections.iter_mut().for_each(|s| {
                s.style.color = text_hidden_color;
            });
        });
    }

    curr.0 = None;
}

#[derive(Debug, Resource, Default)]
struct CurrentIntroParagraph(Option<Entity>);

fn manage_scroll_text_animation(
    roots: Query<
        Entity,
        Or<(
            With<ui::TextScrollAnimation>,
            Added<ui::TextScrollAnimation>,
        )>,
    >,
    texts: Query<Entity, (With<Text>, With<Intro>)>,
    animated_texts: Query<&ui::TextScroll>,
    parents: Query<&Parent>,
    children: Query<&Children>,
    time: Res<Time>,
    framecount: Res<FrameCount>,
    mut next_state: ResMut<NextState<GameState>>,
    mut curr: ResMut<CurrentIntroParagraph>,
    mut commands: Commands,
) {
    info!("Managing scroll text animation");

    roots.iter().for_each(|r| {
        info!("Processing {:?} at frame {:?}", r, framecount.0);

        // Skip to next paragraph only the current paragraph's animation is complete
        if animated_texts.is_empty() {
            info!(
                "No animations playing, moving on to next paragraph {:?}",
                curr.0
            );

            // Create an iterator over all paragraphs
            let mut paragraphs = texts.iter_many(children.iter_descendants(r));
            if curr.0.is_some() {
                // Hide the last active entity
                if let Some(e) = curr.0 {
                    let p = parents.get(e).unwrap();
                    commands.entity(p.get()).insert(Visibility::Hidden);

                    commands.entity(e).insert(Visibility::Hidden);
                }
                // Locate the last entity we were operating with
                while let Some(entity) = paragraphs.next() {
                    if curr.0 == Some(entity) {
                        break;
                    }
                }
            }
            // Operate on the next entity in the list
            curr.0 = paragraphs.next();

            info!("Curr: {:?}", curr.0);

            if let Some(e) = curr.0 {
                commands.entity(e).insert(ui::TextScroll {
                    progress: 0,
                    start: time.elapsed_seconds(),
                });
            } else {
                commands.entity(r).remove::<ui::TextScrollAnimation>();
                next_state.set(GameState::Play);
            }
        }
    });
}

fn scroll_text(
    mut texts: Query<(Entity, &mut Visibility, &mut Text, &mut ui::TextScroll)>,
    tweaks_file: Res<tweak::GameTweaks>,
    tweaks: Res<Assets<tweak::Tweaks>>,
    time: Res<Time>,
    keys: Res<Input<KeyCode>>,
    parents: Query<&Parent>,
    mut commands: Commands,
) {
    let tweak = tweaks.get(tweaks_file.handle.clone()).expect("Load tweaks");
    texts
        .iter_mut()
        .for_each(|(entity, mut vis, mut text, mut text_scroll)| {
            debug!("Updating {:?} {:?}", entity, text_scroll);

            // If the animation just started, make visible
            if text_scroll.progress == 0 {
                let p = parents.get(entity).unwrap();
                commands.entity(p.get()).insert(Visibility::Inherited);

                // Make the text visible as well
                *vis = Visibility::Inherited;
            }

            // If user pressed enter, skip to end of paragraph
            text_scroll.progress = if keys.just_pressed(KeyCode::Return) {
                usize::MAX
            }
            // Otherwise, progress by some fixed increment
            else {
                // Update animation progress for this paragrpah
                let delay = tweak.get::<f32>("intro_delay").unwrap();

                // Update text_scroll.progress based on frame count
                ((time.elapsed_seconds() - text_scroll.start) / delay) as usize
            };

            // Fetch desired color from tweakfile
            let text_visible_hex = tweak.get::<String>("intro_rgba_visible").unwrap();
            let text_color = Color::hex(text_visible_hex).unwrap();

            // Update characters in this paragraph
            // PERF: Re-doing a lot of work every loop
            text.sections.iter_mut().enumerate().for_each(|(i, s)| {
                if i <= text_scroll.progress {
                    s.style.color = text_color;
                }
            });

            // If the entire paragraph is played out, remove the component
            if text.sections.iter().all(|s| s.style.color == text_color) {
                commands.entity(entity).remove::<ui::TextScroll>();
            }
        });
}
