use std::collections::HashMap;
use std::fs;
use std::io::Read;
use std::path::PathBuf;

use proc_macro2::Span;
use syn::visit::Visit;

use crate::environment::Environment;
use crate::visitor::{CustomDerive, CustomModuleDerive, Visitor};

pub(crate) struct Parser {
    crate_name: String,
    crate_root: PathBuf,
    start_file: PathBuf,
    pragmas: Vec<String>,
    derives: Vec<CustomDerive>,
    module_derives: HashMap<String, Vec<CustomModuleDerive>>,
}

impl Parser {
    pub(crate) fn new() -> Self {
        let root_module = std::env::var("CARGO_PKG_NAME").unwrap().replace("-", "_");
        let crate_root = PathBuf::from(
            PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
                .file_name()
                .unwrap(),
        );

        Self {
            crate_name: root_module,
            crate_root,
            start_file: PathBuf::from("src/lib.rs"),
            pragmas: vec![],
            derives: vec![],
            module_derives: Default::default(),
        }
    }

    pub(crate) fn custom(
        crate_name: &str,
        crate_root: impl Into<PathBuf>,
        start_file: impl Into<PathBuf>,
    ) -> Self {
        Self {
            crate_name: crate_name.to_string(),
            crate_root: crate_root.into(),
            start_file: start_file.into(),
            pragmas: vec![],
            derives: vec![],
            module_derives: Default::default(),
        }
    }

    pub(crate) fn with_pragmas<I, T: ToString>(mut self, pragmas: I) -> Self
    where
        I: IntoIterator<Item = T>,
    {
        self.pragmas = pragmas.into_iter().map(|each| each.to_string()).collect();
        self
    }

    pub(crate) fn with_derives(mut self, derives: &[CustomDerive]) -> Self {
        self.derives = derives.to_vec();
        self
    }

    pub(crate) fn with_module_derives(
        mut self,
        module_derives: HashMap<String, Vec<CustomModuleDerive>>,
    ) -> Self {
        self.module_derives = module_derives;
        self
    }

    pub(crate) fn parse(&self) -> Environment {
        let visitor = self.parse_root();
        visitor.into_environment()
    }

    fn parse_root(&self) -> Visitor {
        let mut visitor = Visitor::new(self.crate_root.as_path(), self.start_file.as_path())
            .with_pragmas(&self.pragmas)
            .with_derives(self.derives.clone())
            .with_module_derives(self.module_derives.clone());

        visitor.enter_mod(&syn::Ident::new(&self.crate_name, Span::call_site()));

        let path = visitor.root_path();
        println!("cargo::rerun-if-changed={}", path.display());

        let mut file = fs::File::open(path)
            .map_err(|e| {
                format!(
                    "{}: {} in {}",
                    e,
                    path.display(),
                    std::env::current_dir().unwrap().display()
                )
            })
            .unwrap();
        let mut content = String::new();
        file.read_to_string(&mut content).unwrap();

        let ast = syn::parse_file(&content).unwrap();

        visitor.visit_file(&ast);

        visitor
    }

    pub(crate) fn export_to(&self, file_path: impl AsRef<std::path::Path>) {
        self.parse().export_to(file_path);
    }
}

impl Default for Parser {
    fn default() -> Self {
        Self::new()
    }
}
