//! The top-level sparse settings layer.
//!
//! This struct models a single settings file (`~/.fabro/settings.toml`,
//! `.fabro/project.toml`, or `workflow.toml`) after deserialization. Fields
//! unset in the source stay `None`/empty and are layered later by
//! `fabro-config`.

use std::str::FromStr;

use serde::{Deserialize, Serialize};

use super::cli::CliLayer;
use super::environment::{EnvironmentImageLayer, EnvironmentLayer};
use super::llm::LlmLayer;
use super::maps::MergeMap;
use super::project::ProjectLayer;
use super::run::RunLayer;
use super::server::ServerLayer;
use super::workflow::WorkflowLayer;
use crate::parse::{ParseError, parse_settings};

/// A sparse settings layer before merge/resolve.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)]
pub struct SettingsLayer {
    #[serde(default, rename = "_version", skip_serializing_if = "Option::is_none")]
    pub version:      Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub project:      Option<ProjectLayer>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub workflow:     Option<WorkflowLayer>,
    #[serde(default, skip_serializing_if = "MergeMap::is_empty")]
    pub environments: MergeMap<EnvironmentLayer>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub run:          Option<RunLayer>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub cli:          Option<CliLayer>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub server:       Option<ServerLayer>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub llm:          Option<LlmLayer>,
}

impl FromStr for SettingsLayer {
    type Err = ParseError;

    fn from_str(source: &str) -> Result<Self, Self::Err> {
        parse_settings(source)
    }
}

impl From<CliLayer> for SettingsLayer {
    fn from(cli: CliLayer) -> Self {
        Self {
            cli: Some(cli),
            ..Self::default()
        }
    }
}

impl From<LlmLayer> for SettingsLayer {
    fn from(llm: LlmLayer) -> Self {
        Self {
            llm: Some(llm),
            ..Self::default()
        }
    }
}

impl From<ProjectLayer> for SettingsLayer {
    fn from(project: ProjectLayer) -> Self {
        Self {
            project: Some(project),
            ..Self::default()
        }
    }
}

impl From<RunLayer> for SettingsLayer {
    fn from(run: RunLayer) -> Self {
        Self {
            run: Some(run),
            ..Self::default()
        }
    }
}

impl From<ServerLayer> for SettingsLayer {
    fn from(server: ServerLayer) -> Self {
        Self {
            server: Some(server),
            ..Self::default()
        }
    }
}

impl From<WorkflowLayer> for SettingsLayer {
    fn from(workflow: WorkflowLayer) -> Self {
        Self {
            workflow: Some(workflow),
            ..Self::default()
        }
    }
}

impl SettingsLayer {
    /// Every environment image a settings layer can carry: the image of each
    /// named `[environments.*]` entry plus the `[run.environment]` image.
    ///
    /// This is the single definition of "where images live in a settings
    /// layer". The dockerfile walkers (run compilation, manifest bundling,
    /// workflow-version validation) all iterate through here so a new
    /// image-bearing location only needs to be added once.
    pub fn environment_images(&self) -> impl Iterator<Item = &EnvironmentImageLayer> {
        self.environments
            .values()
            .filter_map(|environment| environment.image.as_ref())
            .chain(
                self.run
                    .as_ref()
                    .and_then(|run| run.environment.as_ref())
                    .and_then(|environment| environment.image.as_ref()),
            )
    }

    /// Mutable variant of [`Self::environment_images`].
    pub fn environment_images_mut(&mut self) -> impl Iterator<Item = &mut EnvironmentImageLayer> {
        self.environments
            .values_mut()
            .filter_map(|environment| environment.image.as_mut())
            .chain(
                self.run
                    .as_mut()
                    .and_then(|run| run.environment.as_mut())
                    .and_then(|environment| environment.image.as_mut()),
            )
    }
}

#[cfg(test)]
impl SettingsLayer {
    /// A default layer that resolves cleanly: populates `server.auth.methods`
    /// with `["dev-token"]`. Use anywhere a test needs a starter
    /// `SettingsLayer` that the strict resolver will accept.
    #[must_use]
    pub(crate) fn test_default() -> Self {
        let mut layer = Self::default();
        layer.ensure_test_auth_methods();
        layer
    }

    /// If `server.auth.methods` is unset, populate it with `["dev-token"]`.
    /// Existing methods (set by a fixture) are preserved. Use to make a
    /// parsed-from-TOML layer resolve cleanly without overriding test intent.
    pub(crate) fn ensure_test_auth_methods(&mut self) {
        use fabro_types::settings::ServerAuthMethod;

        use super::server::{ServerAuthLayer, ServerLayer as ServerLayerTy};

        if self
            .server
            .as_ref()
            .and_then(|server| server.auth.as_ref())
            .and_then(|auth| auth.methods.as_ref())
            .is_some()
        {
            return;
        }
        let server = self.server.get_or_insert_with(ServerLayerTy::default);
        let auth = server.auth.get_or_insert_with(ServerAuthLayer::default);
        auth.methods = Some(vec![ServerAuthMethod::DevToken]);
    }
}
