use super::{Context, Module, ModuleConfig};

use crate::configs::c::{CConfig, CConfigMarker};
use crate::configs::cc::CcConfig;
use crate::configs::cpp::{CppConfig, CppConfigMarker};
use crate::formatter::StringFormatter;
use crate::formatter::VersionFormatter;

use crate::segment::Segment;

use semver::Version;
use std::borrow::Cow;
use std::ops::Deref;
use std::sync::LazyLock;

#[derive(Clone, Copy)]
pub enum Lang {
    C,
    Cpp,
}

fn is_cc_project<T>(config: &CcConfig<T>, context: &Context) -> Option<bool> {
    Some(
        context
            .try_begin_scan()?
            .set_extensions(&config.detect_extensions)
            .set_files(&config.detect_files)
            .set_folders(&config.detect_folders)
            .is_match(),
    )
}

fn parse_module<T>(
    context: &Context,
    module: &mut Module,
    config: CcConfig<T>,
    compilers: [(&str, &str); 2],
) -> Result<Vec<Segment>, crate::formatter::string_formatter::StringFormatterError> {
    StringFormatter::new(config.format).and_then(|formatter| {
        let cc_compiler_info = LazyLock::new(|| context.exec_cmds_return_first(&config.commands));

        formatter
            .map_meta(|var, _| match var {
                "symbol" => Some(config.symbol),
                _ => None,
            })
            .map_style(|variable| match variable {
                "style" => Some(Ok(config.style)),
                _ => None,
            })
            .map(|variable| match variable {
                "name" => {
                    let cc_compiler_info = &cc_compiler_info.deref().as_ref()?.stdout;

                    let cc_compiler =
                        compilers
                            .iter()
                            .find_map(|(compiler_name, compiler_hint)| {
                                cc_compiler_info
                                    .contains(compiler_hint)
                                    .then_some(*compiler_name)
                            })?;
                    Some(Ok(Cow::Borrowed(cc_compiler)))
                }
                "version" => {
                    let cc_compiler_info = &cc_compiler_info.deref().as_ref()?.stdout;

                    VersionFormatter::format_module_version(
                        module.get_name(),
                        cc_compiler_info
                            .split_whitespace()
                            .find(|word| Version::parse(word).is_ok())?,
                        config.version_format,
                    )
                    .map(Cow::Owned)
                    .map(Ok)
                }
                _ => None,
            })
            .parse(None, Some(context))
    })
}

fn create_module<'a, T>(
    context: &'a Context,
    lang: &str,
    compilers: [(&str, &str); 2],
    mut module: Module<'a>,
    config: CcConfig<T>,
) -> Option<Module<'a>> {
    if config.disabled {
        return None;
    }

    if !is_cc_project(&config, context)? {
        return None;
    }

    let parsed = parse_module(context, &mut module, config, compilers);

    module.set_segments(match parsed {
        Ok(segments) => segments,
        Err(error) => {
            log::warn!("Error in module `cc` for `lang: {lang}` :\n{error}");
            return None;
        }
    });

    Some(module)
}

pub fn module<'a>(context: &'a Context, lang: Lang) -> Option<Module<'a>> {
    match lang {
        Lang::C => {
            let lang = "c";
            let compilers = [("clang", "clang"), ("gcc", "Free Software Foundation")];
            let module = context.new_module(lang);
            let config = CConfig::try_load(module.config);

            create_module::<CConfigMarker>(context, lang, compilers, module, config)
        }
        Lang::Cpp => {
            let lang = "cpp";
            let compilers = [("clang++", "clang"), ("g++", "Free Software Foundation")];
            let module = context.new_module(lang);
            let config = CppConfig::try_load(module.config);

            create_module::<CppConfigMarker>(context, lang, compilers, module, config)
        }
    }
}
