Editor/HumanoidMocap/RetargetWindow.AdjustmentsWindow.cs
using System.Collections.Generic;
using System.Linq;
using Editor;
using Sandbox;

namespace HumanoidMocapX.EditorTools;

public sealed partial class RetargetWindow
{
    sealed record AdjustmentPage(string Category,string Icon,string Title,Widget Page,bool Listed);
    readonly List<AdjustmentPage> _adjustmentPages=new();
    readonly List<AdjustmentNode> _adjustmentNodes=new();
    TreeView _adjustmentTree;
    const float AdjustmentSidebarWidth=200;

    /// <summary>Adds a page to Mocap adjustments under <paramref name="category"/>. Unlisted pages are
    /// built (their controls stay in use) but get no sidebar entry.</summary>
    Widget AddAdjustmentPage(string category,string icon,string title,bool listed=true)
    {
        var page=_advancedPanel.Layout.Add(new Widget(this));page.Layout=Layout.Column();page.Layout.Spacing=8;
        _adjustmentPages.Add(new(category,icon,title,page,listed));
        return page;
    }

    static string CategoryIcon(string category)=>category switch{"Processing"=>"settings","Capture"=>"movie_filter","Character"=>"accessibility_new",_=>"auto_fix_high"};

    /// <summary>Adds a host page to Mocap adjustments and returns its body (a column with the
    /// page title above it). Call before the window is first opened; pages added later never show.</summary>
    public Widget AddSettingsPage(string category,string icon,string title)
    {
        var card=AddAdjustmentPage(category,icon,title).Layout.Add(new MocapCard(this){Flat=true});
        card.Header(icon,title);
        return card;
    }

    /// <summary>The host's setup gate. While <paramref name="reason"/> is set, the drop area is grayed
    /// out and shows it, and the settings button carries a red dot; null lifts the gate.</summary>
    public void SetSetupRequired(string reason)
    {
        if(_welcome is VideoDropArea drop)drop.SetLocked(reason);
        _settingsButton.Badge=reason is not null;
        _settingsButton.ToolTip=reason is null?"Mocap adjustments":"Mocap adjustments: finish setup first";
    }

    /// <summary>Mocap adjustments: a category tree on the left and the selected pages on the right,
    /// like the editor's Project Settings, in the workspace's gray rather than black.</summary>
    internal void ShowAdjustments()=>ShowAdjustments(null);

    /// <summary>Opens Mocap adjustments, or brings it forward, on the page titled
    /// <paramref name="title"/>; null or an unknown title keeps the current or first page.</summary>
    public void ShowAdjustments(string title)
    {
        if(_adjustments.IsValid()){SelectAdjustmentPage(title);_adjustments.Show();_adjustments.Window.Raise();return;}
        _adjustments=new Dialog(this);_adjustments.Window.Title="Mocap adjustments";_adjustments.Window.SetWindowIcon("videocam");
        _adjustments.OnPaintOverride=()=>{Paint.ClearPen();Paint.SetBrush(Theme.WidgetBackground);Paint.DrawRect(_adjustments.LocalRect);return true;};
        _adjustments.Layout=Layout.Row();
        var tree=_adjustmentTree=_adjustments.Layout.Add(new TreeView(_adjustments){FixedWidth=AdjustmentSidebarWidth});
        tree.Margin=tree.Margin with{Top=4,Bottom=4};
        tree.OnPaintOverride=()=>
        {
            Paint.ClearPen();Paint.SetBrush(Color.Lerp(Theme.WidgetBackground,Color.Black,.12f));Paint.DrawRect(tree.LocalRect);
            Paint.SetPen(MocapButton.Edge);Paint.DrawLine(tree.LocalRect.TopRight,tree.LocalRect.BottomRight);
            return false;
        };
        var scroll=_adjustments.Layout.Add(new ScrollArea(_adjustments),1);
        _advancedPanel.Parent=scroll;scroll.Canvas=_advancedPanel;_advancedPanel.Visible=true;
        // Right margin keeps the content clear of the vertical scrollbar, as MappingEditor does.
        _advancedPanel.Layout.Margin=new Sandbox.UI.Margin(12,10,16,10);_advancedPanel.Layout.Spacing=8;
        // Pages start at the top instead of floating in the middle of the window.
        _advancedPanel.Layout.AddStretchCell();
        AdjustmentNode first=null;
        foreach(var group in _adjustmentPages.Where(page=>page.Listed).GroupBy(page=>page.Category))
        {
            var category=new AdjustmentNode(group.Key,CategoryIcon(group.Key),group.Select(page=>page.Page).ToArray(),true);
            tree.AddItem(category);_adjustmentNodes.Add(category);
            foreach(var page in group)
            {
                var node=new AdjustmentNode(page.Title,page.Icon,new[]{page.Page},false);
                first??=node;category.AddItem(node);_adjustmentNodes.Add(node);
            }
            tree.Open(category);
        }
        foreach(var page in _adjustmentPages)page.Page.Visible=false;
        // The tree reports a row by its Value, which is the row's page list.
        tree.ItemSelected+=item=>{if(item is Widget[] pages)ShowAdjustmentPages(pages);};
        if(first is not null){tree.SelectItem(first);ShowAdjustmentPages(first.Pages);}
        SelectAdjustmentPage(title);
        // Preserve the settings widgets when the user closes this separate window.
        _adjustments.Window.DeleteOnClose=false;
        _adjustments.Window.MinimumSize=new Vector2(860,480);_adjustments.Window.Size=new Vector2(940,600);_adjustments.Show();
    }

    void SelectAdjustmentPage(string title)
    {
        if(title is null||_adjustmentNodes.FirstOrDefault(node=>node.Name==title) is not { } node)return;
        _adjustmentTree.SelectItem(node);ShowAdjustmentPages(node.Pages);
    }

    /// <summary>Shows the given pages, in their built order, and hides the rest.</summary>
    void ShowAdjustmentPages(Widget[] shown){foreach(var page in _adjustmentPages)page.Page.Visible=shown.Contains(page.Page);}

    /// <summary>A sidebar row: a bold category or an indented page, with a lighter fill on hover and
    /// the icon buttons' green when selected.</summary>
    sealed class AdjustmentNode : TreeNode
    {
        const float IconSize=16,Inset=6,Gap=8;
        readonly string icon;
        readonly bool isCategory;
        internal readonly Widget[] Pages;
        public AdjustmentNode(string title,string icon,Widget[] pages,bool isCategory)
        {Name=title;this.icon=icon;Pages=pages;this.isCategory=isCategory;Height=isCategory?26:24;
            // The tree tracks rows by Value; each row's own page list keeps them distinct.
            Value=pages;}
        public override void OnPaint(VirtualWidget item)
        {
            Paint.Antialiasing=true;
            var row=item.Rect.Shrink(2,1,6,1);
            if(item.Selected||item.Hovered)
            {
                Paint.ClearPen();Paint.SetBrush(item.Selected?Theme.Green.WithAlpha(.15f):MocapButton.Fill.Lighten(.3f));
                Paint.DrawRect(row,4);
            }
            Paint.SetPen(item.Selected?Theme.Green:isCategory?Theme.Text:Theme.TextLight);
            var iconRect=Paint.DrawIcon(new Rect(row.Left+Inset,row.Top,IconSize,row.Height),icon,IconSize,TextFlag.LeftCenter);
            var text=row;text.Left=iconRect.Right+Gap;
            Paint.SetPen(Theme.Text);
            if(isCategory)Paint.SetDefaultFont(weight:600);else Paint.SetDefaultFont();
            Paint.DrawText(text,Name,TextFlag.LeftCenter);
        }
    }
}