// Copyright © 2025 The µcad authors <info@ucad.xyz>
// SPDX-License-Identifier: AGPL-3.0-or-later

//! Model

use rustc_hash::FxHashMap;

use crate::{model::*, render::*, src_ref::*, syntax::*};

/// Render state of the model.
pub enum ModelRenderState {
    /// The model has not been rendered.
    /// Model output is [`None`].
    Pending,
    /// The model has been pre-rendered via [`Model::pre-render`]
    /// Model output is [`Some`], but there is no geometry.
    Preparing,
    /// Rendering is completed.
    Complete,
}

/// The actual model contents
#[derive(custom_debug::Debug, Default)]
pub struct ModelInner {
    /// Optional id.
    ///
    /// The id is set when the model was created by an assignment: `a = Cube(50mm)`.
    pub id: Option<Identifier>,
    /// Parent object.
    #[debug(skip)]
    pub parent: Option<Model>,
    /// Children of the model.
    pub children: Models,
    /// All models that have been created by an assignment.
    pub assignments: FxHashMap<Identifier, Model>,
    /// Element of the model with [SrcRef].
    pub element: Refer<Element>,
    /// Attributes used for export.
    pub attributes: Attributes,
    /// The output type of the this model.
    pub output: Option<RenderOutput>,
}

impl ModelInner {
    /// Create a new [`ModelInner`] with a specific element.
    pub fn new(element: Element, src_ref: SrcRef) -> Self {
        Self {
            element: Refer::new(element, src_ref),
            ..Default::default()
        }
    }

    /// Return render state of the model.
    pub fn render_state(&self) -> ModelRenderState {
        match &self.output {
            Some(output) => match output.geometry {
                Some(_) => ModelRenderState::Complete,
                None => ModelRenderState::Preparing,
            },
            None => ModelRenderState::Pending,
        }
    }

    /// Clone only the content of this model without children and parent.
    pub fn clone_content(&self) -> Self {
        Self {
            id: self.id.clone(),
            parent: None,
            element: self.element.clone(),
            attributes: self.attributes.clone(),
            assignments: self.assignments.clone(),
            output: self.output.clone(),
            ..Default::default()
        }
    }

    /// Return iterator of children.s
    pub fn children(&self) -> std::slice::Iter<'_, Model> {
        self.children.iter()
    }

    /// Return if ,model has no children.
    pub fn is_empty(&self) -> bool {
        self.children.is_empty()
    }

    /// Read-only access to the element of this model.
    pub fn element(&self) -> &Element {
        &self.element
    }

    /// Read-only access to the attributes of this model.
    pub fn attributes(&self) -> &Attributes {
        &self.attributes
    }

    /// Returns the render output, panics if there is no render output.
    pub fn output(&self) -> &RenderOutput {
        self.output
            .as_ref()
            .expect("Render output. You have to pre-render() and render() the model first.")
    }

    /// Returns the mutable render output, panics if there is no render output.
    pub fn output_mut(&mut self) -> &mut RenderOutput {
        self.output
            .as_mut()
            .expect("Render output. You have to pre-render() and render() the model first.")
    }
}

impl PropertiesAccess for ModelInner {
    fn get_property(&self, id: &Identifier) -> Option<&Value> {
        self.element.get_property(id)
    }

    fn set_property(&mut self, id: Identifier, value: Value) -> Option<Value> {
        self.element.set_property(id, value)
    }

    fn get_properties(&self) -> Option<&Properties> {
        self.element.get_properties()
    }

    fn add_properties(&mut self, props: Properties) {
        self.element.add_properties(props);
    }
}

impl SrcReferrer for ModelInner {
    fn src_ref(&self) -> SrcRef {
        self.element.src_ref.clone()
    }
}
