use crate::{deps::parse_deps, parse_image_config, Image, ImageConfig};
use kdl::{KdlDocument, KdlError};
use toposort::{Toposort};

use thiserror::Error;

pub type BuildOrder = Vec<Vec<String>>;

#[derive(Error, Debug)]
pub enum UnisonConfigError {
    #[error("Invalid Unison configuration: {0}")]
    InvalidConfiguration(String),
    #[error("Configuration deserialization failed: {0}")]
    Knuffel(#[from] knuffel::Error),
}

#[derive(Debug)]
pub struct UnisonConfig {
    image_config: ImageConfig,
    build_order: Vec<Vec<String>>,
}

impl UnisonConfig {
    pub fn images(&self) -> &Vec<Image> {
        &self.image_config.images.images
    }

    pub fn find_image(&self, name: &str) -> Option<&Image> {
        self.image_config
            .images
            .images
            .iter()
            .find(|img| img.name == name)
    }

    pub fn build_order(&self) -> BuildOrder {
        self.build_order.clone()
    }
}

pub fn parse(filename: &str, input: &str) -> Result<UnisonConfig, UnisonConfigError> {
    let doc: KdlDocument = input
        .parse()
        .map_err(|e: KdlError| UnisonConfigError::InvalidConfiguration(e.to_string()))?;

    let images = doc
        .get("images")
        .ok_or(UnisonConfigError::InvalidConfiguration(
            "missing `images` configuration".to_string(),
        ))?;

    let dependencies = doc.get("dependencies");
    let image_config = parse_image_config(filename, &images.to_string())?;
    let build_order = dependencies
        .map(|deps| {
            let dag = parse_deps(deps);
            dag.toposort().unwrap_or_default()
        })
        .unwrap_or_else(|| {
            let image_names = image_config.image_names();
            vec![image_names]
        });

    Ok(UnisonConfig {
        image_config,
        build_order,
    })
}
