Editor/UI/JointPanel.cs
using Editor;
using Sandbox;
namespace HumanoidRigger.Editor;

/// <summary>Compact selectable rows following the suite's native mapping panels.</summary>
sealed class JointPanel : Widget
{
    readonly Wizard wizard;
    readonly RiggerViewport viewport;
    readonly ScrollArea scroll;
    readonly LineEdit filter;
    readonly List<JointRow> rows=[];
    internal string SelectedRole=>rows.FirstOrDefault(r=>r.Selected)?.Role;
    internal int VisibleRows=>rows.Count(r=>r.Visible);
    internal void SetFilter(string text){filter.Text=text;ApplyFilter(text);}
    public JointPanel(Widget parent,Wizard wizard,RiggerViewport viewport):base(parent)
    {
        this.wizard=wizard;this.viewport=viewport;FixedWidth=250;
        Layout=Layout.Column();Layout.Margin=8;Layout.Spacing=8;
        Layout.Add(new Label("Joint Selection",this));
        filter=Layout.Add(new LineEdit(this){PlaceholderText="Filter joints…"});filter.TextChanged+=ApplyFilter;
        scroll=Layout.Add(new ScrollArea(this),1);
        viewport.SelectionChanged=UpdateSelection;
    }
    public void Reload()
    {
        var roles=(wizard.Rig is null?viewport.EditablePoints().Select(p=>p.Role):wizard.Rig.Bones.Where(b=>b.Deform).Select(b=>b.Role)).ToArray();
        if(rows.Select(r=>r.Role).SequenceEqual(roles)&&scroll.Canvas.IsValid())
        {
            // Native rows outlive hot reloads; refresh their callbacks while retaining selection and scroll.
            foreach(var row in rows)row.MouseLeftPress=()=>viewport.SelectJoint(row.Role);
            RefreshRows();return;
        }
        scroll.Canvas?.Destroy();scroll.Canvas=new Widget(scroll);scroll.Canvas.Layout=Layout.Column();
        scroll.Canvas.Layout.Margin=new Sandbox.UI.Margin(4,4,16,4);scroll.Canvas.Layout.Spacing=4;rows.Clear();
        foreach(string role in roles)
        {
            var row=new JointRow(scroll.Canvas,wizard,role,()=>viewport.SelectJoint(role));
            rows.Add(row);scroll.Canvas.Layout.Add(row);
        }
        scroll.Canvas.Layout.AddStretchCell();ApplyFilter(filter.Text);UpdateSelection(viewport.SelectedJoint);
    }
    void ApplyFilter(string text){foreach(var row in rows)row.Visible=string.IsNullOrWhiteSpace(text)||row.Role.Contains(text,StringComparison.OrdinalIgnoreCase)||JointRow.Title(row.Role).Contains(text,StringComparison.OrdinalIgnoreCase);}
    void UpdateSelection(string role){foreach(var row in rows){row.Selected=row.Role==role;row.Update();}}
    public void RefreshRows()=>UpdateSelection(viewport.SelectedJoint);
    protected override void OnPaint(){Paint.SetPen(Theme.ControlBackground.Lighten(.25f),1);Paint.SetBrush(Theme.ControlBackground);Paint.DrawRect(LocalRect);}

    sealed class JointRow : Widget
    {
        readonly Wizard wizard;
        public string Role {get;}
        public bool Selected {get;set;}
        public JointRow(Widget parent,Wizard wizard,string role,Action select):base(parent)
        {
            this.wizard=wizard;Role=role;Name="JointRow."+role;FixedHeight=34;MouseLeftPress=select;
            ToolTip=role;MouseTracking=true;
        }
        public static string Title(string role)
        {
            string side=role.EndsWith(".L")?"Left ":role.EndsWith(".R")?"Right ":"";
            string name=side.Length>0?role[..^2]:role;
            name=name switch{"UpperArm"=>"Shoulder","LowerArm"=>"Elbow","Hand"=>"Wrist","UpperLeg"=>"Hip","LowerLeg"=>"Knee","Foot"=>"Ankle",_=>name};
            return side+name;
        }
        protected override void OnPaint()
        {
            var fill=Selected?Theme.Green.WithAlpha(.13f):Paint.HasMouseOver?Theme.ControlBackground.Lighten(.3f):Theme.ControlBackground.Lighten(.12f);
            Paint.SetPen(Selected?Theme.Green.WithAlpha(.7f):Color.Transparent,1);Paint.SetBrush(fill);Paint.DrawRect(LocalRect.Shrink(1),4);
            Paint.SetPen(LandmarkOverlay.PointColor(Role));Paint.DrawIcon(new Rect(7,8,18,18),"adjust",16);
            Paint.SetPen(Theme.Text);Paint.SetDefaultFont(8,400);Paint.DrawText(new Rect(32,3,Width-52,16),Title(Role),TextFlag.LeftCenter);
            string detail=wizard.Rig is not null?Role:wizard.Anatomy!.Points[Role].Corrected?"Adjusted":wizard.Anatomy.Points[Role].Confidence<.35f?"Check Position":"Detected";
            Paint.SetPen(Theme.TextLight);Paint.SetDefaultFont(7,400);Paint.DrawText(new Rect(32,18,Width-52,12),detail,TextFlag.LeftCenter);
            if(Selected){Paint.SetPen(Theme.Green);Paint.DrawIcon(new Rect(Width-20,9,16,16),"chevron_right",14);}
        }
    }
}