use anyhow::anyhow;

use super::{
    DEFAULT_ARCEOS_ARCH, DEFAULT_ARCEOS_TARGET, DEFAULT_AXVISOR_ARCH, DEFAULT_AXVISOR_TARGET,
    DEFAULT_STARRY_ARCH, DEFAULT_STARRY_TARGET,
};

const ARCH_TARGETS: &[(&str, &str)] = &[
    ("aarch64", "aarch64-unknown-none-softfloat"),
    ("x86_64", "x86_64-unknown-none"),
    ("riscv64", "riscv64gc-unknown-none-elf"),
    ("loongarch64", "loongarch64-unknown-none-softfloat"),
];

const SUPPORTED_ARCH_VALUES: &str = "aarch64, x86_64, riscv64, loongarch64";
const SUPPORTED_TARGET_VALUES: &str = "x86_64-unknown-none, aarch64-unknown-none-softfloat, \
                                       riscv64gc-unknown-none-elf, \
                                       loongarch64-unknown-none-softfloat";

pub(crate) fn arch_for_target(target: &str) -> Option<&'static str> {
    ARCH_TARGETS
        .iter()
        .find_map(|(arch, candidate)| (*candidate == target).then_some(*arch))
}

pub(crate) fn starry_target_for_arch_checked(arch: &str) -> anyhow::Result<&'static str> {
    target_for_arch_checked_impl(arch, "Starry")
}

pub(crate) fn target_for_arch_checked(arch: &str) -> anyhow::Result<&'static str> {
    target_for_arch_checked_impl(arch, "Starry")
}

pub(crate) fn starry_arch_for_target_checked(target: &str) -> anyhow::Result<&'static str> {
    arch_for_target_checked_impl(target, "Starry")
}

pub(crate) fn arch_for_target_checked(target: &str) -> anyhow::Result<&'static str> {
    arch_for_target_checked_impl(target, "Starry")
}

pub(crate) fn resolve_starry_arch_and_target(
    arch: Option<String>,
    target: Option<String>,
) -> anyhow::Result<(String, String)> {
    resolve_arch_and_target(
        arch,
        target,
        DEFAULT_STARRY_ARCH,
        DEFAULT_STARRY_TARGET,
        "Starry",
    )
}

pub(crate) fn resolve_arceos_arch_and_target(
    arch: Option<String>,
    target: Option<String>,
) -> anyhow::Result<(String, String)> {
    resolve_arch_and_target(
        arch,
        target,
        DEFAULT_ARCEOS_ARCH,
        DEFAULT_ARCEOS_TARGET,
        "ArceOS",
    )
}

pub(crate) fn resolve_axvisor_arch_and_target(
    arch: Option<String>,
    target: Option<String>,
) -> anyhow::Result<(String, String)> {
    resolve_arch_and_target(
        arch,
        target,
        DEFAULT_AXVISOR_ARCH,
        DEFAULT_AXVISOR_TARGET,
        "Axvisor",
    )
}

fn arch_target_entry(arch: &str) -> Option<&'static (&'static str, &'static str)> {
    ARCH_TARGETS
        .iter()
        .find(|(candidate, _)| *candidate == arch)
}

fn target_for_arch_checked_impl(arch: &str, component: &str) -> anyhow::Result<&'static str> {
    arch_target_entry(arch)
        .map(|(_, target)| *target)
        .ok_or_else(|| {
            anyhow!(
                "unsupported {component} architecture `{arch}`; expected one of \
                 {SUPPORTED_ARCH_VALUES}"
            )
        })
}

fn arch_for_target_checked_impl(target: &str, component: &str) -> anyhow::Result<&'static str> {
    arch_for_target(target).ok_or_else(|| {
        anyhow!(
            "unsupported {component} target `{target}`; expected one of {SUPPORTED_TARGET_VALUES}"
        )
    })
}

fn resolve_arch_and_target(
    arch: Option<String>,
    target: Option<String>,
    default_arch: &str,
    default_target: &str,
    component: &str,
) -> anyhow::Result<(String, String)> {
    match (arch, target) {
        (Some(arch), Some(target)) => {
            let expected_target = target_for_arch_checked_impl(&arch, component)?;
            if target != expected_target {
                anyhow::bail!(
                    "{component} arch `{arch}` maps to target `{expected_target}`, but got \
                     `{target}`"
                );
            }
            Ok((arch, target))
        }
        (Some(arch), None) => Ok((
            arch.clone(),
            target_for_arch_checked_impl(&arch, component)?.to_string(),
        )),
        (None, Some(target)) => Ok((
            arch_for_target_checked_impl(&target, component)?.to_string(),
            target,
        )),
        (None, None) => Ok((default_arch.to_string(), default_target.to_string())),
    }
}
