Code/Generation/SuiHeaderEmitter.cs
using System;
using System.Text.RegularExpressions;
using SboxUiDesigner.Runtime;

namespace SboxUiDesigner.Generation;

/// <summary>
/// Auto-generated header emitter and parser. Every generated file gets a
/// header block with parseable BEGIN/END markers so the manifest checker
/// can verify file ownership before overwriting.
///
/// Format (per PRD doc 10/11):
///
///   SUI:GENERATED:BEGIN ===
///   Generated by Sbox UI Designer.
///   Source: InventoryUI.sui
///   DocumentId: sui_inventoryui_8f31
///   GeneratorVersion: 0.1.0
///   Do not edit this file manually. Changes will be overwritten.
///   SUI:GENERATED:END ===
///
/// Wrapped in Razor `@* ... *@` for .razor and CSS `/* ... */` for .scss.
/// The DocumentId in the header is what makes overwrite safe: the compile
/// step parses the header, sees the DocumentId, and refuses to overwrite
/// when it doesn't match the .sui being compiled.
/// </summary>
public static class SuiHeaderEmitter
{
	public const string Marker = "SUI:GENERATED";
	public const string GeneratorName = SuiSchemaVersion.CreatedWithTag;

	public static string EmitRazorHeader( SuiDocument doc )
	{
		var sourceName = (doc?.Name ?? "(unknown)") + ".sui";
		var docId = doc?.DocumentId ?? "(unknown)";
		var version = doc?.DesignerVersion ?? SuiSchemaVersion.DesignerVersion;

		return
			$"@* {Marker}:BEGIN ============================================================\n" +
			$"   Generated by {GeneratorName}.\n" +
			$"   Source: {sourceName}\n" +
			$"   DocumentId: {docId}\n" +
			$"   GeneratorVersion: {version}\n" +
			$"   Do not edit this file manually. Changes will be overwritten.\n" +
			$"   {Marker}:END ============================================================== *@\n";
	}

	public static string EmitScssHeader( SuiDocument doc )
	{
		var sourceName = (doc?.Name ?? "(unknown)") + ".sui";
		var docId = doc?.DocumentId ?? "(unknown)";
		var version = doc?.DesignerVersion ?? SuiSchemaVersion.DesignerVersion;

		return
			$"/* {Marker}:BEGIN ===========================================================\n" +
			$" * Generated by {GeneratorName}.\n" +
			$" * Source: {sourceName}\n" +
			$" * DocumentId: {docId}\n" +
			$" * GeneratorVersion: {version}\n" +
			$" * Do not edit this file manually. Changes will be overwritten.\n" +
			$" * {Marker}:END ============================================================= */\n";
	}

	/// <summary>
	/// Parse a generated file's header back into a <see cref="ParsedHeader"/>.
	/// Returns null if no valid header block is found; the manifest checker
	/// treats null as "not owned" and refuses to overwrite.
	/// </summary>
	public static ParsedHeader Parse( string content )
	{
		if ( string.IsNullOrEmpty( content ) ) return null;
		var beginMatch = Regex.Match( content, @"SUI:GENERATED:BEGIN\b", RegexOptions.IgnoreCase );
		var endMatch = Regex.Match( content, @"SUI:GENERATED:END\b", RegexOptions.IgnoreCase );
		if ( !beginMatch.Success || !endMatch.Success || endMatch.Index <= beginMatch.Index )
			return null;

		var block = content.Substring( beginMatch.Index, endMatch.Index - beginMatch.Index );

		string Find( string fieldName )
		{
			var m = Regex.Match( block, $@"{fieldName}\s*:\s*(.+)", RegexOptions.IgnoreCase );
			return m.Success ? m.Groups[1].Value.Trim().TrimEnd( '*', '/', ' ', '\t', '\r', '\n' ) : null;
		}

		return new ParsedHeader
		{
			Source = Find( "Source" ),
			DocumentId = Find( "DocumentId" ),
			GeneratorVersion = Find( "GeneratorVersion" ),
		};
	}

	public sealed class ParsedHeader
	{
		public string Source { get; set; }
		public string DocumentId { get; set; }
		public string GeneratorVersion { get; set; }

		public bool MatchesDocument( SuiDocument doc )
		{
			if ( doc == null ) return false;
			return string.Equals( DocumentId, doc.DocumentId, StringComparison.OrdinalIgnoreCase );
		}
	}
}