extern crate proc_macro;

use proc_macro::TokenStream;
use std::{env, path::PathBuf};
use syn::parse_str;
use toml_edit::{DocumentMut, Item};

pub struct MaidenXManifest {
    manifest: DocumentMut,
}

impl Default for MaidenXManifest {
    fn default() -> Self {
        Self {
            manifest: env::var_os("CARGO_MANIFEST_DIR")
                .map(PathBuf::from)
                .map(|mut path| {
                    path.push("Cargo.toml");
                    if !path.exists() {
                        panic!("No Cargo manifest found for crate. Expected: {}", path.display());
                    }
                    let manifest =
                        std::fs::read_to_string(path.clone()).unwrap_or_else(|_| panic!("Unable to read cargo manifest: {}", path.display()));
                    manifest
                        .parse::<DocumentMut>()
                        .unwrap_or_else(|_| panic!("Failed to parse cargo manifest: {}", path.display()))
                })
                .expect("CARGO_MANIFEST_DIR is not defined."),
        }
    }
}

const MAIDENX: &str = "maidenx";
const MAIDENX_INTERNAL: &str = "maidenx_internal";

impl MaidenXManifest {
    pub fn maybe_get_path(&self, name: &str) -> Option<syn::Path> {
        if name == env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME is not defined.") {
            return Some(parse_str("crate").unwrap());
        }

        fn dep_package(dep: &Item) -> Option<&str> {
            if dep.as_str().is_some() {
                None
            } else {
                dep.get("package").map(|name| name.as_str().unwrap())
            }
        }

        let find_in_deps = |deps: &Item| -> Option<syn::Path> {
            let package = if let Some(dep) = deps.get(name) {
                return Some(Self::parse_str(dep_package(dep).unwrap_or(name)));
            } else if let Some(dep) = deps.get(MAIDENX) {
                dep_package(dep).unwrap_or(MAIDENX)
            } else if let Some(dep) = deps.get(MAIDENX_INTERNAL) {
                dep_package(dep).unwrap_or(MAIDENX_INTERNAL)
            } else {
                return None;
            };

            let mut path = Self::parse_str::<syn::Path>(package);
            if let Some(module) = name.strip_prefix("maidenx_") {
                path.segments.push(Self::parse_str(module));
            }
            Some(path)
        };

        let deps = self.manifest.get("dependencies");
        let deps_dev = self.manifest.get("dev-dependencies");

        deps.and_then(find_in_deps).or_else(|| deps_dev.and_then(find_in_deps))
    }

    pub fn get_path(&self, name: &str) -> syn::Path {
        let sanitized_name = name.replace('-', "_");

        self.maybe_get_path(&sanitized_name).unwrap_or_else(|| Self::parse_str(&sanitized_name))
    }

    pub fn try_parse_str<T: syn::parse::Parse>(path: &str) -> Option<T> {
        syn::parse(path.parse::<TokenStream>().ok()?).ok()
    }

    pub fn parse_str<T: syn::parse::Parse>(path: &str) -> T {
        Self::try_parse_str(path).unwrap()
    }
}
