Editor/UI/WelcomeDialogue.cs
using System;
using System.IO;
using System.Threading.Tasks;
using Editor;
using Sandbox.SecBox.Bridge;
using Sandbox.SecBox.Lifecycle;

namespace Sandbox.SecBox.UI;

// One-time post-install welcome shown the first time secbox is loaded into
// a project. Trigger lives in Lifecycle.WelcomeDialogueTrigger; this file
// is pure UI. Modelled on SentinelInstallDialog - same CSS, same Layout
// idioms, same MainThread.Queue pattern for cross-thread label updates.
//
// Spelled "Dialogue" intentionally - chosen by the user despite sibling
// classes (SentinelInstallDialog, ReviewWindow, etc.) using "Dialog".
public sealed class WelcomeDialogue : BaseWindow
{
	const string CssH1     = "font-size: 18px; font-weight: 700; color: #ffffff;";
	const string CssH2     = "font-size: 13px; font-weight: 600; color: #ffffff;";
	const string CssBody   = "color: #e8eaee; font-size: 12px;";
	const string CssSubtle = "color: #9aa0a6; font-size: 11px;";

	const string GitHubUrl = "https://github.com/actual-f4-industries/sbox-secbox";

	Label _scanStatus;
	Button _btnScan;
	Checkbox _cbDontShow;
	bool _scanRunning;
	Pixmap _logo;

	public Action<WelcomeDialogueResult> Closed;

	public WelcomeDialogue() : base()
	{
		DeleteOnClose = true;
		Size = new Vector2(620, 620);
		WindowTitle = "secbox - welcome";
		SetWindowIcon("shield_lock");

		Layout = Layout.Column();
		Layout.Margin = 18;
		Layout.Spacing = 10;

		_logo = TryLoadLogo();

		BuildHeader();
		BuildWhatItDoes();
		BuildHowToUse();
		BuildScanRow();
		BuildDontShowRow();
		BuildFooter();
	}

	void BuildHeader()
	{
		if (_logo != null)
		{
			var banner = new LogoBannerWidget(_logo, this);
			banner.FixedHeight = 96;
			Layout.Add(banner);
		}

		var col = Layout.AddColumn();
		col.Spacing = 4;
		var title = new Label("Welcome to Secbox");
		title.SetStyles(CssH1);
		col.Add(title);
		var sub = new Label("Secbox is a defence-in-depth security layer for s&box editor projects. "
			+ "It scans every library you install for risky patterns, monitors runtime behaviour, "
			+ "and gives you a clear yes/no decision before trusting third-party code.");
		sub.SetStyles(CssSubtle);
		sub.WordWrap = true;
		col.Add(sub);
	}

	void BuildWhatItDoes()
	{
		AddSection("What it does",
			"On every editor boot, Secbox walks your project's Libraries/ folder and scans new or "
			+ "modified packages. Findings are recorded in <projectRoot>/.secbox/trust.json. "
			+ "Until you mark a package Trusted, runtime monitoring keeps an eye on what its code "
			+ "actually does - file writes, network calls, process spawns.");
	}

	void BuildHowToUse()
	{
		AddSection("How to use it",
			"• Install a library as usual via Library Manager.\n"
			+ "• Secbox runs a scan automatically and surfaces findings in the library row.\n"
			+ "• Review findings, mark Trusted or Blocked from the dock.\n"
			+ "• Optional: install the Sentinel sidecar for kernel-level visibility.");
	}

	void BuildScanRow()
	{
		var header = new Label("Run a scan now");
		header.SetStyles(CssH2);
		Layout.Add(header);

		var row = Layout.AddRow();
		row.Spacing = 8;

		_btnScan = new Button("Run first scan now");
		_btnScan.Icon = "radar";
		_btnScan.Clicked = OnRunScan;
		row.Add(_btnScan);

		_scanStatus = new Label("No scan run yet.");
		_scanStatus.SetStyles(CssBody);
		_scanStatus.WordWrap = true;
		row.Add(_scanStatus, 1);
	}

	void BuildDontShowRow()
	{
		_cbDontShow = new Checkbox("Don't show this welcome on any project again");
		_cbDontShow.Value = false;
		Layout.Add(_cbDontShow);

		var hint = new Label("You can re-open this dialogue any time via secbox > Show Welcome...");
		hint.SetStyles(CssSubtle);
		hint.WordWrap = true;
		Layout.Add(hint);
	}

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

		row.AddStretchCell();

		var github = new Button("View source on GitHub");
		github.Icon = "open_in_new";
		github.Clicked = OnOpenGitHub;
		row.Add(github);

		var gotIt = new Button.Primary("Got it");
		gotIt.Icon = "check";
		gotIt.Clicked = () => Close();
		row.Add(gotIt);
	}

	void OnOpenGitHub()
	{
		try
		{
			System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
			{
				FileName = GitHubUrl,
				UseShellExecute = true,
			});
		}
		catch (Exception ex)
		{
			DiagnosticsLog.Warn($"[secbox] welcome: open github failed: {ex.Message}");
		}
	}

	void OnRunScan()
	{
		if (_scanRunning) return;
		_scanRunning = true;
		_btnScan.Enabled = false;
		_scanStatus.Text = "Scanning...";

		// Open the same per-library results window the "Scan now" menu uses, then
		// run the scan off the UI thread and feed results back on the main thread.
		var window = ScanResultsWindow.OpenScanning();

		Task.Run(() =>
		{
			try
			{
				var results = BootAudit.ScanAllLibraries();
				MainThread.Queue(() =>
				{
					try
					{
						window.SetResults(results);
						_scanStatus.Text = results == null
							? "Scan could not run. See the diagnostics log."
							: $"Scan complete. {results.Count} library(ies) scanned.";
						_btnScan.Enabled = true;
					}
					catch { }
					_scanRunning = false;
				});
			}
			catch (Exception ex)
			{
				DiagnosticsLog.Error("[secbox] welcome: scan threw", ex);
				MainThread.Queue(() =>
				{
					try
					{
						window.SetResults(null);
						_scanStatus.Text = $"Scan failed: {ex.Message}";
						_btnScan.Enabled = true;
					}
					catch { }
					_scanRunning = false;
				});
			}
		});
	}

	public override void OnDestroyed()
	{
		try
		{
			Closed?.Invoke(new WelcomeDialogueResult
			{
				DontShowAgainGlobally = _cbDontShow?.Value ?? false,
			});
		}
		catch (Exception ex)
		{
			DiagnosticsLog.Warn($"[secbox] welcome: Closed callback threw: {ex.Message}");
		}
		base.OnDestroyed();
	}

	void AddSection(string title, string body)
	{
		var h = new Label(title);
		h.SetStyles(CssH2);
		Layout.Add(h);

		var p = new Label(body);
		p.SetStyles(CssBody);
		p.WordWrap = true;
		Layout.Add(p);
	}

	static Pixmap TryLoadLogo()
	{
		var root = PackageLocator.CurrentSecboxLibraryRoot();
		if (string.IsNullOrEmpty(root))
		{
			DiagnosticsLog.Warn("[secbox] welcome: could not resolve secbox library root - banner skipped");
			return null;
		}

		var path = Path.Combine(root, "Assets", "Materials", "secbox-logo-transparent.png");
		if (!File.Exists(path))
		{
			DiagnosticsLog.Warn($"[secbox] welcome: logo file not found at '{path}'");
			return null;
		}

		try
		{
			var pixmap = Pixmap.FromFile(path);
			if (pixmap == null)
			{
				DiagnosticsLog.Warn($"[secbox] welcome: Pixmap.FromFile returned null for '{path}' (path scheme issue?)");
				return null;
			}
			return pixmap;
		}
		catch (Exception ex)
		{
			DiagnosticsLog.Warn($"[secbox] welcome: Pixmap.FromFile threw: {ex.Message}");
			return null;
		}
	}

	// Letterboxes a square logo into whatever rect the layout assigns. The
	// pixmap is owned by the parent dialog; this widget only paints it.
	sealed class LogoBannerWidget : Widget
	{
		readonly Pixmap _pixmap;

		public LogoBannerWidget(Pixmap pixmap, Widget parent) : base(parent)
		{
			_pixmap = pixmap;
		}

		protected override void OnPaint()
		{
			if (_pixmap == null) return;

			var size = MathF.Min(LocalRect.Width, LocalRect.Height);
			var x = LocalRect.Left + (LocalRect.Width - size) / 2f;
			var y = LocalRect.Top + (LocalRect.Height - size) / 2f;
			Paint.Draw(new Rect(x, y, size, size), _pixmap);
		}
	}
}

public sealed class WelcomeDialogueResult
{
	public bool DontShowAgainGlobally;
}