Editor/ScfuConfigWindow.cs
using Editor;
using System;
using System.IO;
using System.Linq;
using System.Text.Json;

namespace Sandbox;

/// <summary>
/// Editor dialog for the <c>.scfu-config.json</c> file. Loads the config (or
/// defaults if absent), edits per-section via tabbed <see cref="ControlSheet"/>s
/// and writes back. Unknown fields the editor schema doesn't model are
/// preserved through each section's <c>[JsonExtensionData]</c> bag.
/// </summary>
public sealed class ScfuConfigWindow : Dialog
{
	const string PreferredConfigName = ".scfu-config.json";
	const string LegacyConfigName = "scfu.json";

	readonly string _projectRoot;
	readonly string _configPath;
	readonly bool _configExists;
	ScfuConfigSchema _config;
	Label _statusLabel;

	public ScfuConfigWindow( string projectRoot )
	{
		_projectRoot = projectRoot;

		var preferred = Path.Combine( projectRoot, PreferredConfigName );
		var legacy = Path.Combine( projectRoot, LegacyConfigName );

		if ( File.Exists( preferred ) )
		{
			_configPath = preferred;
			_configExists = true;
		}
		else if ( File.Exists( legacy ) )
		{
			_configPath = legacy;
			_configExists = true;
		}
		else
		{
			_configPath = preferred;
			_configExists = false;
		}

		_config = LoadConfig();

		Window.SetModal( true, true );
		Window.SetWindowIcon( "tune" );
		Window.Title = "SCFU Configuration";
		Window.Size = new Vector2( 760, 820 );
		Window.MinimumSize = new Vector2( 640, 600 );

		Layout = Layout.Column();
		Layout.Margin = 12;
		Layout.Spacing = 8;

		BuildHeader();
		BuildTabs();
		BuildFooter();
	}

	void BuildHeader()
	{
		var header = Layout.AddColumn();
		header.Spacing = 2;
		header.Add( new Label.Header( "SCFU Configuration" ) );

		var subtitle = new Label( _configExists ? $"Editing {Path.GetFileName( _configPath )}" : $"No config yet - defaults shown. Will write {PreferredConfigName} on save." );
		subtitle.WordWrap = true;
		header.Add( subtitle );

		var pathLabel = new Label( _configPath ) { Color = Color.Gray };
		pathLabel.WordWrap = true;
		header.Add( pathLabel );
	}

	void BuildTabs()
	{
		var tabs = new TabWidget( this );
		tabs.StateCookie = "scfu.config.tab";

		tabs.AddPage( "General", "settings", BuildSectionPage( _config, includeOnly: GeneralTopLevelNames ) );
		tabs.AddPage( "Safety", "shield", BuildSectionPage( _config.Safety ) );
		tabs.AddPage( "C#", "code", BuildSectionPage( _config.CSharp ) );
		tabs.AddPage( "Razor", "web", BuildSectionPage( _config.Razor ) );
		tabs.AddPage( "SCSS", "format_paint", BuildSectionPage( _config.Scss ) );
		tabs.AddPage( "JSON Assets", "data_object", BuildSectionPage( _config.JsonAssets ) );
		tabs.AddPage( "Filenames", "folder", BuildSectionPage( _config.Filenames ) );

		Layout.Add( tabs, 1 );
	}

	static readonly string[] GeneralTopLevelNames =
	{
		nameof( ScfuConfigSchema.Version ),
		nameof( ScfuConfigSchema.Seed ),
		nameof( ScfuConfigSchema.Include ),
		nameof( ScfuConfigSchema.Exclude ),
	};

	Widget BuildSectionPage( object section, string[] includeOnly = null )
	{
		var page = new Widget( this );
		page.Layout = Layout.Column();
		page.Layout.Margin = 0;

		var scroll = new ScrollArea( page );
		scroll.Canvas = new Widget( scroll );
		scroll.Canvas.Layout = Layout.Column();
		scroll.Canvas.Layout.Margin = new Sandbox.UI.Margin( 12, 12 );
		scroll.Canvas.Layout.Spacing = 4;

		var so = EditorTypeLibrary.GetSerializedObject( section );

		ControlSheet sheet;
		if ( includeOnly is null )
		{
			sheet = ControlSheet.Create( so );
		}
		else
		{
			sheet = new ControlSheet();
			foreach ( var name in includeOnly )
			{
				var prop = so.GetProperty( name );
				if ( prop is null ) continue;
				sheet.AddRow( prop );
			}
		}

		scroll.Canvas.Layout.Add( sheet );
		scroll.Canvas.Layout.AddStretchCell();

		page.Layout.Add( scroll, 1 );
		return page;
	}

	void BuildFooter()
	{
		var footer = Layout.AddRow();
		footer.Spacing = 8;

		_statusLabel = new Label( "" ) { Color = Color.Gray };
		footer.Add( _statusLabel, 1 );

		footer.AddStretchCell();

		var reset = new Button( "Reset to Defaults", "history" );
		reset.ToolTip = "Replace the in-memory config with fresh defaults. Not saved until you click Save.";
		reset.Clicked = OnReset;
		footer.Add( reset );

		var cancel = new Button( "Cancel" );
		cancel.Clicked = Close;
		footer.Add( cancel );

		var save = new Button.Primary( "Save", "save" );
		save.Clicked = OnSave;
		footer.Add( save );
	}

	ScfuConfigSchema LoadConfig()
	{
		if ( !_configExists )
			return new ScfuConfigSchema();

		try
		{
			var text = File.ReadAllText( _configPath );
			return JsonSerializer.Deserialize<ScfuConfigSchema>( text, ScfuConfigSchema.JsonOptions ) ?? new ScfuConfigSchema();
		}
		catch ( Exception ex )
		{
			Log.Warning( $"[SCFU] Failed to load {_configPath}: {ex.Message}. Showing defaults." );
			return new ScfuConfigSchema();
		}
	}

	void OnReset()
	{
		_config = new ScfuConfigSchema();
		Layout.Clear( true );
		BuildHeader();
		BuildTabs();
		BuildFooter();
		SetStatus( "Reset to defaults (unsaved)." );
	}

	void OnSave()
	{
		try
		{
			var json = JsonSerializer.Serialize( _config, ScfuConfigSchema.JsonOptions );
			Directory.CreateDirectory( Path.GetDirectoryName( _configPath )! );
			File.WriteAllText( _configPath, json );
			Log.Info( $"[SCFU] Wrote {_configPath}" );
			Close();
		}
		catch ( Exception ex )
		{
			Log.Warning( $"[SCFU] Save failed: {ex}" );
			SetStatus( $"Save failed: {ex.Message}", error: true );
		}
	}

	void SetStatus( string msg, bool error = false )
	{
		if ( !_statusLabel.IsValid() ) return;
		_statusLabel.Text = msg;
		_statusLabel.Color = error ? Color.Red : Color.Gray;
	}

	[EditorEvent.Frame]
	void CheckKeys()
	{
		if ( !Window.IsValid() || !Window.Visible ) return;
		if ( Editor.Application.IsKeyDown( KeyCode.Escape ) ) Close();
	}
}