//! Tokens → Tailwind config (TypeScript) + CSS custom-property emission.
//! Emits stable, deterministically-ordered output (BTreeMap input guarantees).

use crate::parse::Tokens;
use std::fmt::Write as _;
use std::fs;
use std::path::Path;

pub fn tailwind_config(t: &Tokens, out: &Path) -> Result<(), String> {
    let mut s = String::new();
    s.push_str("// GENERATED by tokens-sync — do not edit by hand.\n");
    s.push_str("import type { Config } from 'tailwindcss';\n\n");
    s.push_str("const config: Config = {\n");
    s.push_str("  content: [],\n");
    s.push_str("  theme: {\n    extend: {\n");

    emit_category(&mut s, "colors", &t.colors);
    emit_category(&mut s, "fontFamily", &t.fonts);
    emit_category(&mut s, "spacing", &t.spacing);
    emit_category(&mut s, "borderRadius", &t.radius);

    s.push_str("    },\n  },\n  plugins: [],\n};\n\nexport default config;\n");

    fs::write(out, s).map_err(|e| format!("write {}: {e}", out.display()))
}

pub fn css_vars(t: &Tokens, out: &Path) -> Result<(), String> {
    let mut s = String::new();
    s.push_str("/* GENERATED by tokens-sync — do not edit by hand. */\n:root {\n");

    for (k, v) in &t.colors {
        let _ = writeln!(s, "  --color-{}: {};", css_ident(k), v);
    }
    for (k, v) in &t.fonts {
        let _ = writeln!(s, "  --font-{}: {};", css_ident(k), v);
    }
    for (k, v) in &t.spacing {
        let _ = writeln!(s, "  --space-{}: {};", css_ident(k), v);
    }
    for (k, v) in &t.radius {
        let _ = writeln!(s, "  --radius-{}: {};", css_ident(k), v);
    }

    s.push_str("}\n");

    fs::write(out, s).map_err(|e| format!("write {}: {e}", out.display()))
}

fn emit_category(
    s: &mut String,
    key: &str,
    map: &std::collections::BTreeMap<String, String>,
) {
    if map.is_empty() {
        return;
    }
    let _ = writeln!(s, "      {key}: {{");
    for (k, v) in map {
        // Escape JS single quotes inside the value string.
        let escaped = v.replace('\'', "\\'");
        let _ = writeln!(s, "        '{k}': '{escaped}',");
    }
    s.push_str("      },\n");
}

fn css_ident(raw: &str) -> String {
    raw.chars()
        .map(|c| if c.is_ascii_alphanumeric() || c == '-' { c } else { '-' })
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::BTreeMap;

    fn sample_tokens() -> Tokens {
        let mut colors = BTreeMap::new();
        colors.insert("primary".into(), "oklch(0.6 0.2 250)".into());
        colors.insert("surface".into(), "oklch(0.99 0 0)".into());
        let mut fonts = BTreeMap::new();
        fonts.insert("body".into(), "Inter, sans-serif".into());
        let mut spacing = BTreeMap::new();
        spacing.insert("md".into(), "1rem".into());
        let mut radius = BTreeMap::new();
        radius.insert("card".into(), "0.75rem".into());
        Tokens { colors, fonts, spacing, radius }
    }

    #[test]
    fn tailwind_emits_extend_categories() {
        let dir = tempfile::tempdir().unwrap();
        let out = dir.path().join("tw.ts");
        tailwind_config(&sample_tokens(), &out).unwrap();
        let text = fs::read_to_string(&out).unwrap();
        assert!(text.contains("colors: {"));
        assert!(text.contains("'primary': 'oklch(0.6 0.2 250)'"));
        assert!(text.contains("fontFamily: {"));
        assert!(text.contains("borderRadius: {"));
    }

    #[test]
    fn css_emits_vars_under_root() {
        let dir = tempfile::tempdir().unwrap();
        let out = dir.path().join("tokens.css");
        css_vars(&sample_tokens(), &out).unwrap();
        let text = fs::read_to_string(&out).unwrap();
        assert!(text.contains(":root {"));
        assert!(text.contains("--color-primary: oklch(0.6 0.2 250);"));
        assert!(text.contains("--font-body: Inter, sans-serif;"));
        assert!(text.contains("--space-md: 1rem;"));
        assert!(text.contains("--radius-card: 0.75rem;"));
    }

    #[test]
    fn empty_tokens_still_emit_valid_shells() {
        let dir = tempfile::tempdir().unwrap();
        let t = Tokens::default();
        let tw = dir.path().join("tw.ts");
        let css = dir.path().join("c.css");
        tailwind_config(&t, &tw).unwrap();
        css_vars(&t, &css).unwrap();
        let css_text = fs::read_to_string(&css).unwrap();
        assert!(css_text.contains(":root {"));
        assert!(css_text.trim_end().ends_with('}'));
    }
}
