//! Compilation tests for code examples in the widget development docs.
//!
//! These tests verify that the API patterns shown in the docs actually
//! compile against the real plushie-core and iced APIs. If the API
//! changes, these tests fail, signaling that the docs need updating.
//!
//! The tests don't render pixels; they exercise the type system and verify
//! that method calls, field access, and trait implementations are correct.

use plushie_core::protocol::Props;
use plushie_core::types::PlushieType;
use plushie_widget_sdk::prelude::*;
use plushie_widget_sdk::testing::*;
use serde_json::json;

// column and row are excluded from the prelude because the function
// forms conflict with the column!/row! macros under glob import.
use plushie_widget_sdk::iced::widget::{column, row};

// Iced primitives for the render-boundary. The prelude re-exports the
// plushie-core domain equivalents (`Color`, `Length`, `Padding`, `Font`,
// `Theme`); the iced versions are needed when constructing iced
// `Element<'_, Message, IcedTheme, R>` types and reading palette colors.
use plushie_widget_sdk::iced::{Color as IcedColor, Theme as IcedTheme};

// ============================================================================
// Gauge example (PlushieWidget)
// ============================================================================

struct DocGauge;

impl PlushieWidget for DocGauge {
    fn type_names(&self) -> &[&str] {
        &["doc_gauge"]
    }

    fn render<'a>(&'a self, node: &'a TreeNode, _ctx: &RenderCtx<'a>) -> PlushieElement<'a> {
        let value = node.prop_f32("value").unwrap_or(0.0);
        let label = node.prop_str("label").unwrap_or_default();

        column![
            text(format!("{label}: {value:.0}%")),
            progress_bar(0.0..=100.0, value),
        ]
        .spacing(4)
        .into()
    }

    fn fresh_for_session(&self) -> Box<dyn PlushieWidget> {
        Box::new(DocGauge)
    }
}

#[test]
fn doc_gauge_renders() {
    let node = node_with_props("g1", "doc_gauge", json!({"value": 50.0, "label": "CPU"}));
    let test = TestEnv::default();
    let ctx = test.render_ctx();
    let gauge = DocGauge;
    let _element: Element<'_, Message, IcedTheme, iced::Renderer> = gauge.render(&node, &ctx);
}

#[test]
fn doc_gauge_no_props() {
    let node = node("g1", "doc_gauge");
    let test = TestEnv::default();
    let ctx = test.render_ctx();
    let gauge = DocGauge;
    let _element: Element<'_, Message, IcedTheme, iced::Renderer> = gauge.render(&node, &ctx);
}

// ============================================================================
// Prop parsing patterns
// ============================================================================

#[test]
fn doc_prop_parsing() {
    let props = Props::from_json(json!({
        "value": 42.5,
        "label": "test",
        "color": "#3498db",
        "show_label": true,
        "width": "fill",
    }));

    // Free function style
    let _value: Option<f32> = prop_f32(&props, "value");
    let _label: Option<String> = prop_str(&props, "label");
    let _color: Option<IcedColor> =
        Color::extract(&props, "color").map(|c| iced_convert::color(&c));
    let _show_label: bool = prop_bool_default(&props, "show_label", true);
    let _width: Option<Length> = Length::extract(&props, "width");

    // TreeNode shorthand style
    let node = node_with_props("n1", "test", props.to_value());
    let _value: Option<f32> = node.prop_f32("value");
    let _label: Option<&str> = node.prop_str("label");
    let _color: Option<IcedColor> =
        Color::extract(&node.props, "color").map(|c| iced_convert::color(&c));
}

// ============================================================================
// Theme access
// ============================================================================

#[test]
fn doc_theme_access() {
    let test = TestEnv::default();
    let ctx = test.render_ctx();
    let theme = ctx.theme;
    let palette = theme.palette();
    let _primary = palette.primary.base.color;
    let _is_dark = palette.is_dark;
}

// ============================================================================
// Rendering children (container widget)
// ============================================================================

struct DocContainer;

impl<R: PlushieRenderer> PlushieWidget<R> for DocContainer {
    fn type_names(&self) -> &[&str] {
        &["doc_container"]
    }

    fn render<'a>(
        &'a self,
        node: &'a TreeNode,
        ctx: &RenderCtx<'a, R>,
    ) -> Element<'a, Message, IcedTheme, R> {
        let header = text(node.prop_str("title").unwrap_or_default());
        let children: Vec<Element<'a, Message, IcedTheme, R>> = ctx.render_children(node);
        let mut col = column![header].spacing(8);
        for child in children {
            col = col.push(child);
        }
        col.into()
    }

    fn fresh_for_session(&self) -> Box<dyn PlushieWidget<R>> {
        Box::new(DocContainer)
    }
}

#[test]
fn doc_container_renders() {
    let mut node = node_with_props_and_children(
        "c1",
        "doc_container",
        json!({"title": "Section"}),
        vec![node("child1", "text")],
    );
    let mut test = TestEnv::default();
    test.registry
        .prepare_walk(&mut node, &mut test.shared_state, &test.theme);
    let ctx = test.render_ctx();
    let widget = DocContainer;
    let _element: Element<'_, Message, IcedTheme, iced::Renderer> = widget.render(&node, &ctx);
}

// ============================================================================
// GenerationCounter pattern
// ============================================================================

#[test]
fn doc_generation_counter() {
    let mut counter = GenerationCounter::new();
    let gen_before = counter.get();
    counter.bump();
    assert_ne!(gen_before, counter.get());
}

// ============================================================================
// CoalesceHint
// ============================================================================

#[test]
fn doc_coalesce_hint() {
    let event =
        OutgoingEvent::widget_event("cursor_pos", "w1", Some(json!({"x": 10.0, "y": 20.0})))
            .with_coalesce(CoalesceHint::Replace);
    assert!(event.coalesce_hint().is_some());
}

// ============================================================================
// Rating widget (PlushieWidget, complete example)
// ============================================================================

struct DocRating;

impl PlushieWidget for DocRating {
    fn type_names(&self) -> &[&str] {
        &["doc_rating"]
    }

    fn render<'a>(&'a self, node: &'a TreeNode, ctx: &RenderCtx<'a>) -> PlushieElement<'a> {
        let value = node.prop_f32("value").unwrap_or(0.0) as usize;
        let max = prop_u32(&node.props, "max").unwrap_or(5) as usize;
        let size = node.prop_f32("size").unwrap_or(24.0);
        let color = Color::extract(&node.props, "color")
            .map(|c| iced_convert::color(&c))
            .unwrap_or(ctx.theme.palette().primary.base.color);
        let disabled_color = IcedColor {
            a: color.a * 0.3,
            ..color
        };

        let id = node.id.clone();
        let mut stars = row![].spacing(2);

        for i in 1..=max {
            let filled = i <= value;
            let star_color = if filled { color } else { disabled_color };
            let label = if filled { "\u{2605}" } else { "\u{2606}" };

            let star_text = text(label).size(size).color(star_color);

            let star_button = button(star_text)
                .on_press(Message::Event {
                    window_id: String::new(),
                    id: id.clone(),
                    family: "select".into(),
                    value: json!({"value": i}),
                })
                .padding(0)
                .style(button::text);

            stars = stars.push(star_button);
        }

        stars.into()
    }

    fn fresh_for_session(&self) -> Box<dyn PlushieWidget> {
        Box::new(DocRating)
    }
}

#[test]
fn doc_rating_renders() {
    let node = node_with_props(
        "r1",
        "doc_rating",
        json!({"value": 3, "max": 5, "size": 32}),
    );
    let test = TestEnv::default();
    let ctx = test.render_ctx();
    let widget = DocRating;
    let _element: Element<'_, Message, IcedTheme, iced::Renderer> = widget.render(&node, &ctx);
}

#[test]
fn doc_rating_no_props() {
    let node = node("r1", "doc_rating");
    let test = TestEnv::default();
    let ctx = test.render_ctx();
    let widget = DocRating;
    let _element: Element<'_, Message, IcedTheme, iced::Renderer> = widget.render(&node, &ctx);
}

// ============================================================================
// fresh_for_session
// ============================================================================

#[test]
fn doc_fresh_for_session() {
    let widget = DocRating;
    let cloned: Box<dyn PlushieWidget> = widget.fresh_for_session();
    assert_eq!(cloned.type_names(), &["doc_rating"]);
}

// ============================================================================
// #[derive(PlushieWidget)] - boilerplate-free stateless widget
// ============================================================================

#[derive(PlushieWidget)]
#[plushie_widget(type_name = "doc_derive_gauge")]
struct DocDeriveGauge;

impl PlushieWidgetRender for DocDeriveGauge {
    fn render<'a>(&'a self, node: &'a TreeNode, _ctx: &RenderCtx<'a>) -> PlushieElement<'a> {
        let value = node.prop_f32("value").unwrap_or(0.0);
        progress_bar(0.0..=100.0, value).into()
    }
}

#[test]
fn derive_widget_type_names_and_fresh() {
    let widget = DocDeriveGauge;
    assert_eq!(
        <DocDeriveGauge as PlushieWidget<iced::Renderer>>::type_names(&widget),
        &["doc_derive_gauge"]
    );
    let fresh: Box<dyn PlushieWidget> = widget.fresh_for_session();
    assert_eq!(fresh.type_names(), &["doc_derive_gauge"]);
}

// ============================================================================
// #[widget_props(container)] opt-in: builders gain child/children methods
// ============================================================================

#[derive(WidgetProps)]
#[widget(name = "doc_stack")]
#[widget_props(container)]
struct DocStack {
    #[allow(dead_code)]
    /// Direction, for testing purposes.
    direction: String,
}

#[derive(WidgetProps)]
#[widget(name = "doc_leaf")]
struct DocLeaf {
    #[allow(dead_code)]
    /// A label.
    label: String,
}

#[test]
fn widget_props_container_builder_gains_child_methods() {
    // Container builders expose .child() and .children(), leaf
    // builders do not. The test is a compile check; if the opt-in
    // attribute works, these method calls resolve.
    let child_node = plushie_core::protocol::TreeNode {
        id: "inner".into(),
        type_name: "doc_leaf".into(),
        props: plushie_core::protocol::Props::default(),
        children: vec![],
    };
    let builder = DocStack::builder("stack").child(child_node.clone());
    assert_eq!(builder.0.children.len(), 1);
    let builder = DocStack::builder("stack2").children(vec![child_node]);
    assert_eq!(builder.0.children.len(), 1);
    // DocLeaf must NOT have a child method. We don't assert a
    // compile-fail here; the absence of these methods on DocLeafBuilder
    // is verified at compile time by every existing builder test.
    let _leaf = DocLeaf::builder("leaf");
}

#[test]
fn derive_widget_renders() {
    let node = node_with_props("g", "doc_derive_gauge", json!({"value": 42.0}));
    let test = TestEnv::default();
    let ctx = test.render_ctx();
    let widget = DocDeriveGauge;
    let _element: PlushieElement<'_> =
        <DocDeriveGauge as PlushieWidget>::render(&widget, &node, &ctx);
}
