use std::collections::HashMap;

use bevy_ecs::prelude::*;
use rdf_parsers::{IncrementalBias, PrevParseInfo};
use rowan::{GreenNode, NodeOrToken};
use swls_core::prelude::*;
use swls_lang_turtle::{ecs::parse::derive_triples_system, lang::parser::TurtleParseError};
use tracing::instrument;

use crate::TriGLang;

pub fn setup_parsing(world: &mut World) {
    use swls_core::feature::parse::*;
    world.schedule_scope(ParseLabel, |_, schedule| {
        schedule.add_systems((
            parse_trig_system,
            swls_lang_rdf_base::parse::derive_prefixes_system::<TriGLang>
                .after(parse_trig_system)
                .before(prefixes),
            derive_triples_system::<TriGLang>
                .after(parse_trig_system)
                .before(triples),
        ));
    });
}


fn extract_trig_cst_tokens(
    node: &rowan::SyntaxNode<rdf_parsers::trig::parser::Lang>,
) -> Vec<Spanned<rowan::SyntaxKind>> {
    use rdf_parsers::trig::parser::SyntaxKind;
    let mut tokens = Vec::new();
    for node_or_token in node.descendants_with_tokens() {
        if let NodeOrToken::Token(t) = node_or_token {
            let kind = t.kind();
            if kind == SyntaxKind::WhiteSpace {
                continue;
            }
            let range = t.text_range();
            let span = usize::from(range.start())..usize::from(range.end());
            tokens.push(spanned(rowan::SyntaxKind(kind as u16), span));
        }
    }
    tokens
}

fn collect_errors(
    node: &rowan::SyntaxNode<rdf_parsers::trig::parser::Lang>,
) -> Vec<TurtleParseError> {
    use rdf_parsers::trig::parser::SyntaxKind;
    let mut errors = Vec::new();
    let mut stack = vec![node.clone()];
    while let Some(current) = stack.pop() {
        for child in current.children_with_tokens() {
            match child {
                NodeOrToken::Node(n) => {
                    if n.kind() == SyntaxKind::Error {
                        let range = rdf_parsers::effective_error_span::<
                            rdf_parsers::trig::parser::Lang,
                        >(&n);
                        let msg = n
                            .parent()
                            .map(|p| format!("Expected: {:?}", p.kind()))
                            .unwrap_or_else(|| format!("Unexpected: {}", n.text()));
                        errors.push(TurtleParseError { range, msg });
                    } else {
                        stack.push(n);
                    }
                }
                NodeOrToken::Token(t) => {
                    if t.kind() == SyntaxKind::Error {
                        let r = t.text_range();
                        errors.push(TurtleParseError {
                            range: r.start().into()..r.end().into(),
                            msg: format!("Unexpected: {}", t.text()),
                        });
                    }
                }
            }
        }
    }
    errors
}

#[instrument(skip(query, commands, prev_infos, config))]
fn parse_trig_system(
    query: Query<(Entity, &Source, &Label), (Changed<Source>, With<TriGLang>)>,
    mut commands: Commands,
    mut prev_infos: Local<HashMap<String, PrevParseInfo>>,
    config: Res<ServerConfig>,
) {
    if !config.config.trig.unwrap_or(true) {
        return;
    }
    for (entity, source, label) in &query {
        use rdf_parsers::trig::{
            convert::convert,
            parser::{Lang, Rule, SyntaxKind},
        };

        let prev = prev_infos.get(label.as_str());
        let (parse, new_prev) = rdf_parsers::parse_incremental(
            Rule::new(SyntaxKind::TrigDoc),
            source.0.as_str(),
            prev,
            IncrementalBias::default(),
        );

        let gn = parse.green_node.clone();
        prev_infos.insert(label.to_string(), new_prev);

        let syntax = parse.syntax::<Lang>();
        let errors = collect_errors(&syntax);
        let mut trig_model = convert(&syntax);
        trig_model.set_base = Some(label.to_string());

        let cst_tokens = extract_trig_cst_tokens(&syntax);

        tracing::debug!(
            "{} triples ({} parse errors)",
            trig_model.triples.len(),
            errors.len()
        );

        let span = 0..source.0.len();
        let element = Element::<TriGLang>(spanned(trig_model, span));

        if errors.is_empty() {
            commands
                .entity(entity)
                .insert((element, Errors(errors), CstTokens(cst_tokens), Wrapped(gn)))
                .remove::<Dirty>();
        } else {
            commands.entity(entity).insert((
                element,
                Errors(errors),
                CstTokens(cst_tokens),
                Dirty,
                Wrapped(gn),
            ));
        }
    }
}

pub(crate) fn format_trig_system(
    mut query: Query<(&RopeC, &Wrapped<GreenNode>, &mut FormatRequest), With<TriGLang>>,
) {
    use swls_core::lsp_types::{Position, Range};
    for (source, node, mut request) in &mut query {
        if request.0.is_some() {
            tracing::debug!("Didn't format with the trig format system, already formatted");
            continue;
        }
        tracing::debug!("Formatting with trig format system");

        let root = rowan::SyntaxNode::new_root(node.0.clone());

        let formatted = rdf_parsers::trig::format::format(&root, 80);

        request.0 = Some(vec![swls_core::lsp_types::TextEdit::new(
            Range::new(
                Position::new(0, 0),
                Position::new(source.0.len_lines() as u32 + 1, 0),
            ),
            formatted,
        )]);
    }
}
