// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Rust code generator for compiled structures.
//!
//! Following rules are applied for generation.
//!
//! Namespaces:
//!  - Each namespace is generated in each own module
//!  - Camel case identifieds in namespaces are converted to snake case.
//!  - If namespace more than one ids then submodules are generated in according to namespace.
//!

/// Mod name
pub mod mod_name;

/// Documentation generation
pub mod doc;

/// Configuration of generation
pub mod config;

/// Short type name (only name)
pub mod type_name;

/// Full type name (name with fully qualified path)
pub mod full_type_name;

/// Property name for structs
pub mod property_name;

/// Action name for structs
pub mod action_name;

/// Mod definition
pub mod mod_def;

/// Struct definition
pub mod struct_def;

/// Types definitions
pub mod type_def;

/// Enum definitions
pub mod enum_def;

use crate::compiler::Compiled;
use crate::compiler::IsCreatable;
use crate::compiler::QualifiedName;
use proc_macro2::TokenStream;
use quote::quote;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;

#[doc(inline)]
pub use action_name::ActionFullTypeName;
#[doc(inline)]
pub use action_name::ActionName;
#[doc(inline)]
pub use config::Config;
#[doc(inline)]
pub use enum_def::EnumDef;
#[doc(inline)]
pub use full_type_name::FullTypeName;
#[doc(inline)]
pub use mod_def::ModDef;
#[doc(inline)]
pub use mod_name::ModName;
#[doc(inline)]
pub use property_name::StructFieldName;
#[doc(inline)]
pub use struct_def::StructDef;
#[doc(inline)]
pub use type_def::TypeDef;
#[doc(inline)]
pub use type_name::TypeName;

/// Errors that can occur during code generation.
pub enum Error<'a> {
    BaseTypeConflict,
    NameConflict,
    CreateStruct(TypeName<'a>, Box<Error<'a>>),
    CreateSimplType(QualifiedName<'a>, Box<Error<'a>>),
}

impl Display for Error<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        match self {
            Self::BaseTypeConflict => write!(
                f,
                "derivation property name conflict, see `base_type_prop_name` configuration parameter"
            ),
            Self::NameConflict => write!(f, "name conflict"),
            Self::CreateStruct(id, err) => write!(f, "while create struct {id}\n{err}"),
            Self::CreateSimplType(id, err) => write!(f, "while create simple type {id}\n{err}"),
        }
    }
}

pub struct RustGenerator<'a> {
    root: ModDef<'a>,
    config: Config,
}

impl<'a> RustGenerator<'a> {
    /// # Errors
    ///
    /// Returns error if Rust code cannot be generated by the compiled
    /// data structure.
    pub fn new(compiled: Compiled<'a>, config: Config) -> Result<Self, Error<'a>> {
        let root = ModDef::default();
        let mut cactions = compiled.actions;
        let creatable = compiled.creatable_entity_types;
        let mut excerpt_copies = compiled.excerpt_copies;
        let root = cactions.iter().try_fold(root, |m, (_, ma)| {
            ma.iter()
                .try_fold(m, |m, (_, a)| m.add_action_type(a, &config))
        })?;
        let root = compiled
            .complex_types
            .into_iter()
            .try_fold(root, |m, (name, t)| {
                let actions = cactions.remove(&name).unwrap_or_default();
                m.add_complex_type(t, actions, &config)
            })?;
        let root = compiled
            .entity_types
            .into_iter()
            .try_fold(root, |m, (_, t)| {
                let is_creatable = IsCreatable::new(creatable.contains(&t.name));
                let type_excerpt_copies = excerpt_copies
                    .remove(&t.name)
                    .map(|v| v.into_iter().collect::<Vec<_>>())
                    .unwrap_or_default();
                m.add_entity_type(t, is_creatable, type_excerpt_copies, &config)
            })?;
        let root = compiled
            .type_definitions
            .into_iter()
            .try_fold(root, |m, (_, t)| m.add_type_definition(t))?;
        let root = compiled
            .enum_types
            .into_iter()
            .try_fold(root, |m, (_, t)| m.add_enum_type(t))?;
        Ok(Self { root, config })
    }

    /// Generate Rust code from the collected data.
    #[must_use]
    pub fn generate(self) -> TokenStream {
        let mut tokens = TokenStream::new();
        tokens.extend(quote! {
            /// This code is automatically generated by CSDL generator. DO NOT EDIT!
            const _CSDL_GENERATED_CODE_COMMENT: &str = "";

            #[allow(unused_imports)]
            use nv_redfish_core::{
                EntityTypeRef,
                Expandable,
                Updatable,
                Deletable,
                Creatable,
                RedfishSettings,
                Bmc,
                Empty,
                ActionError,
                Reference,
                NavProperty,
                Action,
                ODataId,
                ODataETag,
                ReferenceLeaf,
                AdditionalProperties,
                DynamicProperties,
                ToSnakeCase,
                de_optional_nullable,
                de_required_nullable,
            };

            pub mod edm {
                //! This module is hardcoded in compiler. If you want
                //! to change it you need to change source code of
                //! generator.
                //!
                /// Mapping of `Edm.Boolean` type
                pub type Boolean = bool;
                /// Mapping of `Edm.DateTimeOffset` type
                pub type DateTimeOffset = nv_redfish_core::EdmDateTimeOffset;
                /// Mapping of `Edm.Decimal`
                pub type Decimal = f64;
                /// Mapping of `Edm.Duration` type
                pub type Duration = nv_redfish_core::EdmDuration;
                /// Mapping of `Guid` type
                pub type Guid = nv_redfish_core::EdmGuid;
                /// Mapping of `Edm.Int64` type
                pub type Int64 = i64;
                /// Mapping of `Edm.String` type
                pub type String = ::std::string::String;
                /// Mapping of `Edm.PrimitiveType` type
                pub type PrimitiveType = nv_redfish_core::EdmPrimitiveType;
            }
        });
        self.root.generate(&mut tokens, &self.config);
        tokens
    }
}
