//! Catalogue-driven nightly selection for Dylint and its nested commands.

use serde::Deserialize;
use std::{
    collections::BTreeMap,
    io::Read,
    path::{Path, PathBuf},
    process::Stdio,
    time::{Duration, SystemTime},
};
use wait_timeout::ChildExt;

use crate::core::{
    command_output_with_timeout, suppress_windows_console_window, SoldrError, SoldrPaths,
    TargetTriple,
};
use crate::{apply_implicit_toolchain_homes, resolve_toolchain_binary_for_channel, rustup_binary};

pub(crate) const TOOLCHAIN_ENV_VAR: &str = "SOLDR_DYLINT_TOOLCHAIN";
pub(crate) const COMPILER_RELEASE_ENV_VAR: &str = "SOLDR_DYLINT_RUSTC_RELEASE";
pub(crate) const COMPILER_COMMIT_ENV_VAR: &str = "SOLDR_DYLINT_RUSTC_COMMIT_HASH";
pub(crate) const CACHE_IDENTITY_ENV_VAR: &str = "SOLDR_DYLINT_CACHE_IDENTITY";
pub(crate) const PREPARED_IDENTITY_ENV_VAR: &str = "SOLDR_DYLINT_PREPARED_IDENTITY";
pub(crate) const SUCCESS_MARKER_ENV_VAR: &str = "SOLDR_DYLINT_SUCCESS_MARKER";
pub(crate) const CONFIGURED_TOOLCHAIN_ENV_VAR: &str = "SOLDR_DYLINT_CONFIGURED_TOOLCHAIN";
pub(crate) const CONFIGURED_COMPILER_RELEASE_ENV_VAR: &str =
    "SOLDR_DYLINT_CONFIGURED_RUSTC_RELEASE";
pub(crate) const CONFIGURED_COMPILER_COMMIT_ENV_VAR: &str =
    "SOLDR_DYLINT_CONFIGURED_RUSTC_COMMIT_HASH";

/// Overrides the freshness window (seconds) for the warm-run prepared-plan
/// marker consulted by [`prepare`]. `0` means "never trust the marker" —
/// every run pays the full catalogue-fetch + rustup-probe path. Unset
/// falls back to [`DEFAULT_PREPARE_TTL`].
pub(crate) const PREPARE_TTL_ENV_VAR: &str = "SOLDR_DYLINT_PREPARE_TTL_SECS";
/// When truthy (`1` / `true`, case-insensitive), skips the prepared-plan
/// marker entirely and always re-runs the full catalogue-fetch + rustup
/// verification path, even if a fresh marker exists on disk.
pub(crate) const REVERIFY_ENV_VAR: &str = "SOLDR_DYLINT_REVERIFY";

const MAP_ASSET: &str = "rust-nightly-versions.v1.json";
const REQUIRED_COMPONENTS: &[&str] = &["rustc-dev", "rust-src", "llvm-tools-preview"];

/// Default freshness window for the warm-run prepared-plan marker: 24
/// hours. Long enough that a normal dev inner loop (many `soldr cargo
/// dylint` invocations across a day) skips the network + subprocess
/// probes entirely, short enough that a stale marker is naturally
/// reclaimed without operator intervention.
const DEFAULT_PREPARE_TTL: Duration = Duration::from_secs(24 * 60 * 60);
/// Schema tag for the on-disk marker layout
/// (`<soldr_root>/dylint/prepared/<PREPARE_MARKER_SCHEMA>/<version>.identity`).
/// Bump this if the marker file format ever changes shape so stale
/// markers from an older soldr build are never misread.
const PREPARE_MARKER_SCHEMA: &str = "v1";

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DylintToolchainPlan {
    pub channel: String,
    pub compiler_release: String,
    pub compiler_commit: String,
}

impl DylintToolchainPlan {
    pub(crate) fn cache_identity(&self) -> String {
        format!(
            "{}|{}|{}",
            self.channel, self.compiler_release, self.compiler_commit
        )
    }

    pub(crate) fn apply_to_command(&self, command: &mut std::process::Command) {
        command.env("RUSTUP_TOOLCHAIN", &self.channel);
        command.env(TOOLCHAIN_ENV_VAR, &self.channel);
        command.env(COMPILER_RELEASE_ENV_VAR, &self.compiler_release);
        command.env(COMPILER_COMMIT_ENV_VAR, &self.compiler_commit);
        command.env(CACHE_IDENTITY_ENV_VAR, self.cache_identity());
        command.env(PREPARED_IDENTITY_ENV_VAR, self.cache_identity());
        apply_dylint_driver_path(command);
    }
}

/// Refuse to launch cargo-dylint unless its exact toolchain driver already
/// exists and answers the bounded version probe. Upstream cargo-dylint builds
/// this driver automatically when it is absent or stale; preflighting the same
/// version signal is what makes Soldr's binary-or-exit policy cover the driver,
/// not just the two CLI executables.
pub(crate) fn require_prebuilt_driver(
    plan: &DylintToolchainPlan,
    paths: &SoldrPaths,
) -> Result<PathBuf, SoldrError> {
    let driver_root = std::env::var_os("DYLINT_DRIVER_PATH")
        .filter(|value| !value.is_empty())
        .map(PathBuf::from)
        .unwrap_or_else(|| paths.root.join("dylint").join("drivers"));
    let driver_channel = if is_fully_qualified_nightly(&plan.channel) {
        plan.channel.clone()
    } else {
        let host = TargetTriple::host()?.triple();
        format!("{}-{host}", plan.channel)
    };
    let driver_dir = driver_root.join(&driver_channel);
    let driver = ["dylint-driver", "dylint-driver.exe", "dylint-driver.cmd"]
        .into_iter()
        .map(|name| driver_dir.join(name))
        .find(|candidate| candidate.is_file())
        .ok_or_else(|| unavailable_driver_error(plan, "prebuilt driver is missing"))?;

    let mut command = std::process::Command::new(&driver);
    command
        .arg("-V")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped());
    apply_driver_runtime_environment(&mut command, plan)?;
    suppress_windows_console_window(&mut command);
    let mut child = command.spawn().map_err(|error| {
        unavailable_driver_error(plan, &format!("version probe could not start: {error}"))
    })?;
    let status = match child.wait_timeout(Duration::from_secs(2)) {
        Err(error) => {
            let _ = child.kill();
            let _ = child.wait();
            return Err(unavailable_driver_error(
                plan,
                &format!("version probe failed: {error}"),
            ));
        }
        Ok(Some(status)) => status,
        Ok(None) => {
            let _ = child.kill();
            let _ = child.wait();
            return Err(unavailable_driver_error(
                plan,
                "version probe exceeded the 2-second deadline",
            ));
        }
    };
    let mut stdout = String::new();
    if let Some(mut pipe) = child.stdout.take() {
        pipe.read_to_string(&mut stdout).map_err(|error| {
            unavailable_driver_error(plan, &format!("version probe output failed: {error}"))
        })?;
    }
    if !status.success() {
        return Err(unavailable_driver_error(
            plan,
            &format!("version probe exited with {status}"),
        ));
    }
    let expected = crate::fetch::known_tools::lookup_by_crate("cargo-dylint")
        .and_then(|spec| spec.pinned_version)
        .ok_or_else(|| SoldrError::Other("cargo-dylint must have a registry pin".into()))?;
    let actual = dylint_driver_version(&stdout);
    if actual != Some(expected) {
        return Err(unavailable_driver_error(
            plan,
            &format!("driver version is {actual:?}, expected {expected:?}"),
        ));
    }
    Ok(driver)
}

fn apply_driver_runtime_environment(
    command: &mut std::process::Command,
    plan: &DylintToolchainPlan,
) -> Result<(), SoldrError> {
    apply_driver_runtime_environment_impl(command, plan)
}

fn dylint_toolchain_dirs(plan: &DylintToolchainPlan) -> Result<(PathBuf, PathBuf), SoldrError> {
    let rustc = resolve_toolchain_binary_for_channel("rustc", Some(&plan.channel))?;
    let bin_dir = rustc
        .parent()
        .map(Path::to_path_buf)
        .ok_or_else(|| SoldrError::Other("Dylint rustc has no parent directory".into()))?;
    let toolchain_root = bin_dir
        .parent()
        .map(Path::to_path_buf)
        .ok_or_else(|| SoldrError::Other("Dylint rustc has no toolchain root".into()))?;
    Ok((bin_dir, toolchain_root))
}

fn apply_driver_runtime_environment_impl(
    command: &mut std::process::Command,
    plan: &DylintToolchainPlan,
) -> Result<(), SoldrError> {
    match crate::platform::host::facts::os() {
        crate::platform::host::facts::HostOs::Windows => {
            let (bin_dir, _) = dylint_toolchain_dirs(plan)?;
            prepend_command_path(command, "PATH", &bin_dir)
        }
        crate::platform::host::facts::HostOs::Linux => {
            let (_, toolchain_root) = dylint_toolchain_dirs(plan)?;
            prepend_command_path(command, "LD_LIBRARY_PATH", &toolchain_root.join("lib"))?;
            Ok(())
        }
        crate::platform::host::facts::HostOs::MacOs => {
            let (_, toolchain_root) = dylint_toolchain_dirs(plan)?;
            prepend_command_path(command, "DYLD_LIBRARY_PATH", &toolchain_root.join("lib"))?;
            Ok(())
        }
    }
}

fn prepend_command_path(
    command: &mut std::process::Command,
    key: &str,
    directory: &Path,
) -> Result<(), SoldrError> {
    let existing = std::env::var_os(key);
    let paths = std::iter::once(directory.to_path_buf()).chain(
        existing
            .as_deref()
            .map(std::env::split_paths)
            .into_iter()
            .flatten(),
    );
    let joined = std::env::join_paths(paths)
        .map_err(|error| SoldrError::Other(format!("failed to construct Dylint {key}: {error}")))?;
    command.env(key, joined);
    Ok(())
}

/// Resolve a missing driver from soldr-toolchain before cargo-dylint can
/// trigger its implicit source-build fallback.
pub(crate) async fn ensure_prebuilt_driver(
    plan: &DylintToolchainPlan,
    paths: &SoldrPaths,
) -> Result<PathBuf, SoldrError> {
    if let Ok(driver) = require_prebuilt_driver(plan, paths) {
        return Ok(driver);
    }
    let version = crate::fetch::known_tools::lookup_by_crate("cargo-dylint")
        .and_then(|spec| spec.pinned_version)
        .ok_or_else(|| SoldrError::Other("cargo-dylint must have a registry pin".into()))?;
    let driver_root = std::env::var_os("DYLINT_DRIVER_PATH")
        .filter(|value| !value.is_empty())
        .map(PathBuf::from)
        .unwrap_or_else(|| paths.root.join("dylint").join("drivers"));
    crate::fetch::ensure_dylint_driver(paths, version, &plan.channel, &driver_root).await?;
    require_prebuilt_driver(plan, paths)
}

fn dylint_driver_version(stdout: &str) -> Option<&str> {
    stdout
        .split_whitespace()
        .last()
        .filter(|value| !value.is_empty())
}

fn unavailable_driver_error(plan: &DylintToolchainPlan, reason: &str) -> SoldrError {
    let host = TargetTriple::host()
        .map(|target| target.triple())
        .unwrap_or_else(|_| "unknown-host".to_string());
    let version = crate::fetch::known_tools::lookup_by_crate("cargo-dylint")
        .and_then(|spec| spec.pinned_version)
        .unwrap_or("unknown");
    SoldrError::Other(format!(
        "Dylint v{version} is not built for this machine (host: {host}; missing or unusable \
         component: dylint-driver for {}). Soldr will not build Dylint from source. \
         Cause: {reason}. Corrective action: install the Dylint v{version} driver for {host} \
         under DYLINT_DRIVER_PATH, publish a matching prebuilt release asset, or select a \
         Dylint version that provides {host} prebuilts.",
        plan.channel
    ))
}

/// Give the dylint driver cargo-dylint builds a stable soldr-owned
/// home instead of the tool's own unmanaged default (normally
/// `~/.dylint_drivers` or wherever `$DYLINT_DRIVER_PATH` happens to
/// point). A fixed path means warm runs reuse the already-built
/// driver and CI caches have something deterministic to restore.
/// Respects an explicit caller-set `DYLINT_DRIVER_PATH` — soldr never
/// clobbers a user override.
fn apply_dylint_driver_path(command: &mut std::process::Command) {
    if std::env::var_os("DYLINT_DRIVER_PATH").is_some() {
        return;
    }
    let Ok(paths) = crate::core::SoldrPaths::new() else {
        return;
    };
    let driver_dir = paths.root.join("dylint").join("drivers");
    if std::fs::create_dir_all(&driver_dir).is_err() {
        // Best-effort: if the directory cannot be created, fall back
        // to the tool's own default rather than pointing at a
        // nonexistent path.
        return;
    }
    command.env("DYLINT_DRIVER_PATH", driver_dir);
}

pub(crate) fn write_success_marker(plan: &DylintToolchainPlan) -> Result<(), SoldrError> {
    let Some(path) = std::env::var_os(SUCCESS_MARKER_ENV_VAR)
        .filter(|value| !value.is_empty())
        .map(std::path::PathBuf::from)
    else {
        return Ok(());
    };
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    std::fs::write(path, format!("{}\n", plan.cache_identity()))?;
    Ok(())
}

pub(crate) fn clear_success_marker() -> Result<(), SoldrError> {
    let Some(path) = std::env::var_os(SUCCESS_MARKER_ENV_VAR)
        .filter(|value| !value.is_empty())
        .map(std::path::PathBuf::from)
    else {
        return Ok(());
    };
    match std::fs::remove_file(path) {
        Ok(()) => Ok(()),
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
        Err(error) => Err(error.into()),
    }
}

#[derive(Debug, Deserialize)]
struct NightlyVersionMap {
    schema_version: u32,
    nightlies: BTreeMap<String, NightlyIdentity>,
    versions: BTreeMap<String, VersionBucket>,
}

#[derive(Debug, Deserialize)]
struct NightlyIdentity {
    rust_version: String,
    rustc_release: String,
    rustc_commit_hash: String,
}

#[derive(Debug, Deserialize)]
struct VersionBucket {
    nightlies: Vec<String>,
    selected: String,
}

pub(crate) async fn prepare(
    requested_channel: Option<&str>,
    workspace_root: &Path,
) -> Result<DylintToolchainPlan, SoldrError> {
    if non_empty_env(TOOLCHAIN_ENV_VAR).is_none()
        && plan_from_configured_environment()?.is_none()
        && !truthy_env(REVERIFY_ENV_VAR)
    {
        let requested = requested_toolchain_channel(requested_channel, workspace_root)?;
        if requested
            .as_deref()
            .is_none_or(|channel| !is_dated_nightly(channel))
        {
            let version = requested_rust_version(requested.as_deref())?;
            if let Some(mut plan) = load_prepared_marker(&version) {
                plan.channel = qualify_toolchain_name(&plan.channel)?;
                eprintln!(
                    "soldr: dylint toolchain {} ready (cached identity)",
                    plan.channel
                );
                return Ok(plan);
            }
        }
    }
    let plan = resolve_plan_inner(requested_channel, workspace_root, true).await?;
    prepare_resolved(plan)
}

pub(crate) async fn resolve_plan(
    requested_channel: Option<&str>,
    workspace_root: &Path,
) -> Result<DylintToolchainPlan, SoldrError> {
    resolve_plan_inner(requested_channel, workspace_root, false).await
}

async fn resolve_plan_inner(
    requested_channel: Option<&str>,
    workspace_root: &Path,
    install_explicit_if_unmapped: bool,
) -> Result<DylintToolchainPlan, SoldrError> {
    let plan = if let Some(channel) = non_empty_env(TOOLCHAIN_ENV_VAR) {
        plan_from_retained_environment(&channel)?
    } else if let Some(plan) = plan_from_configured_environment()? {
        plan
    } else {
        let requested = requested_toolchain_channel(requested_channel, workspace_root)?;
        let bytes = crate::fetch::fetch_verified_catalogue_asset(
            "zackees",
            "soldr-toolchain",
            "assets",
            MAP_ASSET,
        )
        .await?;
        if let Some(channel) = requested.as_deref().filter(|value| is_dated_nightly(value)) {
            match select_explicit_from_map(&bytes, channel) {
                Ok(plan) => plan,
                Err(map_error) => {
                    if let Some(plan) = plan_from_installed_explicit_nightly(channel)? {
                        plan
                    } else if install_explicit_if_unmapped {
                        install_and_observe_explicit_nightly(channel)?
                    } else {
                        return Err(map_error);
                    }
                }
            }
        } else {
            let version = requested_rust_version(requested.as_deref())?;
            select_from_map(&bytes, &version)?
        }
    };
    Ok(plan)
}

fn plan_from_installed_explicit_nightly(
    channel: &str,
) -> Result<Option<DylintToolchainPlan>, SoldrError> {
    if resolve_toolchain_binary_for_channel(concat!("rust", "c"), Some(channel)).is_err() {
        return Ok(None);
    }
    let (compiler_release, compiler_commit) = observe_compiler(channel)?;
    let identity = NightlyIdentity {
        rust_version: major_minor(&compiler_release).unwrap_or_default(),
        rustc_release: compiler_release.clone(),
        rustc_commit_hash: compiler_commit.clone(),
    };
    validate_identity(channel, &identity)?;
    Ok(Some(DylintToolchainPlan {
        channel: channel.to_string(),
        compiler_release,
        compiler_commit,
    }))
}

fn install_and_observe_explicit_nightly(channel: &str) -> Result<DylintToolchainPlan, SoldrError> {
    if resolve_toolchain_binary_for_channel(concat!("rust", "c"), Some(channel)).is_err() {
        let code =
            crate::toolchain::rustup_toolchain_install_with_profile(channel, Some("minimal"))?;
        if code != 0 {
            return Err(SoldrError::Other(format!(
                "rustup failed to install {channel} (exit {code})"
            )));
        }
    }
    plan_from_installed_explicit_nightly(channel)?.ok_or_else(|| {
        SoldrError::Other(format!(
            "installed explicit Dylint toolchain `{channel}` could not be resolved"
        ))
    })
}

pub(crate) fn prepare_resolved(
    mut plan: DylintToolchainPlan,
) -> Result<DylintToolchainPlan, SoldrError> {
    let prepared_identity = plan.cache_identity();
    if non_empty_env(PREPARED_IDENTITY_ENV_VAR).as_deref() != Some(prepared_identity.as_str()) {
        ensure_installed(&plan)?;
        verify_installed_identity(&plan)?;
    }
    plan.channel = qualify_toolchain_name(&plan.channel)?;
    if let Some(version) = major_minor(&plan.compiler_release) {
        if let Err(error) = write_prepared_marker(&version, &plan) {
            eprintln!("soldr: failed to write dylint prepared marker: {error}");
        }
    }
    Ok(plan)
}

pub(crate) fn verify_if_installed(plan: &DylintToolchainPlan) -> Result<bool, SoldrError> {
    if resolve_toolchain_binary_for_channel(concat!("rust", "c"), Some(&plan.channel)).is_err() {
        return Ok(false);
    }
    verify_installed_identity(plan)?;
    Ok(true)
}

pub(crate) fn verify_observed_identity(plan: &DylintToolchainPlan) -> Result<(), SoldrError> {
    verify_installed_identity(plan)
}

fn qualify_toolchain_name(channel: &str) -> Result<String, SoldrError> {
    if is_fully_qualified_nightly(channel) {
        return Ok(channel.to_string());
    }
    let binary = resolve_toolchain_binary_for_channel(concat!("rust", "c"), Some(channel))?;
    let host = observe_compiler_host(&binary, channel)?;
    Ok(format!("{channel}-{host}"))
}

fn is_fully_qualified_nightly(channel: &str) -> bool {
    channel
        .strip_prefix("nightly-")
        .and_then(|value| value.get(10..))
        .is_some_and(|suffix| suffix.starts_with('-') && suffix.len() > 1)
}

/// `<soldr_root>/dylint/prepared/<PREPARE_MARKER_SCHEMA>/<sanitized version>.identity`
fn prepared_marker_path(base_dir: &Path, version: &str) -> PathBuf {
    base_dir
        .join("dylint")
        .join("prepared")
        .join(PREPARE_MARKER_SCHEMA)
        .join(format!("{}.identity", sanitize_marker_key(version)))
}

/// Replace any character that is not filesystem-safe across every
/// soldr-supported host with `_`. `version` is normally a plain
/// `major.minor` string, but this also tolerates an explicit channel
/// string being threaded through in the future without risking path
/// traversal or reserved-character issues on Windows.
fn sanitize_marker_key(value: &str) -> String {
    value
        .chars()
        .map(|c| {
            if c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == '_' {
                c
            } else {
                '_'
            }
        })
        .collect()
}

fn prepare_ttl() -> Duration {
    non_empty_env(PREPARE_TTL_ENV_VAR)
        .and_then(|value| value.parse::<u64>().ok())
        .map(Duration::from_secs)
        .unwrap_or(DEFAULT_PREPARE_TTL)
}

fn truthy_env(key: &str) -> bool {
    non_empty_env(key)
        .map(|value| value == "1" || value.eq_ignore_ascii_case("true"))
        .unwrap_or(false)
}

/// Production entry point for the warm-path marker lookup: resolves the
/// real soldr root + `RUSTUP_HOME` and delegates to
/// [`load_prepared_marker_from`] with the current TTL policy and wall
/// clock. Returns `None` on any resolution failure so callers always
/// fall through to the full cold path rather than erroring.
fn load_prepared_marker(version: &str) -> Option<DylintToolchainPlan> {
    let base_dir = SoldrPaths::new().ok()?.root;
    let rustup_home = crate::core::resolve_rustup_home()?;
    load_prepared_marker_from(
        &base_dir,
        &rustup_home,
        version,
        prepare_ttl(),
        SystemTime::now(),
    )
}

/// Testable core of the warm-path marker lookup. A marker "hits" only
/// when all of the following hold:
///
/// * `ttl` is non-zero (zero means "never trust the marker"),
/// * the marker file exists and its mtime is within `ttl` of `now`,
/// * its single line parses into a shape-valid identity
///   (`validate_identity`'s channel / release / commit checks), and
/// * the toolchain directory the identity names is still present under
///   `rustup_home/toolchains/` (a cheap directory-existence check —
///   deliberately no subprocess spawn).
fn load_prepared_marker_from(
    base_dir: &Path,
    rustup_home: &Path,
    version: &str,
    ttl: Duration,
    now: SystemTime,
) -> Option<DylintToolchainPlan> {
    if ttl.is_zero() {
        return None;
    }
    let path = prepared_marker_path(base_dir, version);
    let metadata = std::fs::metadata(&path).ok()?;
    let modified = metadata.modified().ok()?;
    let age = now.duration_since(modified).ok()?;
    if age > ttl {
        return None;
    }
    let contents = std::fs::read_to_string(&path).ok()?;
    let plan = parse_marker_identity(contents.lines().next()?)?;
    if !is_toolchain_installed_at(rustup_home, &plan.channel) {
        return None;
    }
    Some(plan)
}

/// Parse a single `<channel>|<rustc_release>|<commit_hash>` marker line
/// (the same shape [`DylintToolchainPlan::cache_identity`] produces)
/// back into a plan, reusing [`validate_identity`]'s shape checks so a
/// corrupted or foreign-format marker is rejected rather than trusted.
fn parse_marker_identity(line: &str) -> Option<DylintToolchainPlan> {
    let mut parts = line.splitn(3, '|');
    let channel = parts.next()?.to_string();
    let compiler_release = parts.next()?.to_string();
    let compiler_commit = parts.next()?.to_string();
    if parts.next().is_some() || channel.is_empty() {
        return None;
    }
    let identity = NightlyIdentity {
        rust_version: major_minor(&compiler_release).unwrap_or_default(),
        rustc_release: compiler_release.clone(),
        rustc_commit_hash: compiler_commit.clone(),
    };
    validate_identity(&channel, &identity).ok()?;
    Some(DylintToolchainPlan {
        channel,
        compiler_release,
        compiler_commit,
    })
}

/// Cheap on-disk sanity check standing in for a `rustup component list`
/// subprocess: does `rustup_home/toolchains/` contain a directory for
/// `channel`? Tries the exact `<channel>-<host triple>` name first (the
/// standard rustup toolchain directory naming); falls back to accepting
/// any directory whose name starts with `<channel>-` if the host triple
/// cannot be determined, so an unusual host doesn't spuriously miss a
/// perfectly good warm cache.
fn is_toolchain_installed_at(rustup_home: &Path, channel: &str) -> bool {
    let toolchains_dir = rustup_home.join("toolchains");
    if toolchains_dir.join(channel).is_dir() {
        return true;
    }
    if let Ok(triple) = TargetTriple::host() {
        if toolchains_dir
            .join(format!("{channel}-{}", triple.triple()))
            .is_dir()
        {
            return true;
        }
    }
    let Ok(entries) = std::fs::read_dir(&toolchains_dir) else {
        return false;
    };
    let prefix = format!("{channel}-");
    entries.filter_map(Result::ok).any(|entry| {
        entry.file_name().to_string_lossy().starts_with(&prefix) && entry.path().is_dir()
    })
}

/// Production entry point for persisting the warm-path marker after a
/// successful cold-path prepare. Best-effort by contract — callers must
/// not fail the run on a write error, only log it.
fn write_prepared_marker(version: &str, plan: &DylintToolchainPlan) -> Result<(), SoldrError> {
    let base_dir = SoldrPaths::new()?.root;
    write_prepared_marker_at(&base_dir, version, plan)
}

fn write_prepared_marker_at(
    base_dir: &Path,
    version: &str,
    plan: &DylintToolchainPlan,
) -> Result<(), SoldrError> {
    let path = prepared_marker_path(base_dir, version);
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    std::fs::write(path, format!("{}\n", plan.cache_identity()))?;
    Ok(())
}

fn plan_from_configured_environment() -> Result<Option<DylintToolchainPlan>, SoldrError> {
    let values = (
        non_empty_env(CONFIGURED_TOOLCHAIN_ENV_VAR),
        non_empty_env(CONFIGURED_COMPILER_RELEASE_ENV_VAR),
        non_empty_env(CONFIGURED_COMPILER_COMMIT_ENV_VAR),
    );
    match values {
        (None, None, None) => Ok(None),
        (Some(channel), Some(compiler_release), Some(compiler_commit)) => {
            let identity = NightlyIdentity {
                rust_version: major_minor(&compiler_release).unwrap_or_default(),
                rustc_release: compiler_release.clone(),
                rustc_commit_hash: compiler_commit.clone(),
            };
            validate_identity(&channel, &identity)?;
            Ok(Some(DylintToolchainPlan {
                channel,
                compiler_release,
                compiler_commit,
            }))
        }
        _ => Err(SoldrError::Other(
            "configured Dylint compiler identity is incomplete".into(),
        )),
    }
}

fn plan_from_retained_environment(channel: &str) -> Result<DylintToolchainPlan, SoldrError> {
    match (
        non_empty_env(COMPILER_RELEASE_ENV_VAR),
        non_empty_env(COMPILER_COMMIT_ENV_VAR),
    ) {
        (Some(compiler_release), Some(compiler_commit)) => Ok(DylintToolchainPlan {
            channel: channel.to_string(),
            compiler_release,
            compiler_commit,
        }),
        _ => {
            let (compiler_release, compiler_commit) = observe_compiler(channel)?;
            Ok(DylintToolchainPlan {
                channel: channel.to_string(),
                compiler_release,
                compiler_commit,
            })
        }
    }
}

fn requested_rust_version(requested_channel: Option<&str>) -> Result<String, SoldrError> {
    if let Some(channel) = requested_channel {
        if let Some(version) = major_minor(channel) {
            return Ok(version);
        }
        let (release, _) = observe_compiler(channel)?;
        return major_minor(&release).ok_or_else(|| {
            SoldrError::Other(format!(
                "could not derive a major.minor Rust version from `{release}`"
            ))
        });
    }
    let binary = crate::resolve_toolchain_binary(concat!("rust", "c"))?;
    let (release, _) = observe_compiler_binary(&binary, "the default Rust toolchain")?;
    major_minor(&release).ok_or_else(|| {
        SoldrError::Other(format!(
            "could not derive a major.minor Rust version from `{release}`"
        ))
    })
}

fn requested_toolchain_channel(
    requested_channel: Option<&str>,
    workspace_root: &Path,
) -> Result<Option<String>, SoldrError> {
    let manifest = crate::core::read_rust_toolchain_manifest(workspace_root)?;
    Ok(requested_channel
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(str::to_string)
        .or_else(|| non_empty_env("RUSTUP_TOOLCHAIN"))
        .or(manifest.channel))
}

fn is_dated_nightly(channel: &str) -> bool {
    let Some(value) = channel.strip_prefix("nightly-") else {
        return false;
    };
    let Some(date) = value.get(..10) else {
        return false;
    };
    let valid_date_shape = date.as_bytes().iter().enumerate().all(|(index, byte)| {
        matches!(index, 4 | 7) && *byte == b'-' || !matches!(index, 4 | 7) && byte.is_ascii_digit()
    });
    let suffix = &value[10..];
    valid_date_shape && (suffix.is_empty() || suffix.starts_with('-') && suffix.len() > 1)
}

fn select_explicit_from_map(
    bytes: &[u8],
    requested_channel: &str,
) -> Result<DylintToolchainPlan, SoldrError> {
    let map: NightlyVersionMap = serde_json::from_slice(bytes)
        .map_err(|error| SoldrError::Other(format!("failed to parse {MAP_ASSET}: {error}")))?;
    if map.schema_version != 1 {
        return Err(SoldrError::Other(format!(
            "{MAP_ASSET} has unsupported schema_version {}",
            map.schema_version
        )));
    }
    let map_channel = requested_channel
        .get(..18)
        .filter(|channel| is_dated_nightly(channel))
        .unwrap_or(requested_channel);
    let identity = map.nightlies.get(map_channel).ok_or_else(|| {
        SoldrError::Other(format!(
            "{MAP_ASSET} has no identity for explicitly configured {requested_channel}"
        ))
    })?;
    validate_identity(map_channel, identity)?;
    Ok(DylintToolchainPlan {
        channel: requested_channel.to_string(),
        compiler_release: identity.rustc_release.clone(),
        compiler_commit: identity.rustc_commit_hash.clone(),
    })
}

fn select_from_map(bytes: &[u8], version: &str) -> Result<DylintToolchainPlan, SoldrError> {
    let map: NightlyVersionMap = serde_json::from_slice(bytes)
        .map_err(|error| SoldrError::Other(format!("failed to parse {MAP_ASSET}: {error}")))?;
    if map.schema_version != 1 {
        return Err(SoldrError::Other(format!(
            "{MAP_ASSET} has unsupported schema_version {}",
            map.schema_version
        )));
    }
    let bucket = map.versions.get(version).ok_or_else(|| {
        SoldrError::Other(format!(
            "{MAP_ASSET} has no nightly mapping for Rust {version}"
        ))
    })?;
    if bucket.nightlies.first() != Some(&bucket.selected) {
        return Err(SoldrError::Other(format!(
            "{MAP_ASSET} violates its newest-first contract for Rust {version}"
        )));
    }
    if !bucket.nightlies.windows(2).all(|pair| pair[0] > pair[1]) {
        return Err(SoldrError::Other(format!(
            "{MAP_ASSET} nightlies for Rust {version} are not descending"
        )));
    }
    let identity = map.nightlies.get(&bucket.selected).ok_or_else(|| {
        SoldrError::Other(format!(
            "{MAP_ASSET} selects missing nightly {}",
            bucket.selected
        ))
    })?;
    if identity.rust_version != version {
        return Err(SoldrError::Other(format!(
            "{} is indexed under Rust {version} but reports Rust {}",
            bucket.selected, identity.rust_version
        )));
    }
    validate_identity(&bucket.selected, identity)?;
    Ok(DylintToolchainPlan {
        channel: bucket.selected.clone(),
        compiler_release: identity.rustc_release.clone(),
        compiler_commit: identity.rustc_commit_hash.clone(),
    })
}

fn validate_identity(channel: &str, identity: &NightlyIdentity) -> Result<(), SoldrError> {
    if !channel.starts_with("nightly-")
        || !identity.rustc_release.ends_with("-nightly")
        || identity.rustc_commit_hash.len() != 40
        || !identity
            .rustc_commit_hash
            .bytes()
            .all(|byte| byte.is_ascii_hexdigit())
    {
        return Err(SoldrError::Other(format!(
            "{MAP_ASSET} contains a malformed identity for {channel}"
        )));
    }
    Ok(())
}

fn ensure_installed(plan: &DylintToolchainPlan) -> Result<(), SoldrError> {
    let installed = installed_components(&plan.channel).unwrap_or_default();
    if resolve_toolchain_binary_for_channel(concat!("rust", "c"), Some(&plan.channel)).is_err() {
        let code = crate::toolchain::rustup_toolchain_install_with_profile(
            &plan.channel,
            Some("minimal"),
        )?;
        if code != 0 {
            return Err(SoldrError::Other(format!(
                "rustup failed to install {} (exit {code})",
                plan.channel
            )));
        }
    }
    for component in REQUIRED_COMPONENTS {
        if !installed.iter().any(|line| line.starts_with(component)) {
            let code = crate::toolchain::rustup_component_add(&plan.channel, component)?;
            if code != 0 {
                return Err(SoldrError::Other(format!(
                    "rustup failed to add {component} to {} (exit {code})",
                    plan.channel
                )));
            }
        }
    }
    Ok(())
}

fn installed_components(channel: &str) -> Result<Vec<String>, SoldrError> {
    let mut command = std::process::Command::new(rustup_binary());
    command.args(["component", "list", "--toolchain", channel, "--installed"]);
    apply_implicit_toolchain_homes(&mut command);
    suppress_windows_console_window(&mut command);
    let output = command_output_with_timeout(&mut command, "rustup component list for Dylint")?;
    if !output.status.success() {
        return Ok(Vec::new());
    }
    Ok(String::from_utf8_lossy(&output.stdout)
        .lines()
        .map(str::trim)
        .filter(|line| !line.is_empty())
        .map(str::to_string)
        .collect())
}

fn verify_installed_identity(plan: &DylintToolchainPlan) -> Result<(), SoldrError> {
    let observed = observe_compiler(&plan.channel)?;
    if observed.0 != plan.compiler_release || observed.1 != plan.compiler_commit {
        return Err(SoldrError::Other(format!(
            "installed {} differs from catalogue: expected {} {}, got {} {}",
            plan.channel, plan.compiler_release, plan.compiler_commit, observed.0, observed.1
        )));
    }
    Ok(())
}

fn observe_compiler(channel: &str) -> Result<(String, String), SoldrError> {
    let binary = resolve_toolchain_binary_for_channel(concat!("rust", "c"), Some(channel))?;
    observe_compiler_binary(&binary, channel)
}

fn observe_compiler_binary(
    binary: &Path,
    description: &str,
) -> Result<(String, String), SoldrError> {
    let mut command = std::process::Command::new(binary);
    command.arg("-vV");
    apply_implicit_toolchain_homes(&mut command);
    suppress_windows_console_window(&mut command);
    let output = command_output_with_timeout(&mut command, "Dylint compiler identity")?;
    if !output.status.success() {
        return Err(SoldrError::Other(format!(
            "{description} identity probe failed with {}",
            output.status
        )));
    }
    parse_compiler_verbose(&String::from_utf8_lossy(&output.stdout))
}

fn observe_compiler_host(binary: &Path, description: &str) -> Result<String, SoldrError> {
    let mut command = std::process::Command::new(binary);
    command.arg("-vV");
    apply_implicit_toolchain_homes(&mut command);
    suppress_windows_console_window(&mut command);
    let output = command_output_with_timeout(&mut command, "Dylint compiler host identity")?;
    if !output.status.success() {
        return Err(SoldrError::Other(format!(
            "{description} host identity probe failed with {}",
            output.status
        )));
    }
    parse_compiler_host(&String::from_utf8_lossy(&output.stdout))
}

fn parse_compiler_verbose(output: &str) -> Result<(String, String), SoldrError> {
    let mut release = None;
    let mut commit = None;
    for line in output.lines() {
        if let Some(value) = line.strip_prefix("release:") {
            release = Some(value.trim().to_string());
        } else if let Some(value) = line.strip_prefix("commit-hash:") {
            commit = Some(value.trim().to_string());
        }
    }
    match (release, commit) {
        (Some(release), Some(commit)) if commit.len() == 40 => Ok((release, commit)),
        _ => Err(SoldrError::Other(
            "compiler identity lacks a release or full commit hash".into(),
        )),
    }
}

fn parse_compiler_host(output: &str) -> Result<String, SoldrError> {
    output
        .lines()
        .find_map(|line| line.strip_prefix("host:").map(str::trim))
        .filter(|host| !host.is_empty())
        .map(str::to_string)
        .ok_or_else(|| SoldrError::Other("compiler identity lacks a host triple".into()))
}

fn major_minor(value: &str) -> Option<String> {
    let value = value
        .strip_prefix(concat!("rust", "c", " "))
        .unwrap_or(value);
    let version = value
        .split_whitespace()
        .next()?
        .trim_end_matches("-nightly");
    let mut parts = version.split('.');
    let major = parts.next()?;
    let minor = parts.next()?;
    (major.bytes().all(|byte| byte.is_ascii_digit())
        && minor.bytes().all(|byte| byte.is_ascii_digit()))
    .then(|| format!("{major}.{minor}"))
}

fn non_empty_env(key: &str) -> Option<String> {
    std::env::var(key)
        .ok()
        .map(|value| value.trim().to_string())
        .filter(|value| !value.is_empty())
}

#[cfg(test)]
#[path = "dylint_toolchain_tests.rs"]
mod tests;
