use std::collections::BTreeSet;
use std::fmt::{Display, Formatter, Result as FmtResult};

/// Generated TOML example
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TomlExample(String);

impl TomlExample {
    #[doc(hidden)]
    pub fn new(value: String) -> Self {
        Self(value)
    }

    /// TOML text
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl Display for TomlExample {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.write_str(self.as_str())
    }
}

#[doc(hidden)]
#[derive(Default)]
pub struct TomlExampleBuilder {
    lines: Vec<String>,
    sections: BTreeSet<String>,
}

impl TomlExampleBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn add_value(&mut self, path: &str, value: Option<&str>) {
        let (section, key) = split_path(path);
        self.add_section(section);
        self.lines
            .push(format!("{} = {}", key, example_value(value)));
    }

    pub fn finish(mut self) -> TomlExample {
        if !self.lines.is_empty() {
            self.lines.push(String::new());
        }
        TomlExample::new(self.lines.join("\n"))
    }

    fn add_section(&mut self, section: Option<&str>) {
        let Some(section) = section else {
            return;
        };
        if !self.sections.insert(section.into()) {
            return;
        }
        if !self.lines.is_empty() {
            self.lines.push(String::new());
        }
        self.lines.push(format!("[{section}]"));
    }
}

fn split_path(path: &str) -> (Option<&str>, &str) {
    match path.rsplit_once('.') {
        Some((section, key)) => (Some(section), key),
        None => (None, path),
    }
}

fn example_value(value: Option<&str>) -> String {
    match value {
        Some(value) if is_raw_toml(value) => value.into(),
        Some(value) => format!("{value:?}"),
        None => "\"<required>\"".into(),
    }
}

fn is_raw_toml(value: &str) -> bool {
    matches!(value, "true" | "false")
        || value.parse::<i64>().is_ok()
        || value.parse::<f64>().is_ok()
}
