use bone_kernel::{BrepError, BrepSolid, ExtrudeFeature};
use bone_solver::SolverError;
use bone_types::{FeatureId, GeometryGeneration, RebuildError};

use crate::Sketch;
use crate::profile::build_profile;

#[derive(Clone, Debug, PartialEq)]
pub enum EvaluatedSketch {
    Solved(Sketch),
    Failed(SolverError),
    PlaneUnresolved(RebuildError),
}

#[must_use]
pub fn evaluate_sketch(input: &Sketch) -> EvaluatedSketch {
    match input.solve() {
        Ok(solved) => EvaluatedSketch::Solved(solved),
        Err(error) => EvaluatedSketch::Failed(error),
    }
}

#[derive(Clone, Debug, thiserror::Error)]
pub enum ExtrudeError {
    #[error(transparent)]
    UnsolvedSketch(SolverError),
    #[error(transparent)]
    Kernel(BrepError),
    #[error("{0}")]
    PlaneUnresolved(RebuildError),
}

#[derive(Clone)]
pub struct EvaluatedExtrude {
    result: Result<BrepSolid, ExtrudeError>,
    generation: Option<GeometryGeneration>,
}

impl EvaluatedExtrude {
    pub fn result(&self) -> &Result<BrepSolid, ExtrudeError> {
        &self.result
    }

    #[must_use]
    pub fn solid(&self) -> Option<&BrepSolid> {
        self.result.as_ref().ok()
    }

    #[must_use]
    pub fn generation(&self) -> Option<GeometryGeneration> {
        self.generation
    }
}

impl core::fmt::Debug for EvaluatedExtrude {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let mut state = f.debug_struct("EvaluatedExtrude");
        state.field("generation", &self.generation);
        match &self.result {
            Ok(solid) => state.field("faces", &solid.iter_faces().count()),
            Err(error) => state.field("error", error),
        };
        state.finish()
    }
}

#[must_use]
pub fn evaluate_extrude(
    extrude: FeatureId,
    sketch: &EvaluatedSketch,
    feature: &ExtrudeFeature,
) -> EvaluatedExtrude {
    let result = match sketch {
        EvaluatedSketch::Solved(solved) => build_profile(solved)
            .and_then(|profile| bone_kernel::evaluate_extrude(extrude, &profile, feature))
            .map_err(ExtrudeError::Kernel),
        EvaluatedSketch::Failed(error) => Err(ExtrudeError::UnsolvedSketch(error.clone())),
        EvaluatedSketch::PlaneUnresolved(error) => Err(ExtrudeError::PlaneUnresolved(*error)),
    };
    let generation = result
        .as_ref()
        .ok()
        .map(|solid| GeometryGeneration::from_solid_key(solid.content_key()));
    EvaluatedExtrude { result, generation }
}
