// crates/atomcode-tuix/src/highlight/theme.rs
//
// Theme-aware SGR sequences for markdown inline elements (headings,
// inline code, bold/italic, muted chrome). Two variants — `dark` and
// `light` — selected at startup from `Config::ui.theme` via
// `set_theme_mode()`. Constants that don't change between themes
// (RESET, bold/italic attribute toggles, muted SGR 90) stay as plain
// `pub const &str`.
//
// History: this module used to also expose per-token colour accessors
// for the syntect-driven code-block highlighter (`keyword()`, `string()`,
// `function()`, etc.). Those were removed when per-token colouring in
// code blocks was dropped — macOS Terminal.app's semi-transparent grey
// selection overlay made truecolor tokens unreadable inside selections.
// See `highlight::highlight_block`'s doc for the full rationale. Git
// history retains the removed accessors and their palettes.

use std::sync::atomic::{AtomicU8, Ordering};

const MODE_DARK: u8 = 0;
const MODE_LIGHT: u8 = 1;

/// Runtime theme selector. Updated once at startup by the TUIX entry
/// point after reading `Config::ui.theme`; readers see eventual
/// consistency via `Relaxed` ordering.
static MODE: AtomicU8 = AtomicU8::new(MODE_DARK);

/// Switch the palette. Idempotent. Call once during startup before the
/// first markdown / highlight emission.
pub fn set_theme_mode(light: bool) {
    MODE.store(if light { MODE_LIGHT } else { MODE_DARK }, Ordering::Relaxed);
}

#[inline]
fn is_light() -> bool {
    MODE.load(Ordering::Relaxed) == MODE_LIGHT
}

/// `render/theme.rs` reads this when picking the session-name pill SGR
/// (and any other render-side choice that depends on the active theme).
#[inline]
pub fn is_light_for_render() -> bool {
    is_light()
}

/// Full SGR clear. Use after every wrapped token span.
///
/// Historically this was `"\x1b[23;39m"` (italic off + default fg only)
/// to minimise emitted bytes. That under-cleared: any upstream UI chrome
/// that left `reverse` (SGR 7), `bold` (SGR 1), or `faint` (SGR 2) on
/// the working style — e.g. the ApprovalPrompt Y chip or the top-rule
/// session pill — leaked across token boundaries inside
/// `parse_markdown_to_cells`, baking those bits onto syntect-coloured
/// cells. On Terminal.app this rendered as solid coloured blocks at
/// Number/String token positions (fg-as-bg with default-fg glyph); iTerm2
/// happened to mask the bug with more lenient SGR state handling.
///
/// `"\x1b[0m"` is the full ECMA-48 reset (4 bytes, no perceptible perf
/// impact) and is the only safe close when we don't know what attributes
/// the surrounding stream left enabled.
pub const RESET: &str = "\x1b[0m";

// ── Markdown inline element colours ──────────────────────────────────

/// Heading H1-H3.
/// `dark`: bold + bright cyan (SGR 1;96, matches `Palette::ACCENT`).
/// `light`: bold + bright blue (SGR 1;94) — bright cyan renders too pale
/// on white in most light-theme terminal profiles; blue still maps to a
/// dark, readable variant on light profiles.
pub fn md_heading_open() -> &'static str {
    if is_light() { "\x1b[1;34m" } else { "\x1b[1;96m" }
}

/// Close heading: bold off + fg default (SGR 22;39). Theme-invariant.
pub const MD_HEADING_CLOSE: &str = "\x1b[22;39m";

/// Inline code.
/// `dark`: bold + bright cyan (matches headings).
/// `light`: bold + standard magenta (SGR 1;35) — distinct from headings,
/// terminal profiles map 35 to a dark magenta that's readable on white.
pub fn md_inline_code_open() -> &'static str {
    if is_light() { "\x1b[1;35m" } else { "\x1b[1;96m" }
}

/// Close inline code: bold off + fg default. Theme-invariant.
pub const MD_INLINE_CODE_CLOSE: &str = "\x1b[22;39m";

/// Bold text: SGR 1 (bold on). Theme-invariant — bold is an attribute,
/// not a colour.
pub const MD_BOLD_OPEN: &str = "\x1b[1m";
pub const MD_BOLD_CLOSE: &str = "\x1b[22m";

/// Italic text: SGR 3 (italic on). Theme-invariant.
pub const MD_ITALIC_OPEN: &str = "\x1b[3m";
pub const MD_ITALIC_CLOSE: &str = "\x1b[23m";

/// Muted / structural chrome (list markers, table borders): bright
/// black / dark grey (SGR 90). The terminal's profile maps this to a
/// shade with adequate contrast on either background — keep as constant.
pub const MD_MUTED_OPEN: &str = "\x1b[90m";
pub const MD_MUTED_CLOSE: &str = "\x1b[39m";

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

    // Guard around theme-switching tests so they don't race each other
    // (the static `MODE` is per-process). Each test takes the lock,
    // switches, asserts, switches back.
    static THEME_LOCK: Mutex<()> = Mutex::new(());

    fn with_dark<F: FnOnce()>(f: F) {
        let _g = THEME_LOCK.lock().unwrap();
        set_theme_mode(false);
        f();
        set_theme_mode(false); // restore default
    }

    fn with_light<F: FnOnce()>(f: F) {
        let _g = THEME_LOCK.lock().unwrap();
        set_theme_mode(true);
        f();
        set_theme_mode(false); // restore default
    }

    #[test]
    fn reset_is_full_sgr_clear() {
        // Must be a full SGR reset (`\x1b[0m`), not the historical
        // partial close (`\x1b[23;39m`) which only cleared italic + fg
        // and let reverse / bold / faint leak across token boundaries
        // inside `parse_markdown_to_cells`. See the doc on `RESET` for
        // the full bug class this guards against.
        assert_eq!(RESET, "\x1b[0m");
    }

    #[test]
    fn dark_md_heading_is_bold_bright_cyan() {
        with_dark(|| assert_eq!(md_heading_open(), "\x1b[1;96m"));
    }

    #[test]
    fn light_md_heading_is_bold_blue() {
        with_light(|| assert_eq!(md_heading_open(), "\x1b[1;34m"));
    }

    #[test]
    fn dark_md_inline_code_is_bold_bright_cyan() {
        with_dark(|| assert_eq!(md_inline_code_open(), "\x1b[1;96m"));
    }

    #[test]
    fn light_md_inline_code_is_bold_magenta() {
        with_light(|| assert_eq!(md_inline_code_open(), "\x1b[1;35m"));
    }

    #[test]
    fn close_codes_are_theme_invariant() {
        // Close codes only manipulate SGR attributes (bold off / italic
        // off / fg default), never set a colour — should be identical
        // regardless of theme.
        assert_eq!(MD_HEADING_CLOSE, "\x1b[22;39m");
        assert_eq!(MD_INLINE_CODE_CLOSE, "\x1b[22;39m");
        assert_eq!(MD_BOLD_CLOSE, "\x1b[22m");
        assert_eq!(MD_ITALIC_CLOSE, "\x1b[23m");
        assert_eq!(MD_MUTED_CLOSE, "\x1b[39m");
    }
}