use std::collections::BTreeSet;

use accesskit::NodeId;
use bone_ui::a11y::{root_node_id, widget_node_id};
use bone_ui::gallery::{GALLERY_LABEL, GalleryState, render_with};
use bone_ui::{AccessTreeBuilder, FocusManager, StringTable, Theme, WidgetId};

fn collect_reachable(update: &accesskit::TreeUpdate) -> BTreeSet<NodeId> {
    let nodes_by_id: std::collections::BTreeMap<NodeId, &accesskit::Node> =
        update.nodes.iter().map(|(id, node)| (*id, node)).collect();
    let Some(tree) = update.tree.as_ref() else {
        return BTreeSet::new();
    };
    let mut seen = BTreeSet::new();
    let mut stack = vec![tree.root];
    while let Some(node_id) = stack.pop() {
        if !seen.insert(node_id) {
            continue;
        }
        if let Some(node) = nodes_by_id.get(&node_id) {
            stack.extend(node.children().iter().copied());
        }
    }
    seen
}

#[test]
fn every_a11y_entry_is_reachable_from_root() {
    let mut focus = FocusManager::new();
    let mut a11y = AccessTreeBuilder::new();
    let mut state = GalleryState::new();
    let _ = render_with(
        std::sync::Arc::new(Theme::light()),
        &mut state,
        &mut focus,
        &mut a11y,
        StringTable::empty(),
    );

    let strings = StringTable::empty();
    let update = a11y.build(strings, focus.focused());
    let reachable = collect_reachable(&update);

    assert!(
        reachable.contains(&root_node_id()),
        "root node must be reachable from itself",
    );
    let entries: Vec<WidgetId> = a11y.ids().collect();
    assert!(
        !entries.is_empty(),
        "gallery must declare at least one a11y entry",
    );
    entries.iter().for_each(|id| {
        let node_id = widget_node_id(*id);
        assert!(
            reachable.contains(&node_id),
            "a11y entry {id:?} must be reachable in the accesskit tree",
        );
    });
}

#[test]
fn every_focusable_widget_appears_with_a_label() {
    let mut focus = FocusManager::new();
    let mut a11y = AccessTreeBuilder::new();
    let mut state = GalleryState::new();
    let _ = render_with(
        std::sync::Arc::new(Theme::light()),
        &mut state,
        &mut focus,
        &mut a11y,
        StringTable::empty(),
    );

    let strings = StringTable::from_entries([(GALLERY_LABEL, "Gallery".to_owned())]);
    let update = a11y.build(&strings, focus.focused());
    let nodes_by_id: std::collections::BTreeMap<NodeId, &accesskit::Node> =
        update.nodes.iter().map(|(id, node)| (*id, node)).collect();

    let focusables: Vec<WidgetId> = focus.focusable_ids().collect();
    assert!(
        !focusables.is_empty(),
        "gallery declares at least one focusable",
    );
    focusables.iter().for_each(|id| {
        let node_id = widget_node_id(*id);
        let Some(node) = nodes_by_id.get(&node_id) else {
            panic!("focusable {id:?} missing in tree update");
        };
        assert!(
            node.label().is_some(),
            "focusable {id:?} must publish a label",
        );
    });
}
