using Bunit;
using Microsoft.AspNetCore.Components;
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;

namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests;

/// <summary>
/// bUnit tests for TreeView's Checkbox-selection mode (T19 of the Transport
/// feature). Verifies that:
///   - a checkbox renders next to every node in Checkbox mode,
///   - clicking a folder checkbox cascades selection to every descendant leaf,
///   - clicking a leaf produces a partial parent state (Indeterminate),
///   - Single-mode behaviour is preserved (regression).
/// </summary>
public class TreeViewMultiSelectTests : BunitContext
{
    private record TestNode(string Key, string Label, List<TestNode> Children);

    private static List<TestNode> TwoFoldersThreeLeaves() => new()
    {
        // Folder1 → Leaf1a, Leaf1b
        new("f1", "Folder1", new()
        {
            new("l1a", "Leaf1a", new()),
            new("l1b", "Leaf1b", new()),
        }),
        // Folder2 → Leaf2a
        new("f2", "Folder2", new()
        {
            new("l2a", "Leaf2a", new()),
        }),
    };

    private IRenderedComponent<TreeView<TestNode>> RenderCheckboxTree(
        List<TestNode>? items = null,
        HashSet<object>? selectedKeys = null,
        Action<HashSet<object>>? onSelectedKeysChanged = null)
    {
        // Indeterminate is set via JS interop after each render. We stub it so
        // bUnit's strict mode doesn't blow up on the unmocked call.
        JSInterop.SetupVoid("treeviewStorage.setIndeterminate", _ => true);

        return Render<TreeView<TestNode>>(parameters =>
        {
            parameters
                .Add(p => p.Items, items ?? TwoFoldersThreeLeaves())
                .Add(p => p.ChildrenSelector, n => n.Children)
                .Add(p => p.HasChildrenSelector, n => n.Children.Count > 0)
                .Add(p => p.KeySelector, n => n.Key)
                .Add(p => p.NodeContent, node => builder =>
                {
                    builder.AddMarkupContent(0, $"<span class=\"node-label\">{node.Label}</span>");
                })
                .Add(p => p.SelectionMode, TreeViewSelectionMode.Checkbox)
                .Add(p => p.InitiallyExpanded, _ => true)
                .Add(p => p.SelectedKeys, selectedKeys);

            if (onSelectedKeysChanged != null)
            {
                parameters.Add(p => p.SelectedKeysChanged, onSelectedKeysChanged);
            }
        });
    }

    [Fact]
    public void Checkbox_mode_renders_input_checkbox_per_node()
    {
        var cut = RenderCheckboxTree();

        // 2 folders + 3 leaves = 5 nodes, expanded → 5 checkboxes.
        var checkboxes = cut.FindAll("input.tv-checkbox");
        Assert.Equal(5, checkboxes.Count);
    }

    [Fact]
    public void Clicking_folder_checkbox_selects_all_descendant_leaves()
    {
        HashSet<object>? captured = null;
        var cut = RenderCheckboxTree(
            selectedKeys: new HashSet<object>(),
            onSelectedKeysChanged: keys => captured = keys);

        // First checkbox is Folder1 (root #1). It has leaves l1a + l1b.
        var folderCheckbox = cut.FindAll("input.tv-checkbox")[0];
        folderCheckbox.Change(true);

        Assert.NotNull(captured);
        Assert.Equal(2, captured!.Count);
        Assert.Contains((object)"l1a", captured);
        Assert.Contains((object)"l1b", captured);
    }

    [Fact]
    public void Clicking_leaf_makes_parent_indeterminate_when_sibling_unchecked()
    {
        // Pre-select one of Folder1's two leaves → Folder1 should compute as
        // Indeterminate on the next render (carries the `tv-checkbox-indeterminate`
        // CSS marker class — the JS-set `indeterminate` DOM property is set via
        // interop and isn't observable through bUnit's rendered HTML).
        var cut = RenderCheckboxTree(
            selectedKeys: new HashSet<object> { "l1a" });

        // Folder1 checkbox is the first .tv-checkbox; should carry the partial
        // marker class.
        var folderCheckbox = cut.FindAll("input.tv-checkbox")[0];
        var classAttr = folderCheckbox.GetAttribute("class") ?? string.Empty;
        Assert.Contains("tv-checkbox-indeterminate", classAttr);

        // …and not the fully-checked attribute.
        Assert.NotEqual("true", folderCheckbox.GetAttribute("checked"));
    }

    [Fact]
    public void Single_mode_unchanged()
    {
        // Default SelectionMode is Single. No SelectedKeysChanged should fire,
        // and SelectedKeyChanged (singular) should fire on content click.
        object? selected = null;
        HashSet<object>? bulk = null;

        var cut = Render<TreeView<TestNode>>(parameters =>
        {
            parameters
                .Add(p => p.Items, TwoFoldersThreeLeaves())
                .Add(p => p.ChildrenSelector, n => n.Children)
                .Add(p => p.HasChildrenSelector, n => n.Children.Count > 0)
                .Add(p => p.KeySelector, n => n.Key)
                .Add(p => p.NodeContent, node => builder =>
                {
                    builder.AddMarkupContent(0, $"<span class=\"node-label\">{node.Label}</span>");
                })
                .Add(p => p.InitiallyExpanded, _ => true)
                .Add(p => p.Selectable, true)
                .Add(p => p.SelectedKeyChanged, (Action<object?>)(k => selected = k))
                .Add(p => p.SelectedKeysChanged, (Action<HashSet<object>>)(s => bulk = s));
        });

        // No checkboxes rendered in Single mode.
        Assert.Empty(cut.FindAll("input.tv-checkbox"));

        // Clicking the content fires SelectedKeyChanged (singular) and NOT
        // SelectedKeysChanged (plural).
        cut.Find(".tv-content").Click();
        Assert.Equal("f1", selected);
        Assert.Null(bulk);
    }
}
