@* Shared template folder/template tree.

   Wraps TreeView<TemplateTreeNode> with template-folder-specific layout: folder
   nodes carry their child folders + templates, leaves are templates. Used by:
     - Templates page (Single mode, navigation on click)
     - Transport Export wizard (Checkbox mode, bulk selection)

   Optional ExtraTemplateChildren lets callers nest extra leaves (e.g. composition
   slots) under a template without forking the component. *@

@using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates
@using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared

<TreeView @ref="_tree" TItem="TemplateTreeNode"
          Items="_visibleRoots"
          ChildrenSelector="n => n.Children"
          HasChildrenSelector="n => n.Children.Count > 0"
          KeySelector="n => (object)n.Key"
          Selectable="@(SelectionMode == TreeViewSelectionMode.Single)"
          SelectionMode="SelectionMode"
          SelectedKeys="SelectedKeys"
          SelectedKeysChanged="SelectedKeysChanged"
          InitiallyExpanded="@(_initiallyExpanded)"
          StorageKey="@StorageKey">
    <NodeContent Context="node">
        @if (NodeContent != null)
        {
            @NodeContent(node)
        }
        else
        {
            <span class="tv-glyph">
                <i class="bi @(NodeGlyph(node))"></i>
            </span>
            <span class="tv-label @(node.Children.Count > 0 ? "fw-semibold" : "")"
                  title="@node.Name"
                  @onclick="() => InvokeNodeClick(node)">@node.Name</span>
            @if (NodeExtras != null)
            {
                @NodeExtras(node)
            }
        }
    </NodeContent>
    <ContextMenu Context="node">
        @if (ContextMenu != null)
        {
            @ContextMenu(node)
        }
    </ContextMenu>
    <EmptyContent>
        @if (EmptyContent != null)
        {
            @EmptyContent
        }
        else
        {
            <span class="text-muted fst-italic">No templates.</span>
        }
    </EmptyContent>
</TreeView>

@code {
    [Parameter] public IReadOnlyList<TemplateFolder> Folders { get; set; } = Array.Empty<TemplateFolder>();
    [Parameter] public IReadOnlyList<Template> Templates { get; set; } = Array.Empty<Template>();
    [Parameter] public TreeViewSelectionMode SelectionMode { get; set; } = TreeViewSelectionMode.Single;
    [Parameter] public HashSet<object>? SelectedKeys { get; set; }
    [Parameter] public EventCallback<HashSet<object>> SelectedKeysChanged { get; set; }
    [Parameter] public string Filter { get; set; } = "";
    [Parameter] public RenderFragment<TemplateTreeNode>? NodeExtras { get; set; }
    [Parameter] public RenderFragment<TemplateTreeNode>? NodeContent { get; set; }
    [Parameter] public RenderFragment<TemplateTreeNode>? ContextMenu { get; set; }
    [Parameter] public RenderFragment? EmptyContent { get; set; }
    [Parameter] public EventCallback<TemplateTreeNode> OnNodeClick { get; set; }
    [Parameter] public string? StorageKey { get; set; }

    /// <summary>
    /// Optional: caller-supplied extra leaves to nest under a template. Used by
    /// the Templates page to surface composition slots; left null by the
    /// Transport Export wizard (compositions aren't exportable as standalone
    /// items, so the wizard's checkbox tree intentionally hides them).
    /// </summary>
    [Parameter] public Func<Template, IReadOnlyList<TemplateTreeNode>>? ExtraTemplateChildren { get; set; }

    private TreeView<TemplateTreeNode>? _tree;
    private List<TemplateTreeNode> _allRoots = new();
    private List<TemplateTreeNode> _visibleRoots = new();
    private HashSet<string>? _filterRevealed;
    private Func<TemplateTreeNode, bool>? _initiallyExpanded;

    protected override void OnParametersSet()
    {
        BuildTree();
        ApplyFilter();
    }

    private void BuildTree()
    {
        var folderNodes = Folders.ToDictionary(
            f => f.Id,
            f => new TemplateTreeNode
            {
                Kind = TemplateTreeNodeKind.Folder,
                Id = f.Id,
                Name = f.Name,
            });

        var roots = new List<TemplateTreeNode>();
        foreach (var f in Folders.OrderBy(f => f.Name, StringComparer.OrdinalIgnoreCase))
        {
            var node = folderNodes[f.Id];
            if (f.ParentFolderId is int pid && folderNodes.TryGetValue(pid, out var parent))
                parent.Children.Add(node);
            else
                roots.Add(node);
        }

        foreach (var t in Templates
            .Where(t => !t.IsDerived)
            .OrderBy(t => t.Name, StringComparer.OrdinalIgnoreCase))
        {
            var tNode = new TemplateTreeNode
            {
                Kind = TemplateTreeNodeKind.Template,
                Id = t.Id,
                Name = t.Name,
            };

            if (ExtraTemplateChildren != null)
            {
                foreach (var extra in ExtraTemplateChildren(t))
                {
                    tNode.Children.Add(extra);
                }
            }

            if (t.FolderId is int fid && folderNodes.TryGetValue(fid, out var parentFolder))
                parentFolder.Children.Add(tNode);
            else
                roots.Add(tNode);
        }

        SortChildren(roots);
        foreach (var node in folderNodes.Values)
            SortChildren(node.Children);

        _allRoots = roots;
    }

    private static void SortChildren(List<TemplateTreeNode> children)
    {
        children.Sort((a, b) =>
        {
            var kindOrder = (int)a.Kind - (int)b.Kind;
            if (kindOrder != 0) return kindOrder;
            return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
        });
    }

    private void ApplyFilter()
    {
        if (string.IsNullOrWhiteSpace(Filter))
        {
            _visibleRoots = _allRoots;
            _filterRevealed = null;
            _initiallyExpanded = null;
            return;
        }

        var needle = Filter.Trim();
        var revealed = new HashSet<string>(StringComparer.Ordinal);
        var filtered = new List<TemplateTreeNode>();
        foreach (var root in _allRoots)
        {
            if (CopyMatching(root, needle, revealed) is { } copy)
                filtered.Add(copy);
        }

        _visibleRoots = filtered;
        _filterRevealed = revealed;
        // Force every ancestor of a match to be expanded so the matched leaf is
        // visible without the user clicking through.
        _initiallyExpanded = n => revealed.Contains(n.Key);
    }

    private static TemplateTreeNode? CopyMatching(TemplateTreeNode node, string needle, HashSet<string> revealed)
    {
        var selfMatch = node.Name.Contains(needle, StringComparison.OrdinalIgnoreCase);
        var keptChildren = new List<TemplateTreeNode>();
        foreach (var child in node.Children)
        {
            var copy = CopyMatching(child, needle, revealed);
            if (copy != null) keptChildren.Add(copy);
        }

        if (!selfMatch && keptChildren.Count == 0) return null;

        var clone = new TemplateTreeNode
        {
            Kind = node.Kind,
            Id = node.Id,
            Name = node.Name,
        };
        foreach (var k in keptChildren) clone.Children.Add(k);
        if (keptChildren.Count > 0)
        {
            // Mark this node as an ancestor on the match path so it auto-expands.
            revealed.Add(clone.Key);
        }
        return clone;
    }

    private static string NodeGlyph(TemplateTreeNode node) =>
        node.Kind == TemplateTreeNodeKind.Folder ? "bi-folder" : "bi-file-earmark-text";

    private async Task InvokeNodeClick(TemplateTreeNode node)
    {
        if (OnNodeClick.HasDelegate)
        {
            await OnNodeClick.InvokeAsync(node);
        }
    }

    /// <summary>Forwarded to the inner TreeView so callers can drive expand/collapse.</summary>
    public void ExpandAll() => _tree?.ExpandAll();

    /// <summary>Forwarded to the inner TreeView so callers can drive expand/collapse.</summary>
    public void CollapseAll() => _tree?.CollapseAll();
}
