Editor/StepIdAutoFixer.cs
#nullable disable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

public sealed class StepIdFixPlan
{
	public string FilePath { get; init; }
	public string ResourceId { get; init; }
	public string OriginalText { get; init; }
	public string UpdatedText { get; init; }
	public int AddedCount { get; init; }
	public bool HasChanges => AddedCount > 0 && OriginalText != UpdatedText;
}

public static class StepIdAutoFixer
{
	public static StepIdFixPlan BuildPlan( string filePath, string resourceId )
	{
		var original = File.ReadAllText( filePath );
		var updated = AddMissingStepIds( original, out var added );
		return new StepIdFixPlan
		{
			FilePath = filePath,
			ResourceId = resourceId,
			OriginalText = original,
			UpdatedText = updated,
			AddedCount = added
		};
	}

	public static void ApplyPlanWithBackup( StepIdFixPlan plan )
	{
		if ( plan == null || !plan.HasChanges ) return;
		var backupPath = $"{plan.FilePath}.bak.{DateTime.Now:yyyyMMddHHmmss}";
		File.Copy( plan.FilePath, backupPath, overwrite: false );
		File.WriteAllText( plan.FilePath, plan.UpdatedText );
	}

	private static string AddMissingStepIds( string source, out int added )
	{
		added = 0;
		var newline = source.Contains( "\r\n" ) ? "\r\n" : "\n";
		var lines = source.Replace( "\r\n", "\n" ).Replace( '\r', '\n' ).Split( '\n' ).ToList();
		var usedIds = new HashSet<string>( StringComparer.OrdinalIgnoreCase );

		for ( var i = 0; i < lines.Count; i++ )
		{
			if ( !IsStepsLine( lines[i], out var stepsIndent ) ) continue;

			var itemIndent = -1;
			for ( var j = i + 1; j < lines.Count; j++ )
			{
				var line = lines[j];
				if ( string.IsNullOrWhiteSpace( line ) ) continue;
				var indent = CountIndent( line );
				var trimmed = line.TrimStart();
				if ( indent <= stepsIndent && !trimmed.StartsWith( "#" ) ) break;
				if ( !trimmed.StartsWith( "-" ) ) continue;
				if ( itemIndent < 0 ) itemIndent = indent;
				if ( indent != itemIndent ) continue;

				var blockEnd = FindBlockEnd( lines, j + 1, itemIndent, stepsIndent );
				CollectExistingIds( lines, j, blockEnd, usedIds );
				if ( StepHasId( lines, j, blockEnd ) )
				{
					j = blockEnd - 1;
					continue;
				}

				var id = UniqueId( BuildReadableId( lines, j, blockEnd, added + 1 ), usedIds );
				usedIds.Add( id );
				InsertId( lines, j, id );
				added++;
				j = blockEnd;
			}
		}

		return string.Join( newline, lines );
	}

	private static bool IsStepsLine( string line, out int indent )
	{
		indent = CountIndent( line );
		var trimmed = line.Trim();
		return trimmed == "steps:";
	}

	private static int FindBlockEnd( List<string> lines, int start, int itemIndent, int stepsIndent )
	{
		for ( var i = start; i < lines.Count; i++ )
		{
			if ( string.IsNullOrWhiteSpace( lines[i] ) ) continue;
			var indent = CountIndent( lines[i] );
			var trimmed = lines[i].TrimStart();
			if ( indent <= stepsIndent && !trimmed.StartsWith( "#" ) ) return i;
			if ( indent == itemIndent && trimmed.StartsWith( "-" ) ) return i;
		}
		return lines.Count;
	}

	private static bool StepHasId( List<string> lines, int start, int end )
	{
		var first = lines[start].TrimStart();
		if ( Regex.IsMatch( first, @"^-\s+id\s*:\s*\S+" ) ) return true;
		var itemIndent = CountIndent( lines[start] );
		for ( var i = start + 1; i < end; i++ )
		{
			var trimmed = lines[i].TrimStart();
			if ( CountIndent( lines[i] ) > itemIndent && Regex.IsMatch( trimmed, @"^id\s*:\s*\S+" ) ) return true;
		}
		return false;
	}

	private static void CollectExistingIds( List<string> lines, int start, int end, HashSet<string> usedIds )
	{
		for ( var i = start; i < end; i++ )
		{
			var match = Regex.Match( lines[i], @"(?:^|[-\s])id\s*:\s*['\""']?([A-Za-z0-9_-]+)" );
			if ( match.Success ) usedIds.Add( match.Groups[1].Value );
		}
	}

	private static void InsertId( List<string> lines, int index, string id )
	{
		var line = lines[index];
		var indent = new string( ' ', CountIndent( line ) );
		var afterDash = line.TrimStart()[1..].TrimStart();
		if ( string.IsNullOrWhiteSpace( afterDash ) )
		{
			lines.Insert( index + 1, $"{indent}  id: {id}" );
			return;
		}
		lines[index] = $"{indent}- id: {id}";
		lines.Insert( index + 1, $"{indent}  {afterDash}" );
	}

	private static string BuildReadableId( List<string> lines, int start, int end, int ordinal )
	{
		var type = FindValue( lines, start, end, "type" );
		var collection = FindValue( lines, start, end, "collection" );
		var target = FindValue( lines, start, end, "target" ) ?? FindValue( lines, start, end, "path" );
		var parts = new[] { VerbForType( type ), collection, target }.Where( x => !string.IsNullOrWhiteSpace( x ) ).ToList();
		var candidate = Slugify( string.Join( "-", parts ) );
		return string.IsNullOrWhiteSpace( candidate ) ? $"step-{ordinal:000}" : candidate;
	}

	private static string FindValue( List<string> lines, int start, int end, string key )
	{
		var pattern = new Regex( $@"(?:^|[-\s]){Regex.Escape( key )}\s*:\s*['\""']?([^'\""#]+)" );
		for ( var i = start; i < end; i++ )
		{
			var match = pattern.Match( lines[i] );
			if ( match.Success ) return match.Groups[1].Value.Trim();
		}
		return null;
	}

	private static string VerbForType( string type ) => (type ?? "").ToLowerInvariant() switch
	{
		"read" => "read",
		"write" => "save",
		"set" => "set",
		"create" => "create",
		"transform" => "set",
		"lookup" => "lookup",
		"condition" => "check",
		"assert" => "validate",
		_ => type
	};

	private static string UniqueId( string baseId, HashSet<string> used )
	{
		var id = string.IsNullOrWhiteSpace( baseId ) ? "step" : baseId;
		if ( !used.Contains( id ) ) return id;
		for ( var i = 2; i < 999; i++ )
		{
			var next = $"{id}-{i:00}";
			if ( !used.Contains( next ) ) return next;
		}
		return $"{id}-{used.Count + 1}";
	}

	private static string Slugify( string value )
	{
		value = Regex.Replace( value ?? "", @"\{\{.*?\}\}", "" );
		value = Regex.Replace( value.ToLowerInvariant(), @"[^a-z0-9]+", "-" ).Trim( '-' );
		return string.IsNullOrWhiteSpace( value ) ? "" : value;
	}

	private static int CountIndent( string line )
	{
		var count = 0;
		while ( count < line.Length && line[count] == ' ' ) count++;
		return count;
	}
}