Editor/UI/LibraryDetailPanel.cs
using System;
using System.Reflection;
using Editor;
using Sandbox.SecBox.Lifecycle;
using DiagnosticsLog = Sandbox.SecBox.Bridge.DiagnosticsLog;

namespace Sandbox.SecBox.UI;

// Re-injects a SecboxStatusPanel into the Library Manager's internal
// LibraryDetail widget. LibraryDetail is transient:
//   - LibraryManagerDock.Rebuild() replaces the dock's content widget each
//     time the engine bumps the dock's content hash.
//   - LibraryList.OnLibrarySelected fires `content.Layout.Clear( true );
//     content.Layout.Add( new LibraryDetail( library ) )` on every row click.
//   - LibraryDetail.FetchAndBuild() calls Layout.Clear( true ) on itself
//     after construction to populate header/body/buttons.
//
// One-shot injection is therefore guaranteed to be wiped. EnsureInstalled
// is called every frame from LibraryManagerInjector; it locates the current
// LibraryDetail descendant(s) and adds a SecboxStatusPanel to any that don't
// already have one as a descendant. The check is cheap and idempotent.
//
// The "currently displayed library" accessor on LibraryDetail is discovered
// once via reflection (instance field of type Package or LibraryProject) and
// cached as a MemberInfo. Per-instance resolvers close over the discovered
// member + the specific LibraryDetail instance.
internal static class LibraryDetailPanel
{
	static Type _detailType;
	static MemberInfo _selectedMember; // FieldInfo or PropertyInfo
	static Type _selectedMemberType;

	public static bool Installed { get; private set; }

	public static bool EnsureInstalled( Widget libraryManagerDock )
	{
		if ( libraryManagerDock == null || !libraryManagerDock.IsValid )
		{
			ResetState();
			return false;
		}

		if ( _detailType == null )
		{
			_detailType = ReflectionHelpers.ResolveEditorType(
				"Editor.LibraryManager.LibraryDetail", anchor: libraryManagerDock.GetType() );
			if ( _detailType == null )
			{
				DiagnosticsLog.Warn( "[secbox] LibraryDetailPanel: Editor.LibraryManager.LibraryDetail type not found - detail panel disabled" );
				return false;
			}
			DiscoverSelectedMember( _detailType );
		}

		bool any = false;
		try
		{
			foreach ( var w in libraryManagerDock.GetDescendants<Widget>() )
			{
				if ( w == null || w.GetType() != _detailType ) continue;
				if ( w.Layout == null ) continue;

				if ( HasPanel( w ) ) { any = true; continue; }

				// Don't inject into our own detail view. Same content-based
				// self-detection as BootAudit: sbproj presence is ground truth,
				// since "{org}.{ident}#local" forms (and forks) defeat prefix filters.
				if ( IsSecBoxLibrary( ResolveSelected( w ) ) ) continue;

				var detailInstance = w;
				var panel = new SecboxStatusPanel( detailInstance, () => ResolveSelected( detailInstance ) );
				w.Layout.Add( panel );
				panel.Refresh();
				any = true;

				if ( !Installed )
				{
					Installed = true;
					DiagnosticsLog.Info( "[secbox] LibraryDetailPanel installed in LibraryDetail (will re-inject as instances are recreated)" );
				}
			}
		}
		catch ( Exception ex )
		{
			DiagnosticsLog.Warn( $"[secbox] LibraryDetailPanel.EnsureInstalled: walk threw: {ex.Message}" );
		}

		// Refresh existing panels each tick so the status updates when the
		// trust store changes or when the user switches packages within the
		// same LibraryDetail instance.
		try
		{
			foreach ( var p in libraryManagerDock.GetDescendants<SecboxStatusPanel>() )
				p.Refresh();
		}
		catch { /* paint-adjacent - never propagate */ }

		return any;
	}

	static bool HasPanel( Widget detail )
	{
		try
		{
			foreach ( var d in detail.GetDescendants<SecboxStatusPanel>() )
				if ( d != null && d.IsValid ) return true;
		}
		catch { }
		return false;
	}

	static void DiscoverSelectedMember( Type detailType )
	{
		try
		{
			var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

			// Prefer LibraryProject-typed members. LibraryDetail has both a
			// `Package` field (always set, even for not-installed browse rows)
			// and an `Installed` LibraryProject field (null when the package
			// isn't locally installed). We want the latter so the SecBox panel
			// can hide itself when the user is browsing uninstalled packages.
			foreach ( var fi in detailType.GetFields( flags ) )
			{
				if ( IsLibraryProjectType( fi.FieldType ) )
				{
					_selectedMember = fi;
					_selectedMemberType = fi.FieldType;
					DiagnosticsLog.Trace( $"[secbox] LibraryDetail resolver: field '{fi.Name}' ({fi.FieldType.Name})" );
					return;
				}
			}
			foreach ( var pi in detailType.GetProperties( flags ) )
			{
				if ( IsLibraryProjectType( pi.PropertyType ) )
				{
					_selectedMember = pi;
					_selectedMemberType = pi.PropertyType;
					DiagnosticsLog.Trace( $"[secbox] LibraryDetail resolver: property '{pi.Name}' ({pi.PropertyType.Name})" );
					return;
				}
			}

			// Fallback: Package-typed member (legacy / future engine refactors).
			foreach ( var fi in detailType.GetFields( flags ) )
			{
				if ( IsLibraryType( fi.FieldType ) )
				{
					_selectedMember = fi;
					_selectedMemberType = fi.FieldType;
					DiagnosticsLog.Trace( $"[secbox] LibraryDetail resolver (fallback): field '{fi.Name}' ({fi.FieldType.Name})" );
					return;
				}
			}
			foreach ( var pi in detailType.GetProperties( flags ) )
			{
				if ( IsLibraryType( pi.PropertyType ) )
				{
					_selectedMember = pi;
					_selectedMemberType = pi.PropertyType;
					DiagnosticsLog.Trace( $"[secbox] LibraryDetail resolver (fallback): property '{pi.Name}' ({pi.PropertyType.Name})" );
					return;
				}
			}

			DiagnosticsLog.Warn( "[secbox] LibraryDetailPanel: no LibraryProject/Package-typed member found on LibraryDetail - panel will show 'no library selected'" );
		}
		catch ( Exception ex )
		{
			DiagnosticsLog.Warn( $"[secbox] LibraryDetailPanel.DiscoverSelectedMember threw: {ex.Message}" );
		}
	}

	static bool IsLibraryType( Type t )
	{
		if ( t == null ) return false;
		if ( t == typeof( LibraryProject ) ) return true;
		if ( t == typeof( Package ) ) return true;
		if ( typeof( LibraryProject ).IsAssignableFrom( t ) ) return true;
		if ( typeof( Package ).IsAssignableFrom( t ) ) return true;
		return false;
	}

	static bool IsLibraryProjectType( Type t )
	{
		if ( t == null ) return false;
		if ( t == typeof( LibraryProject ) ) return true;
		if ( typeof( LibraryProject ).IsAssignableFrom( t ) ) return true;
		return false;
	}

	static object ResolveSelected( Widget detailInstance )
	{
		if ( detailInstance == null || !detailInstance.IsValid || _selectedMember == null ) return null;
		try
		{
			return _selectedMember switch
			{
				FieldInfo fi => fi.GetValue( detailInstance ),
				PropertyInfo pi => pi.GetValue( detailInstance ),
				_ => null,
			};
		}
		catch { return null; }
	}

	static bool IsSecBoxLibrary( object lib )
	{
		if ( lib is not LibraryProject lp ) return false;
		try
		{
			var folder = lp.Project?.RootDirectory?.FullName;
			if ( string.IsNullOrEmpty( folder ) ) return false;
			return System.IO.File.Exists( System.IO.Path.Combine( folder, "secbox.sbproj" ) );
		}
		catch { return false; }
	}

	static void ResetState()
	{
		Installed = false;
		// _detailType + _selectedMember are stable across dock lifetimes - keep them cached.
	}
}