use bladeink::story::Story;
use glam::Vec3;

use crate::components::dialog::{
    DialogBubbleComponent, DialogOutcomeEvent, DialogPhase, DialogProjectileComponent,
    DialogSourceComponent, ParryButton,
};
use crate::components::trigger::{TriggerEvent, TriggerEventKind};
use crate::entity::{EntityHandle, EntityManager};
use crate::utility::transform::Transform;
use crate::world::Storage;

const DEFAULT_DISPLAY_TIME: f32 = 3.0;
const PARRY_TAG_PREFIX: &str = "parry:";
const TIMER_TAG_PREFIX: &str = "timer:";

pub fn dialog_system(
    entities: &mut EntityManager,
    trigger_events: &[TriggerEvent],
    dialog_sources: &Storage<DialogSourceComponent>,
    bubble_tags: &mut Storage<()>,
    dialog_bubbles: &mut Storage<DialogBubbleComponent>,
    transforms: &mut Storage<Transform>,
    names: &mut Storage<String>,
    player_tags: &Storage<()>,
    projectile_tags: &mut Storage<()>,
    dialog_projectiles: &mut Storage<DialogProjectileComponent>,
    dialog_outcomes: &mut Vec<DialogOutcomeEvent>,
    delta: f32,
)
{
    process_trigger_events(
        entities,
        trigger_events,
        dialog_sources,
        bubble_tags,
        dialog_bubbles,
        transforms,
        names,
        projectile_tags,
        dialog_projectiles,
    );
    tick_displaying_bubbles(
        entities,
        bubble_tags,
        dialog_bubbles,
        transforms,
        player_tags,
        projectile_tags,
        dialog_projectiles,
        delta,
    );
    process_outcomes(bubble_tags, dialog_bubbles, dialog_outcomes);
}

fn process_trigger_events(
    entities: &mut EntityManager,
    trigger_events: &[TriggerEvent],
    dialog_sources: &Storage<DialogSourceComponent>,
    bubble_tags: &mut Storage<()>,
    dialog_bubbles: &mut Storage<DialogBubbleComponent>,
    transforms: &mut Storage<Transform>,
    names: &mut Storage<String>,
    projectile_tags: &mut Storage<()>,
    dialog_projectiles: &mut Storage<DialogProjectileComponent>,
)
{
    let events: Vec<_> = trigger_events.iter().cloned().collect();

    for event in events
    {
        let has_source = dialog_sources.get(event.trigger_entity).is_some();
        if !has_source
        {
            continue;
        }

        match event.kind
        {
            TriggerEventKind::Entered =>
            {
                let already_active = bubble_tags.all().iter().any(|&b| {
                    dialog_bubbles
                        .with(b, |db| db.character_entity == event.trigger_entity)
                        .unwrap_or(false)
                });

                if already_active
                {
                    continue;
                }

                spawn_bubble(
                    entities,
                    dialog_sources,
                    bubble_tags,
                    dialog_bubbles,
                    transforms,
                    names,
                    event.trigger_entity,
                );
            }

            TriggerEventKind::Exited =>
            {
                despawn_bubbles_for_character(
                    bubble_tags,
                    dialog_bubbles,
                    transforms,
                    projectile_tags,
                    dialog_projectiles,
                    event.trigger_entity,
                );
            }
        }
    }
}

fn spawn_bubble(
    entities: &mut EntityManager,
    dialog_sources: &Storage<DialogSourceComponent>,
    bubble_tags: &mut Storage<()>,
    dialog_bubbles: &mut Storage<DialogBubbleComponent>,
    transforms: &mut Storage<Transform>,
    names: &mut Storage<String>,
    character_entity: EntityHandle,
)
{
    let ink_json = match dialog_sources.with(character_entity, |s| s.ink_json.clone())
    {
        Some(json) => json,
        None => return,
    };

    let mut story = match Story::new(&ink_json)
    {
        Ok(s) => s,
        Err(e) =>
        {
            eprintln!("Failed to load ink story: {e}");
            return;
        }
    };

    let (text, parry, display_time) = advance_story(&mut story);

    let character_pos = transforms
        .with(character_entity, |t| t.position)
        .unwrap_or(Vec3::ZERO);

    let bubble_entity = entities.spawn();
    transforms.insert(
        bubble_entity,
        Transform::from_position(character_pos + Vec3::new(0.0, 8.0, 0.0)),
    );
    names.insert(bubble_entity, "DialogBubble".to_string());
    bubble_tags.insert(bubble_entity, ());
    dialog_bubbles.insert(
        bubble_entity,
        DialogBubbleComponent {
            story,
            character_entity,
            current_text: text,
            phase: DialogPhase::Displaying {
                timer: display_time,
            },
            correct_parry: parry,
            display_time,
        },
    );
}

fn despawn_bubbles_for_character(
    bubble_tags: &mut Storage<()>,
    dialog_bubbles: &mut Storage<DialogBubbleComponent>,
    transforms: &mut Storage<Transform>,
    projectile_tags: &mut Storage<()>,
    dialog_projectiles: &mut Storage<DialogProjectileComponent>,
    character_entity: EntityHandle,
)
{
    let bubbles: Vec<EntityHandle> = bubble_tags.all();

    for bubble_entity in bubbles
    {
        let matches = dialog_bubbles
            .with(bubble_entity, |b| b.character_entity == character_entity)
            .unwrap_or(false);

        if !matches
        {
            continue;
        }

        if let Some(bubble) = dialog_bubbles.get(bubble_entity)
        {
            if let DialogPhase::ProjectileInFlight { projectile_entity } = bubble.phase
            {
                transforms.remove(projectile_entity);
                projectile_tags.remove(projectile_entity);
                dialog_projectiles.remove(projectile_entity);
            }
        }

        transforms.remove(bubble_entity);
        bubble_tags.remove(bubble_entity);
        dialog_bubbles.remove(bubble_entity);
    }
}

fn tick_displaying_bubbles(
    entities: &mut EntityManager,
    bubble_tags: &mut Storage<()>,
    dialog_bubbles: &mut Storage<DialogBubbleComponent>,
    transforms: &mut Storage<Transform>,
    player_tags: &Storage<()>,
    projectile_tags: &mut Storage<()>,
    dialog_projectiles: &mut Storage<DialogProjectileComponent>,
    delta: f32,
)
{
    let bubbles: Vec<EntityHandle> = bubble_tags.all();

    for bubble_entity in bubbles
    {
        let expired = dialog_bubbles
            .with_mut(bubble_entity, |b| {
                if let DialogPhase::Displaying { ref mut timer } = b.phase
                {
                    *timer -= delta;
                    *timer <= 0.0
                }
                else
                {
                    false
                }
            })
            .unwrap_or(false);

        if expired
        {
            let correct_parry = match dialog_bubbles.with(bubble_entity, |b| b.correct_parry)
            {
                Some(Some(p)) => p,
                _ =>
                {
                    transforms.remove(bubble_entity);
                    bubble_tags.remove(bubble_entity);
                    dialog_bubbles.remove(bubble_entity);
                    continue;
                }
            };

            let bubble_pos = transforms
                .with(bubble_entity, |t| t.position)
                .unwrap_or(Vec3::ZERO);

            let player_entity = player_tags
                .all()
                .into_iter()
                .next()
                .expect("no player entity");
            let player_pos = transforms
                .with(player_entity, |t| t.position)
                .unwrap_or(Vec3::ZERO);

            let velocity = player_pos - bubble_pos;

            let projectile_entity = entities.spawn();
            transforms.insert(projectile_entity, Transform::from_position(bubble_pos));
            projectile_tags.insert(projectile_entity, ());
            dialog_projectiles.insert(
                projectile_entity,
                DialogProjectileComponent {
                    bubble_entity,
                    correct_parry,
                    parry_window_open: false,
                    velocity,
                },
            );

            dialog_bubbles.with_mut(bubble_entity, |b| {
                b.phase = DialogPhase::ProjectileInFlight { projectile_entity };
            });
        }
    }
}

fn process_outcomes(
    bubble_tags: &mut Storage<()>,
    dialog_bubbles: &mut Storage<DialogBubbleComponent>,
    dialog_outcomes: &mut Vec<DialogOutcomeEvent>,
)
{
    let outcomes: Vec<_> = dialog_outcomes.drain(..).collect();

    for event in outcomes
    {
        let bubble_entity = event.bubble_entity;

        if dialog_bubbles.get(bubble_entity).is_none()
        {
            continue;
        }

        let choice_tag = event.outcome.to_choice_tag();

        let next = dialog_bubbles.with_mut(bubble_entity, |b| {
            let choices = b.story.get_current_choices();
            let idx = choices
                .iter()
                .position(|c| c.tags.iter().any(|t| t.trim() == choice_tag));

            if let Some(idx) = idx
            {
                let _ = b.story.choose_choice_index(idx);
            }
            else
            {
                println!("No choice found for outcome tag '{choice_tag}', using first available");
                if !choices.is_empty()
                {
                    let _ = b.story.choose_choice_index(0);
                }
            }

            if b.story.can_continue()
            {
                let (text, parry, display_time) = advance_story(&mut b.story);
                Some((text, parry, display_time))
            }
            else
            {
                None
            }
        });

        match next
        {
            Some(Some((text, parry, display_time))) =>
            {
                dialog_bubbles.with_mut(bubble_entity, |b| {
                    b.current_text = text;
                    b.correct_parry = parry;
                    b.display_time = display_time;
                    b.phase = DialogPhase::Displaying {
                        timer: display_time,
                    };
                });
            }

            Some(None) =>
            {
                bubble_tags.remove(bubble_entity);
                dialog_bubbles.remove(bubble_entity);
            }

            None =>
            {}
        }
    }
}

fn advance_story(story: &mut Story) -> (String, Option<ParryButton>, f32)
{
    let mut full_text = String::new();
    let mut parry: Option<ParryButton> = None;
    let mut display_time = DEFAULT_DISPLAY_TIME;

    while story.can_continue()
    {
        match story.cont()
        {
            Ok(line) => full_text.push_str(&line),
            Err(e) =>
            {
                eprintln!("Story error: {e}");
                break;
            }
        }

        if let Ok(tags) = story.get_current_tags()
        {
            for tag in tags
            {
                let tag = tag.trim().to_string();
                let tag = tag.as_str();
                if let Some(val) = tag.strip_prefix(PARRY_TAG_PREFIX)
                {
                    parry = ParryButton::from_tag(val.trim());
                }
                else if let Some(val) = tag.strip_prefix(TIMER_TAG_PREFIX)
                {
                    if let Ok(t) = val.trim().parse::<f32>()
                    {
                        display_time = t;
                    }
                }
            }
        }
    }

    (full_text.trim().to_string(), parry, display_time)
}
