use crate::lst_parser::{self, grammar, input, syntax_kind};
use crate::SyntaxNode;

use crate::lst_parser::output::Output;

use crate::lst_parser::error::SyntaxError;

use crate::ast::ParseError;

use rowan::ast::{AstNode, AstPtr};

use std::path::Path;
use std::{fs, io};

use rowan::GreenNode;

use std::path::PathBuf;

pub(crate) struct SourceFile {
    pub(crate) path: PathBuf,
    pub(crate) lst: rowan::GreenNode,
}

impl SourceFile {
    pub(crate) fn open(p: &Path) -> io::Result<(Self, Vec<(AstPtr<ParseError>, SyntaxError)>)> {
        assert!(p.exists());

        let f = fs::read_to_string(p)?;
        let (lst, errs) = Self::parse(f);

        Ok((
            Self {
                path: p.to_path_buf(),
                lst,
            },
            errs,
        ))
    }

    pub(crate) fn parse(f: String) -> (GreenNode, Vec<(AstPtr<ParseError>, SyntaxError)>) {
        let toks = syntax_kind::lex(&f);
        let input = input::Input::new(&toks);
        let mut parser = lst_parser::Parser::new(input);

        grammar::source_file(&mut parser);

        let p_out = parser.finish();
        let (lst, errs) = Output::from_parser_output(toks, p_out).dissolve();

        (lst.clone(), Self::find_errs(lst, errs))
    }

    pub(crate) fn find_errs(
        lst: GreenNode,
        mut errs: Vec<SyntaxError>,
    ) -> Vec<(AstPtr<ParseError>, SyntaxError)> {
        let mut out = Vec::new();
        errs.reverse();

        let lst = SyntaxNode::new_root(lst);
        Self::find_errs_recursive(&mut out, lst, &mut errs);

        out
    }

    pub(crate) fn find_errs_recursive(
        mut out: &mut Vec<(AstPtr<ParseError>, SyntaxError)>,
        lst: SyntaxNode,
        mut errs: &mut Vec<SyntaxError>,
    ) {
        lst.children()
            .filter_map(|c| ParseError::cast(c))
            .for_each(|e| out.push((AstPtr::new(&e), errs.pop().unwrap())));

        lst.children()
            .for_each(|c| Self::find_errs_recursive(out, c, errs));
    }
}

#[cfg(test)]
mod tests {
    use crate::world::files::source_file::SourceFile;

    fn check_find_errs(input: &str, expected: &[&str]) {
        let (_, errs) = SourceFile::parse(input.to_string());

        let errs = errs
            .into_iter()
            .map(|(loc, err)| format!("{:?}@{:?}", err, loc.syntax_node_ptr().text_range()))
            .collect::<Vec<String>>();

        assert_eq!(
            errs,
            expected
                .into_iter()
                .map(|s| s.to_string())
                .collect::<Vec<_>>()
        )
    }

    #[test]
    fn test_find_errs() {
        check_find_errs(
            "def meow = ;\n mod ;",
            &["Expected([DEF_BODY])@11..11", "Expected([IDENT])@18..18"],
        );

        check_find_errs(
            "def awawa = a |",
            &["UnterminatedTopLevelItem@0..15", "PipelineNeedsSink@12..15"],
        )
    }
}
