pub mod component;
pub mod expr;
pub mod node;

use crate::ast::source::*;
use crate::error::{NbclError, Result, Span};
use crate::parser::Rule;
use pest::iterators::Pair;

pub(crate) fn build_file(pair: Pair<Rule>) -> Result<File> {
    let span = Span::from_pair(&pair);
    let mut items = Vec::new();

    for inner in pair.into_inner() {
        match inner.as_rule() {
            Rule::top_level_item => {
                let child = inner.into_inner().next().unwrap();
                let item = match child.as_rule() {
                    Rule::import_stmt => {
                        let mut inner = child.clone().into_inner();

                        let path_pair = inner.next().unwrap();
                        let path = unquote(path_pair.as_str());

                        let alias_pair = inner.next().ok_or_else(|| NbclError::Ast {
                            message: "import statement missing 'as' alias".into(),
                            hint: Some("All imports must follow this structure: 'import \"..\" as example'.".to_string()),
                            span: Some(Span::from_pair(&path_pair)),
                        })?;

                        let alias = alias_pair.as_str().to_string();

                        TopLevelItem::Import(ImportDef {
                            def: ImportDefType::Module(path, alias),
                            span: Span::from_pair(&child),
                        })
                    }
                    Rule::import_lib_stmt => {
                        let mut inner = child.clone().into_inner();

                        let library_pair = inner.next().unwrap();
                        let library = library_pair.as_str().to_string();

                        let item_pair = inner.nth(1).unwrap();
                        let item = item_pair.as_str().to_string();

                        TopLevelItem::Import(ImportDef {
                            def: ImportDefType::Library(library, item),
                            span: Span::from_pair(&child),
                        })
                    }
                    Rule::component_def => {
                        TopLevelItem::ComponentDef(component::build_component_def(child)?)
                    }
                    Rule::fn_def => TopLevelItem::FnDef(build_fn_def(child)?),
                    Rule::node_invocation => {
                        TopLevelItem::Node(node::build_node_invocation(child)?)
                    }
                    Rule::stmt => TopLevelItem::Stmt(expr::build_stmt(child)?),
                    _ => continue,
                };
                items.push(item);
            }
            Rule::EOI => break,
            _ => {}
        }
    }
    Ok(File { items, span })
}

fn build_fn_def(pair: Pair<Rule>) -> Result<FnDef> {
    let span = Span::from_pair(&pair);
    let mut inner = pair.into_inner();

    let name = inner.next().unwrap().as_str().to_string();

    let mut params = Vec::new();
    let mut return_type = None;
    let mut body = Vec::new();

    for part in inner {
        match part.as_rule() {
            Rule::fn_param => {
                let mut p_inner = part.into_inner();
                let p_name = p_inner.next().unwrap().as_str().to_string();
                let p_type = p_inner.next().map(|t| t.as_str().to_string());
                params.push(FnParam { name: p_name, type_hint: p_type });
            }
            Rule::fn_return_type => {
                return_type = Some(part.into_inner().next().unwrap().as_str().to_string());
            }
            Rule::fn_body => {
                for item in part.into_inner() {
                    match item.as_rule() {
                        Rule::fn_item => {
                            let child = item.into_inner().next().unwrap();
                            match child.as_rule() {
                                Rule::node_invocation => {
                                    body.push(FnItem::Node(node::build_node_invocation(child)?));
                                }
                                Rule::stmt => {
                                    body.push(FnItem::Stmt(expr::build_stmt(child)?));
                                }
                                _ => {}
                            }
                        }
                        _ => {}
                    }
                }
            }
            _ => {}
        }
    }

    Ok(FnDef { name, params, return_type, body, span })
}

pub fn unquote(s: &str) -> String {
    s.trim_matches(|c| c == '"' || c == '\'').to_string()
}
