//! Engine dispatch plus the file I/O around it.
//!
//! Looks an engine up by id, finds the file it owns, reads the current bytes,
//! reconciles, and (for `init`) writes the expected bytes unless a hard-failure
//! finding forbids it. All reads and writes route through [`crate::fs`]; this
//! module owns no `std::fs` calls of its own.

use std::path::Path;

use aqc_file_engine_core::Finding;
use shakrs_core::EngineRequirementOutput;

use crate::runtime::fs;
use crate::types::{BrokerError, ReconciledFile, Registry};

impl core::fmt::Display for BrokerError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::UnknownEngine(id) => {
                write!(f, "requirements routed to unregistered engine `{id}`")
            }
            Self::Read { path, detail } => write!(f, "could not read {path}: {detail}"),
        }
    }
}

/// Reconcile one engine's requirements against the file it owns.
///
/// Reads the current bytes (absent file -> `None`), then calls the engine's
/// `reconcile`. Pure beyond the single read.
///
/// # Errors
///
/// Returns [`BrokerError::UnknownEngine`] if `engine_id` is not registered, or
/// [`BrokerError::Read`] if the file read fails.
pub(crate) fn reconcile_engine(
    registry: &Registry,
    workspace_root: &Path,
    engine_id: &str,
    reqs: &EngineRequirementOutput,
) -> Result<ReconciledFile, BrokerError> {
    let Some(engine) = registry.engines.get(engine_id) else {
        return Err(BrokerError::UnknownEngine(engine_id.to_owned()));
    };
    let path = engine.target_path(workspace_root);
    let current = match fs::read(&path) {
        Ok(bytes) => bytes,
        Err(err) => {
            return Err(BrokerError::Read {
                path: path.display().to_string(),
                detail: err.to_string(),
            });
        }
    };
    let output = engine.reconcile(current.as_deref(), reqs);
    let expected_bytes = if current.as_deref() == Some(output.expected_bytes.as_slice()) {
        Vec::new()
    } else {
        output.expected_bytes
    };
    Ok(ReconciledFile {
        path,
        expected_bytes,
        findings: output.findings,
    })
}

/// Write a reconciled file's expected bytes, unless a hard-failure finding
/// forbids it. `init` calls this; `validate` never does.
///
/// True for findings `init` must not write over: everything except `Mismatch`.
pub(crate) const fn is_hard_failure(finding: &Finding) -> bool {
    matches!(
        finding,
        Finding::ParseError { .. }
            | Finding::InvalidRequirements { .. }
            | Finding::UnwritableRequiredKey { .. }
            | Finding::ConflictingRequirements { .. }
            | Finding::InternalError { .. }
    )
}
