//! Tests extracted from style.rs for readability.
//! Wired in via `#[path = ...] mod tests;`.

use super::*;

// Display tests
#[test]
fn test_display_not_bold() {
    let style = Style::new(
        None,
        None,
        Some(false),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )
    .unwrap();
    assert_eq!(style.to_string(), "not bold");
}

#[test]
fn test_display_not_bold_with_color() {
    let style = Style::new(
        Some("red"),
        None,
        Some(false),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )
    .unwrap();
    assert_eq!(style.to_string(), "not bold red");
}

#[test]
fn test_display_null() {
    let style = Style::null();
    assert_eq!(style.to_string(), "none");
}

#[test]
fn test_display_bold() {
    let style = Style::new(
        None,
        None,
        Some(true),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )
    .unwrap();
    assert_eq!(style.to_string(), "bold");
}

#[test]
fn test_display_bold_red_on_black() {
    let style = Style::new(
        Some("red"),
        Some("black"),
        Some(true),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )
    .unwrap();
    assert_eq!(style.to_string(), "bold red on black");
}

#[test]
fn test_display_link() {
    let style = Style::new(
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        Some("foo"),
    )
    .unwrap();
    assert_eq!(style.to_string(), "link foo");
}

#[test]
fn test_display_all_attributes() {
    let style = Style::new(
        Some("red"),
        Some("black"),
        Some(true),
        Some(true),
        Some(true),
        Some(true),
        Some(true),
        Some(true),
        Some(true),
        Some(true),
        Some(true),
        Some(true),
        Some(true),
        Some(true),
        Some(true),
        None,
    )
    .unwrap();
    let s = style.to_string();
    assert!(s.contains("bold"));
    assert!(s.contains("dim"));
    assert!(s.contains("italic"));
    assert!(s.contains("underline"));
    assert!(s.contains("blink"));
    assert!(s.contains("blink2"));
    assert!(s.contains("reverse"));
    assert!(s.contains("conceal"));
    assert!(s.contains("strike"));
    assert!(s.contains("underline2"));
    assert!(s.contains("frame"));
    assert!(s.contains("encircle"));
    assert!(s.contains("overline"));
    assert!(s.contains("red"));
    assert!(s.contains("on black"));
}

// Equality tests
#[test]
fn test_equality_same() {
    let style1 = Style::new(
        Some("red"),
        None,
        Some(true),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )
    .unwrap();
    let style2 = Style::new(
        Some("red"),
        None,
        Some(true),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )
    .unwrap();
    assert_eq!(style1, style2);
}

#[test]
fn test_equality_different_color() {
    let style1 = Style::new(
        Some("red"),
        None,
        Some(true),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )
    .unwrap();
    let style2 = Style::new(
        Some("green"),
        None,
        Some(true),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )
    .unwrap();
    assert_ne!(style1, style2);
}

// is_null tests
#[test]
fn test_is_null_true() {
    let style = Style::null();
    assert!(style.is_null());
}

#[test]
fn test_is_null_false_with_bold() {
    let style = Style::new(
        None,
        None,
        Some(true),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )
    .unwrap();
    assert!(!style.is_null());
}

#[test]
fn test_is_null_false_with_color() {
    let style = Style::new(
        Some("red"),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )
    .unwrap();
    assert!(!style.is_null());
}

// Parse tests
#[test]
fn test_parse_empty() {
    let style = Style::parse("");
    assert!(style.is_null());
}

#[test]
fn test_parse_red() {
    let style = Style::parse("red");
    assert_eq!(style.color().unwrap().name(), "red");
}

#[test]
fn test_parse_not_bold() {
    let style = Style::parse("not bold");
    assert_eq!(style.bold(), Some(false));
}

#[test]
fn test_parse_bold_red_on_black() {
    let style = Style::parse("bold red on black");
    assert_eq!(style.bold(), Some(true));
    assert_eq!(style.color().unwrap().name(), "red");
    assert_eq!(style.bgcolor().unwrap().name(), "black");
}

#[test]
fn test_parse_bold_link() {
    let style = Style::parse("bold link https://example.org");
    assert_eq!(style.bold(), Some(true));
    assert_eq!(style.link(), Some("https://example.org"));
}

#[test]
fn test_parse_error_on_alone() {
    let result = Style::parse_strict("on");
    assert!(result.is_err());
}

#[test]
fn test_parse_error_on_invalid_color() {
    let result = Style::parse_strict("on nothing");
    assert!(result.is_err());
}

#[test]
fn test_parse_error_rgb_out_of_range() {
    let result = Style::parse_strict("rgb(999,999,999)");
    assert!(result.is_err());
}

#[test]
fn test_parse_error_not_unknown_attribute() {
    let result = Style::parse_strict("not monkey");
    assert!(result.is_err());
}

#[test]
fn test_parse_error_link_alone() {
    let result = Style::parse_strict("link");
    assert!(result.is_err());
}

// Render tests
#[test]
fn test_render_no_color_system() {
    let style = Style::parse("red");
    assert_eq!(style.render("foo", None, false), "foo");
}

#[test]
fn test_render_empty_text() {
    let style = Style::parse("red");
    assert_eq!(style.render("", Some(ColorSystem::TrueColor), false), "");
}

#[test]
fn test_render_null_style() {
    let style = Style::null();
    assert_eq!(
        style.render("foo", Some(ColorSystem::TrueColor), false),
        "foo"
    );
}

#[test]
fn test_render_bold_red_on_black() {
    let style = Style::parse("bold red on black");
    let rendered = style.render("foo", Some(ColorSystem::TrueColor), false);
    assert!(rendered.contains("\x1b[1;31;40m"));
    assert!(rendered.contains("foo"));
    assert!(rendered.contains("\x1b[0m"));
}

#[test]
fn test_render_all_attributes() {
    let style = Style::parse("bold dim italic underline blink blink2 reverse conceal strike underline2 frame encircle overline red on black");
    let rendered = style.render("foo", Some(ColorSystem::TrueColor), false);
    assert!(rendered.contains("1;2;3;4;5;6;7;8;9;21;51;52;53;31;40"));
}

// Add tests
#[test]
fn test_add_with_none() {
    let style = Style::parse("red");
    let result = style.clone() + None;
    assert_eq!(result, style);
}

#[test]
fn test_add_styles() {
    let style1 = Style::parse("red");
    let style2 = Style::parse("bold");
    let result = style1 + style2;
    assert_eq!(result.color().unwrap().name(), "red");
    assert_eq!(result.bold(), Some(true));
}

#[test]
fn test_add_override_color() {
    let style1 = Style::parse("red");
    let style2 = Style::parse("blue");
    let result = style1 + style2;
    assert_eq!(result.color().unwrap().name(), "blue");
}

// StyleStack tests
#[test]
fn test_style_stack_new() {
    let stack = StyleStack::new(Style::parse("red"));
    assert_eq!(stack.current().color().unwrap().name(), "red");
}

#[test]
fn test_style_stack_push() {
    let mut stack = StyleStack::new(Style::parse("red"));
    stack.push(Style::parse("bold"));
    assert_eq!(stack.current().color().unwrap().name(), "red");
    assert_eq!(stack.current().bold(), Some(true));
}

#[test]
fn test_style_stack_pop() {
    let mut stack = StyleStack::new(Style::parse("red"));
    stack.push(Style::parse("bold"));
    stack.pop().unwrap();
    assert_eq!(stack.current().color().unwrap().name(), "red");
    assert_eq!(stack.current().bold(), None);
}

#[test]
fn test_style_stack_pop_error() {
    let mut stack = StyleStack::new(Style::null());
    let result = stack.pop();
    assert!(result.is_err());
}

// HTML style tests
#[test]
fn test_get_html_style_complex() {
    let style = Style::parse("reverse dim red on blue bold italic underline strike overline");
    let html = style.get_html_style(None);
    // With reverse: blue becomes fg, red becomes bg
    // With dim: blend blue (0,0,128) with red (128,0,0) at 50% = (64,0,64) = #400040
    assert!(html.contains("color: #400040"));
    assert!(html.contains("text-decoration-color: #400040"));
    assert!(html.contains("background-color: #800000"));
    assert!(html.contains("font-weight: bold"));
    assert!(html.contains("font-style: italic"));
    assert!(html.contains("text-decoration: underline line-through overline"));
}

#[test]
fn test_get_html_style_simple() {
    let style = Style::parse("bold red");
    let html = style.get_html_style(None);
    assert!(html.contains("color: #800000"));
    assert!(html.contains("font-weight: bold"));
}

// without_color tests
#[test]
fn test_without_color() {
    let style = Style::parse("bold red on blue");
    let without = style.without_color();
    assert_eq!(without.bold(), Some(true));
    assert!(without.color().is_none());
    assert!(without.bgcolor().is_none());
}

// background_style tests
#[test]
fn test_background_style() {
    let style = Style::parse("bold yellow on red");
    let bg = style.background_style();
    assert!(bg.color().is_none());
    assert_eq!(bg.bgcolor().unwrap().name(), "red");
    assert_eq!(bg.bold(), None);
}

// clear_meta_and_links tests
#[test]
fn test_clear_meta_and_links() {
    let style = Style::parse("bold red link https://example.org");
    let cleared = style.clear_meta_and_links();
    assert_eq!(cleared.bold(), Some(true));
    assert_eq!(cleared.color().unwrap().name(), "red");
    assert!(cleared.link().is_none());
}

// Combine tests
#[test]
fn test_combine_empty() {
    let result = Style::combine(&[]);
    assert!(result.is_null());
}

#[test]
fn test_combine_multiple() {
    let styles = vec![
        Style::parse("red"),
        Style::parse("bold"),
        Style::parse("on blue"),
    ];
    let result = Style::combine(&styles);
    assert_eq!(result.color().unwrap().name(), "red");
    assert_eq!(result.bold(), Some(true));
    assert_eq!(result.bgcolor().unwrap().name(), "blue");
}

// Attribute aliases tests
#[test]
fn test_parse_attribute_alias_b() {
    let style = Style::parse("b");
    assert_eq!(style.bold(), Some(true));
}

#[test]
fn test_parse_attribute_alias_i() {
    let style = Style::parse("i");
    assert_eq!(style.italic(), Some(true));
}

#[test]
fn test_parse_attribute_alias_u() {
    let style = Style::parse("u");
    assert_eq!(style.underline(), Some(true));
}

#[test]
fn test_parse_attribute_alias_s() {
    let style = Style::parse("s");
    assert_eq!(style.strike(), Some(true));
}

#[test]
fn test_parse_attribute_alias_uu() {
    let style = Style::parse("uu");
    assert_eq!(style.underline2(), Some(true));
}

#[test]
fn test_parse_attribute_alias_o() {
    let style = Style::parse("o");
    assert_eq!(style.overline(), Some(true));
}

// Hash test
#[test]
fn test_hash() {
    use std::collections::HashSet;
    let mut set = HashSet::new();
    let style1 = Style::parse("bold red");
    let style2 = Style::parse("bold red");
    let style3 = Style::parse("bold blue");

    set.insert(style1);
    assert!(set.contains(&style2));
    set.insert(style3);
    assert_eq!(set.len(), 2);
}

// from_color tests
#[test]
fn test_from_color() {
    let color = Color::parse("red").unwrap();
    let bgcolor = Color::parse("blue").unwrap();
    let style = Style::from_color(Some(color), Some(bgcolor));
    assert_eq!(style.color().unwrap().name(), "red");
    assert_eq!(style.bgcolor().unwrap().name(), "blue");
    assert!(style.bold().is_none());
}

#[test]
fn test_from_color_none() {
    let style = Style::from_color(None, None);
    assert!(style.color().is_none());
    assert!(style.bgcolor().is_none());
}

// -- with_link builder tests --------------------------------------------

#[test]
fn test_with_link() {
    let style = Style::with_link("https://example.com");
    assert_eq!(style.link(), Some("https://example.com"));
    assert!(style.color().is_none());
    assert!(style.bold().is_none());
}

#[test]
fn test_with_link_is_not_null() {
    let style = Style::with_link("https://example.com");
    assert!(!style.is_null());
}

// -- link=URL parse syntax tests ----------------------------------------

#[test]
fn test_parse_link_equals_syntax() {
    let style = Style::parse("link=https://example.com");
    assert_eq!(style.link(), Some("https://example.com"));
}

#[test]
fn test_parse_bold_link_equals_syntax() {
    let style = Style::parse("bold link=https://example.com");
    assert_eq!(style.bold(), Some(true));
    assert_eq!(style.link(), Some("https://example.com"));
}

#[test]
fn test_parse_link_equals_empty_error() {
    let result = Style::parse_strict("link=");
    assert!(result.is_err());
}

// -- link rendering tests -----------------------------------------------

#[test]
fn test_render_link_only() {
    let style = Style::with_link("https://example.com");
    let rendered = style.render("click", Some(ColorSystem::TrueColor), false);
    // OSC 8 with id= prefix; structural assertions, not exact id.
    assert!(rendered.starts_with("\x1b]8;id="));
    assert!(rendered.contains(";https://example.com\x1b\\click\x1b]8;;\x1b\\"));
}

#[test]
fn test_pick_first_returns_first_non_null() {
    let s1 = Style::parse("bold red");
    let s2 = Style::parse("italic");
    let null = Style::null();
    let picked = Style::pick_first(&[Some(&null), Some(&s1), Some(&s2)]);
    assert_eq!(picked.bold(), Some(true));
    assert_eq!(
        picked.color().map(|c| c.name().into_owned()),
        Some("red".to_string())
    );
}

#[test]
fn test_pick_first_skips_none_and_null() {
    let null = Style::null();
    let s = Style::parse("underline");
    let picked = Style::pick_first(&[None, Some(&null), None, Some(&s)]);
    assert_eq!(picked.underline(), Some(true));
}

#[test]
fn test_pick_first_all_null_returns_null() {
    let null = Style::null();
    let picked = Style::pick_first(&[None, Some(&null), None]);
    assert!(picked.is_null());
}

#[test]
fn test_link_id_is_monotonic() {
    let a = next_link_id();
    let b = next_link_id();
    let c = next_link_id();
    assert!(b > a);
    assert!(c > b);
}

#[test]
fn test_render_bold_with_link() {
    let style = Style::parse("bold link https://example.com");
    let rendered = style.render("click", Some(ColorSystem::TrueColor), false);
    assert!(rendered.starts_with("\x1b]8;id="));
    assert!(rendered.contains(";https://example.com\x1b\\"));
    assert!(rendered.ends_with("\x1b]8;;\x1b\\"));
    assert!(rendered.contains("\x1b[1m"));
    assert!(rendered.contains("click"));
}

#[test]
fn test_render_link_no_color_system() {
    // With no color system, render returns plain text (no link wrapping)
    let style = Style::with_link("https://example.com");
    let rendered = style.render("click", None, false);
    assert_eq!(rendered, "click");
}

// -- link combine tests -------------------------------------------------

#[test]
fn test_add_link_override() {
    let style1 = Style::parse("link https://a.com");
    let style2 = Style::parse("link https://b.com");
    let result = style1 + style2;
    assert_eq!(result.link(), Some("https://b.com"));
}

#[test]
fn test_add_link_preserved() {
    let style1 = Style::parse("link https://a.com");
    let style2 = Style::parse("bold");
    let result = style1 + style2;
    assert_eq!(result.link(), Some("https://a.com"));
    assert_eq!(result.bold(), Some(true));
}

#[test]
fn test_combine_link() {
    let styles = vec![
        Style::parse("red"),
        Style::with_link("https://example.com"),
        Style::parse("bold"),
    ];
    let result = Style::combine(&styles);
    assert_eq!(result.link(), Some("https://example.com"));
    assert_eq!(result.color().unwrap().name(), "red");
    assert_eq!(result.bold(), Some(true));
}

// -- Underline enhancement tests ----------------------------------------

#[test]
fn test_underline_style_setter_getter() {
    let mut style = Style::null();
    assert!(style.underline_style().is_none());
    style.set_underline_style(Some(UnderlineStyle::Curly));
    assert_eq!(style.underline_style(), Some(UnderlineStyle::Curly));
}

#[test]
fn test_underline_color_setter_getter() {
    let mut style = Style::null();
    assert!(style.underline_color().is_none());
    let red = Color::parse("red").unwrap();
    style.set_underline_color(Some(red));
    assert!(style.underline_color().is_some());
    assert_eq!(style.underline_color().unwrap().name(), "red");
}

#[test]
fn test_underline_style_is_not_null() {
    let mut style = Style::null();
    style.set_underline_style(Some(UnderlineStyle::Double));
    assert!(!style.is_null());
}

#[test]
fn test_underline_color_is_not_null() {
    let mut style = Style::null();
    style.set_underline_color(Some(Color::parse("red").unwrap()));
    assert!(!style.is_null());
}

#[test]
fn test_underline_style_display() {
    let mut style = Style::null();
    style.set_underline_style(Some(UnderlineStyle::Curly));
    assert!(style.to_string().contains("curly"));
}

#[test]
fn test_underline_color_display() {
    let mut style = Style::null();
    style.set_underline_color(Some(Color::parse("red").unwrap()));
    assert!(style.to_string().contains("underline_color(red)"));
}

#[test]
fn test_underline_style_add() {
    let mut s1 = Style::null();
    s1.set_underline_style(Some(UnderlineStyle::Curly));
    let mut s2 = Style::null();
    s2.set_underline_style(Some(UnderlineStyle::Dashed));
    let result = s1 + s2;
    assert_eq!(result.underline_style(), Some(UnderlineStyle::Dashed));
}

#[test]
fn test_underline_color_add() {
    let mut s1 = Style::null();
    s1.set_underline_color(Some(Color::parse("red").unwrap()));
    let s2 = Style::parse("bold");
    let result = s1 + s2;
    assert_eq!(result.underline_color().unwrap().name(), "red");
}

#[test]
fn test_underline_style_render_curly() {
    let mut style = Style::null();
    style.set_underline_style(Some(UnderlineStyle::Curly));
    let rendered = style.render("foo", Some(ColorSystem::TrueColor), false);
    assert!(rendered.contains("4:3"));
}

#[test]
fn test_underline_style_render_dashed() {
    let mut style = Style::null();
    style.set_underline_style(Some(UnderlineStyle::Dashed));
    let rendered = style.render("foo", Some(ColorSystem::TrueColor), false);
    assert!(rendered.contains("4:5"));
}

#[test]
fn test_underline_color_render_truecolor() {
    let mut style = Style::null();
    style.set_underline(Some(true));
    style.set_underline_color(Some(Color::from_rgb(255, 0, 0)));
    let rendered = style.render("foo", Some(ColorSystem::TrueColor), false);
    // Should contain 58;2;255;0;0 for underline color
    assert!(rendered.contains("58;2;255;0;0"), "rendered: {}", rendered);
}

#[test]
fn test_without_color_preserves_underline_color() {
    let mut style = Style::parse("bold red on blue");
    style.set_underline_color(Some(Color::parse("green").unwrap()));
    let without = style.without_color();
    assert!(without.color().is_none());
    assert!(without.bgcolor().is_none());
    assert!(without.underline_color().is_some());
    assert_eq!(without.underline_color().unwrap().name(), "green");
}

#[test]
fn test_background_style_clears_underline() {
    let mut style = Style::parse("bold red on blue");
    style.set_underline_color(Some(Color::parse("green").unwrap()));
    style.set_underline_style(Some(UnderlineStyle::Curly));
    let bg = style.background_style();
    assert!(bg.underline_color().is_none());
    assert!(bg.underline_style().is_none());
}

#[test]
fn test_underline_equality() {
    let mut s1 = Style::null();
    s1.set_underline_style(Some(UnderlineStyle::Curly));
    let mut s2 = Style::null();
    s2.set_underline_style(Some(UnderlineStyle::Curly));
    assert_eq!(s1, s2);

    let mut s3 = Style::null();
    s3.set_underline_style(Some(UnderlineStyle::Dashed));
    assert_ne!(s1, s3);
}

// -- Display↔parse round-trip tests for underline styles (Fix 7) --------

#[test]
fn test_underline_style_display_parse_roundtrip() {
    for variant in &[
        UnderlineStyle::Single,
        UnderlineStyle::Double,
        UnderlineStyle::Curly,
        UnderlineStyle::Dotted,
        UnderlineStyle::Dashed,
    ] {
        let mut original = Style::null();
        original.set_underline_style(Some(*variant));
        let display_str = original.to_string();
        let parsed = Style::parse_strict(&display_str).expect("round-trip parse failed");
        assert_eq!(
            parsed.underline_style(),
            Some(*variant),
            "round-trip failed for {:?}: display='{}', parsed={:?}",
            variant,
            display_str,
            parsed.underline_style()
        );
    }
}

#[test]
fn test_underline_color_display_parse_roundtrip() {
    let mut original = Style::null();
    original.set_underline_color(Some(Color::parse("red").unwrap()));
    let display_str = original.to_string();
    let parsed = Style::parse_strict(&display_str).expect("round-trip parse failed");
    assert_eq!(
        parsed.underline_color().map(|c| c.name().into_owned()),
        Some("red".to_string()),
        "underline_color round-trip failed: display='{}'",
        display_str,
    );
}

// -- Style::chain and Style::normalize tests (Fix 8) --------------------

#[test]
fn test_chain_is_combine() {
    let styles = vec![
        Style::parse("red"),
        Style::parse("bold"),
        Style::parse("on blue"),
    ];
    let chained = Style::chain(&styles);
    let combined = Style::combine(&styles);
    assert_eq!(chained, combined);
}

#[test]
fn test_chain_later_overrides_earlier() {
    let styles = vec![Style::parse("red"), Style::parse("blue")];
    let result = Style::chain(&styles);
    assert_eq!(result.color().unwrap().name(), "blue");
}

#[test]
fn test_normalize_null_is_none() {
    let style = Style::null();
    assert_eq!(style.normalize(), "none");
}

#[test]
fn test_normalize_bold_red() {
    let style = Style::parse("bold red");
    let norm = style.normalize();
    // The canonical form is the Display output; re-parsing must reproduce
    // the same style.
    let reparsed = Style::parse_strict(&norm).unwrap();
    assert_eq!(style, reparsed);
}

#[test]
fn test_normalize_roundtrips() {
    let style = Style::parse("italic on black");
    let norm = style.normalize();
    let reparsed = Style::parse_strict(&norm).unwrap();
    assert_eq!(style, reparsed);
}

#[test]
fn test_public_setters() {
    let mut style = Style::null();
    style.set_bold(Some(true));
    style.set_dim(Some(true));
    style.set_italic(Some(true));
    style.set_underline(Some(true));
    style.set_blink(Some(true));
    style.set_reverse(Some(true));
    style.set_conceal(Some(true));
    style.set_strike(Some(true));

    assert_eq!(style.bold(), Some(true));
    assert_eq!(style.dim(), Some(true));
    assert_eq!(style.italic(), Some(true));
    assert_eq!(style.underline(), Some(true));
    assert_eq!(style.blink(), Some(true));
    assert_eq!(style.reverse(), Some(true));
    assert_eq!(style.conceal(), Some(true));
    assert_eq!(style.strike(), Some(true));
}

// ---------------------------------------------------------------------------
// Task 3: typed Style builder methods
// ---------------------------------------------------------------------------

#[test]
fn test_style_fg_sets_foreground() {
    use crate::color::Color;
    let color = Color::from_rgb(1, 2, 3);
    let s = Style::null().fg(color);
    assert!(s.color().is_some());
    assert_eq!(
        s.color().unwrap().get_truecolor(None, true),
        color.get_truecolor(None, true)
    );
}

#[test]
fn test_style_bg_sets_background() {
    use crate::color::Color;
    let color = Color::from_rgb(10, 20, 30);
    let s = Style::null().bg(color);
    assert!(s.bgcolor().is_some());
    assert_eq!(
        s.bgcolor().unwrap().get_truecolor(None, false),
        color.get_truecolor(None, false)
    );
}

#[test]
fn test_style_with_underline_color() {
    use crate::color::Color;
    let color = Color::from_rgb(0, 255, 0);
    let s = Style::null().with_underline_color(color);
    assert!(s.underline_color().is_some());
}

#[test]
fn test_style_fg_bg_chain() {
    use crate::color::Color;
    let s = Style::null()
        .fg(Color::from_rgb(255, 0, 0))
        .bg(Color::from_rgb(0, 0, 255));
    assert!(s.color().is_some());
    assert!(s.bgcolor().is_some());
}

#[test]
fn render_downgrades_color_to_console_system() {
    // A truecolor style rendered for a Standard (16-color) console must emit a
    // 16-color SGR (30-37/90-97), never a truecolor "38;2;r;g;b".
    let st = Style::parse("#ff0000"); // bright red truecolor fg
    let out = st.render("X", Some(ColorSystem::Standard), false);
    assert!(
        !out.contains("38;2;"),
        "must not emit truecolor on Standard: {out:?}"
    );
    assert!(out.contains("\x1b["), "must still emit an SGR: {out:?}");
    // EightBit console -> 256-color form, not truecolor.
    let out8 = st.render("X", Some(ColorSystem::EightBit), false);
    assert!(
        !out8.contains("38;2;"),
        "must not emit truecolor on EightBit: {out8:?}"
    );
    assert!(
        out8.contains("38;5;"),
        "EightBit fg should use 38;5;N: {out8:?}"
    );
}

// -- Phase 7.13 parity tests -----------------------------------------------

/// Item 2: Style::render with legacy_windows=true must omit OSC 8 link wrapper.
#[test]
fn test_render_legacy_windows_suppresses_osc8() {
    let style = Style::parse("link https://example.com");
    let rendered = style.render("click", Some(ColorSystem::TrueColor), true);
    assert!(
        !rendered.contains("\x1b]8;"),
        "legacy_windows=true must not emit OSC 8; got: {rendered:?}"
    );
    assert!(
        rendered.contains("click"),
        "text must still be present; got: {rendered:?}"
    );
}

/// Item 2: Style::render with legacy_windows=false must still emit OSC 8 link wrapper.
#[test]
fn test_render_no_legacy_windows_emits_osc8() {
    let style = Style::parse("link https://example.com");
    let rendered = style.render("click", Some(ColorSystem::TrueColor), false);
    assert!(
        rendered.contains("\x1b]8;"),
        "legacy_windows=false must emit OSC 8; got: {rendered:?}"
    );
}

/// Item 3: normalize_str parses a definition string and returns the canonical form.
#[test]
fn test_normalize_str_canonical() {
    assert_eq!(Style::normalize_str("bold red"), "bold red");
}

#[test]
fn test_normalize_str_trims_and_lowercases() {
    // Style::parse is case-insensitive; normalised output is lowercase.
    assert_eq!(Style::normalize_str("  BOLD  RED  "), "bold red");
}

#[test]
fn test_normalize_str_negated() {
    assert_eq!(Style::normalize_str("bold not italic"), "bold not italic");
}

/// Item 4: parse cache capacity 4096 — verify more than 256 unique entries are cached.
#[test]
fn test_style_cache_holds_more_than_256_entries() {
    // Clear the cache first so the count reflects only what this test adds.
    crate::style::clear_style_cache();

    // Warm up the cache with 300 distinct styles by varying the link URL.
    // Using `link https://example.com/NNN` produces syntactically valid style
    // strings that parse successfully and are stored in the cache.
    for i in 0..300u32 {
        let _ = Style::parse(&format!("link https://example.com/{i}"));
    }
    let size = crate::style::style_cache_size();
    assert!(
        size > 256,
        "cache should hold >256 entries after 4096 capacity bump; got {size}"
    );
}
