Editor/SyncToolSourceFiles.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using Sandbox;

public static partial class SyncToolConfig
{
	public sealed class SourceSummary
	{
		public string Kind { get; set; } = "";
		public string Id { get; set; } = "";
		public string Name { get; set; } = "";
		public string Description { get; set; } = "";
		public string Path { get; set; } = "";
	}

	public static string LibrariesPath => $"{SyncToolsPath}/libraries";

	public static void SetSourceExportMode( SourceExportMode mode )
	{
		SourceExport = mode;
		if ( File.Exists( Abs( ProjectConfigFile ) ) )
			Save( SecretKey, PublicApiKey, ProjectId, BaseUrl, DataSource, DataFolder, CdnUrl );
	}

	public static string SaveSourceResource( string kind, string id, string sourceText, string sourcePath = null )
	{
		if ( string.IsNullOrWhiteSpace( sourceText ) )
			return null;

		var folder = SourceFolderForKind( kind );
		var fileName = SourceFileNameForResource( kind, id, sourcePath );
		var relativePath = $"{folder}/{fileName}";
		var absoluteDir = Abs( folder );
		if ( !Directory.Exists( absoluteDir ) )
			Directory.CreateDirectory( absoluteDir );

		File.WriteAllText( Abs( relativePath ), sourceText );
		return relativePath;
	}

	public static List<SourceSummary> LoadSourceSummaries( string kind )
	{
		var folder = SourceFolderForKind( kind );
		var absoluteDir = Abs( folder );
		if ( !Directory.Exists( absoluteDir ) )
			return new List<SourceSummary>();

		return Directory.GetFiles( absoluteDir, "*.y*ml" )
			.Select( path => TryReadSourceSummary( kind, path ) )
			.Where( summary => summary != null )
			.OrderBy( summary => summary.Id, StringComparer.OrdinalIgnoreCase )
			.ToList();
	}

	public static List<JsonElement> LoadSourcePayloadResources( string kind, bool includeDeprecated = true )
	{
		var folder = SourceFolderForKind( kind );
		var absoluteDir = Abs( folder );
		if ( !Directory.Exists( absoluteDir ) )
			return new List<JsonElement>();

		var typed = Directory.GetFiles( absoluteDir, $"*.{kind}.yml" )
			.Concat( Directory.GetFiles( absoluteDir, $"*.{kind}.yaml" ) );
		var plain = Directory.GetFiles( absoluteDir, "*.yml" )
			.Concat( Directory.GetFiles( absoluteDir, "*.yaml" ) )
			.Where( path => !Path.GetFileName( path ).Contains( $".{kind}.", StringComparison.OrdinalIgnoreCase ) );

		return typed.Concat( plain )
			.Distinct( StringComparer.OrdinalIgnoreCase )
			.OrderBy( path => path, StringComparer.OrdinalIgnoreCase )
			.Select( path => TryLoadSourcePayloadResource( kind, path, out var resource, includeDeprecated ) ? resource : default )
			.Where( resource => resource.ValueKind != JsonValueKind.Undefined )
			.ToList();
	}

	public static bool TryLoadSourcePayloadResource( string kind, string absolutePath, out JsonElement resource, bool includeDeprecated = true )
	{
		resource = default;
		try
		{
			var sourceText = File.ReadAllText( absolutePath );
			if ( !includeDeprecated && kind.Equals( "endpoint", StringComparison.OrdinalIgnoreCase ) && IsDeprecatedSourceText( sourceText ) )
				return false;

			var id = ResourceIdFromFilePath( absolutePath, kind );
			var payload = new Dictionary<string, object>
			{
				["kind"] = kind,
				["id"] = id,
				["authoringMode"] = "source",
				["sourceFormat"] = Path.GetExtension( absolutePath ).Equals( ".json", StringComparison.OrdinalIgnoreCase ) ? "json" : "yaml",
				["sourcePath"] = Path.GetFileName( absolutePath ),
				["sourceText"] = sourceText
			};
			resource = JsonSerializer.Deserialize<JsonElement>( JsonSerializer.Serialize( payload ) );
			return resource.ValueKind == JsonValueKind.Object;
		}
		catch ( Exception ex )
		{
			Log.Warning( $"[SyncTool] Failed to load raw source {absolutePath}: {ex.Message}" );
			return false;
		}
	}

	private static bool IsDeprecatedSourceText( string sourceText )
	{
		foreach ( var raw in (sourceText ?? "").Replace( "\r\n", "\n" ).Replace( '\r', '\n' ).Split( '\n' ) )
		{
			var line = raw.Trim();
			if ( line.Length == 0 || line.StartsWith( "#" ) )
				continue;

			var colon = line.IndexOf( ':' );
			if ( colon <= 0 )
				continue;

			var key = line[..colon].Trim();
			if ( !key.Equals( "deprecated", StringComparison.OrdinalIgnoreCase )
				&& !key.Equals( "_deprecated", StringComparison.OrdinalIgnoreCase )
				&& !key.Equals( "depreciated", StringComparison.OrdinalIgnoreCase )
				&& !key.Equals( "depricated", StringComparison.OrdinalIgnoreCase ) )
				continue;

			var value = line[(colon + 1)..].Trim().Trim( '"', '\'' );
			if ( IsTruthyString( value ) )
				return true;
		}

		return false;
	}

	public static List<JsonElement> LoadSourceCanonicalResources( string kind )
	{
		var folder = SourceFolderForKind( kind );
		var absoluteDir = Abs( folder );
		if ( !Directory.Exists( absoluteDir ) )
			return new List<JsonElement>();

		var typed = Directory.GetFiles( absoluteDir, $"*.{kind}.yml" )
			.Concat( Directory.GetFiles( absoluteDir, $"*.{kind}.yaml" ) );
		var plain = Directory.GetFiles( absoluteDir, "*.yml" )
			.Concat( Directory.GetFiles( absoluteDir, "*.yaml" ) )
			.Where( path => !Path.GetFileName( path ).Contains( $".{kind}.", StringComparison.OrdinalIgnoreCase ) );

		return typed.Concat( plain )
			.Distinct( StringComparer.OrdinalIgnoreCase )
			.OrderBy( path => path, StringComparer.OrdinalIgnoreCase )
			.Select( path => TryLoadSourceCanonicalResource( kind, path, out var resource ) ? resource : default )
			.Where( resource => resource.ValueKind != JsonValueKind.Undefined )
			.ToList();
	}

	public static bool TryLoadSourceCanonicalResource( string kind, string absolutePath, out JsonElement resource )
	{
		resource = default;

		try
		{
			var sourceText = File.ReadAllText( absolutePath );
			return TryParseSourceText( kind, sourceText, absolutePath, out resource );
		}
		catch ( Exception ex )
		{
			Log.Warning( $"[SyncTool] Failed to load source {absolutePath}: {ex.Message}" );
			return false;
		}
	}

	private static bool TryParseSourceText( string kind, string sourceText, string sourcePath, out JsonElement resource )
	{
		resource = default;

		var lines = sourceText.Replace( "\r\n", "\n" ).Replace( '\r', '\n' ).Split( '\n' );
		var index = 0;
		var root = ParseYamlMap( sourcePath, lines, ref index, 0 );

		if ( root.TryGetValue( "sourceText", out var embeddedSourceTextValue )
			&& embeddedSourceTextValue is string embeddedSourceText
			&& !string.IsNullOrWhiteSpace( embeddedSourceText )
			&& (!root.TryGetValue( "kind", out var embeddedKind ) || string.IsNullOrWhiteSpace( ScalarToString( embeddedKind ) )) )
		{
			return TryParseSourceText( kind, embeddedSourceText, sourcePath, out resource );
		}

		var actualKind = root.TryGetValue( "kind", out var kindValue )
			? ScalarToString( kindValue )
			: root.TryGetValue( "resourceKind", out var resourceKindValue )
				? ScalarToString( resourceKindValue )
				: null;
		if ( !string.Equals( actualKind, kind, StringComparison.OrdinalIgnoreCase ) )
			return false;

		var sourceBody = UnwrapSourceBody( kind, root );
		var id = sourceBody.TryGetValue( "id", out var bodyId ) ? ScalarToString( bodyId ) : null;
		if ( string.IsNullOrWhiteSpace( id ) )
			id = ResourceIdFromFilePath( sourcePath, kind );
		if ( string.IsNullOrWhiteSpace( id ) )
			return false;

		var canonical = new Dictionary<string, object>( StringComparer.OrdinalIgnoreCase );
		switch ( kind )
		{
			case "collection":
				canonical["id"] = id;
				canonical["name"] = id;
				break;
			case "endpoint":
				canonical["id"] = id;
				canonical["slug"] = id;
				break;
			default:
				canonical["id"] = id;
				break;
		}

		foreach ( var pair in sourceBody )
		{
			if ( IsSourceOnlyProperty( pair.Key ) )
				continue;
			canonical[pair.Key] = pair.Value;
		}

		canonical["authoringMode"] = "source";
		canonical["sourceFormat"] = "yaml";
		canonical["sourcePath"] = Path.GetFileName( sourcePath );
		canonical["sourceText"] = sourceText;
		if ( root.TryGetValue( "sourceVersion", out var sourceVersionValue ) )
		{
			var sourceVersion = ScalarToString( sourceVersionValue );
			if ( !string.IsNullOrWhiteSpace( sourceVersion ) )
				canonical["sourceVersion"] = sourceVersion;
		}

		if ( kind == "endpoint" )
		{
			if ( canonical.TryGetValue( "exposure", out var exposure ) )
				canonical["exposure"] = NormalizeExposure( exposure?.ToString() );
			else if ( canonical.TryGetValue( "internalOnly", out var internalOnly ) && internalOnly is bool b && b )
				canonical["exposure"] = "internal";
			else if ( canonical.TryGetValue( "publiclyCallable", out var callable ) && callable is bool c && c )
				canonical["exposure"] = "public";
		}

		resource = JsonSerializer.Deserialize<JsonElement>( JsonSerializer.Serialize( canonical ) );
		return resource.ValueKind == JsonValueKind.Object;
	}

	private static Dictionary<string, object> UnwrapSourceBody( string kind, Dictionary<string, object> source )
	{
		if ( TryGetMap( source, kind, out var kindBody ) )
		{
			var merged = CopyExcept( source, kind, "resource" );
			foreach ( var pair in kindBody ) merged[pair.Key] = pair.Value;
			return merged;
		}

		if ( TryGetMap( source, "resource", out var resourceBody ) )
		{
			var merged = CopyExcept( source, "resource", kind );
			foreach ( var pair in resourceBody ) merged[pair.Key] = pair.Value;
			return merged;
		}

		if ( TryGetMap( source, "definition", out var definitionBody ) )
		{
			var merged = new Dictionary<string, object>( definitionBody, StringComparer.OrdinalIgnoreCase );
			foreach ( var pair in source )
			{
				if ( pair.Key.Equals( "definition", StringComparison.OrdinalIgnoreCase )
					|| pair.Key.Equals( kind, StringComparison.OrdinalIgnoreCase )
					|| pair.Key.Equals( "resource", StringComparison.OrdinalIgnoreCase ) )
					continue;
				merged[pair.Key] = pair.Value;
			}
			return merged;
		}

		return new Dictionary<string, object>( source, StringComparer.OrdinalIgnoreCase );
	}

	private static Dictionary<string, object> CopyExcept( Dictionary<string, object> source, params string[] excludedKeys )
	{
		var excluded = new HashSet<string>( excludedKeys, StringComparer.OrdinalIgnoreCase );
		var copy = new Dictionary<string, object>( StringComparer.OrdinalIgnoreCase );
		foreach ( var pair in source )
		{
			if ( excluded.Contains( pair.Key ) )
				continue;
			copy[pair.Key] = pair.Value;
		}
		return copy;
	}

	private static bool TryGetMap( Dictionary<string, object> source, string key, out Dictionary<string, object> map )
	{
		map = null;
		if ( !source.TryGetValue( key, out var value ) )
			return false;

		if ( value is Dictionary<string, object> typed )
		{
			map = typed;
			return true;
		}

		if ( value is IDictionary<string, object> generic )
		{
			map = new Dictionary<string, object>( generic, StringComparer.OrdinalIgnoreCase );
			return true;
		}

		return false;
	}

	private static string ScalarToString( object value )
	{
		return value switch
		{
			null => null,
			string s => s,
			JsonElement element when element.ValueKind == JsonValueKind.String => element.GetString(),
			JsonElement element => element.ToString(),
			_ => value.ToString()
		};
	}

	private static bool IsSourceOnlyProperty( string name ) => name switch
	{
		"sourceVersion" or "kind" or "resourceKind" or "imports" or "libraries" or "dslVersion" or "definition" or "resource" => true,
		_ => false
	};

	private static string NormalizeExposure( string value )
	{
		var text = (value ?? "").Trim().ToLowerInvariant().Replace( '_', '-' ).Replace( ' ', '-' );
		return text switch
		{
			"public" or "publicly-callable" or "game-api" or "external" or "exposed" => "public",
			"internal" or "private" or "internal-private" or "reusable" or "reusable-logic" or "workflow" => "internal",
			_ => text
		};
	}

	public static bool HasSourceFiles()
	{
		return HasSourceFiles( CollectionsPath )
			|| HasSourceFiles( EndpointsPath )
			|| HasSourceFiles( WorkflowsPath )
			|| HasSourceFiles( TestsPath )
			|| HasSourceFiles( LibrariesPath );
	}

	private static bool HasSourceFiles( string relativeFolder )
	{
		var absoluteDir = Abs( relativeFolder );
		return Directory.Exists( absoluteDir )
			&& (Directory.GetFiles( absoluteDir, "*.yaml" ).Length > 0
				|| Directory.GetFiles( absoluteDir, "*.yml" ).Length > 0);
	}

	public static string ResourceIdFromFilePath( string filePath, string kind )
	{
		var fileName = Path.GetFileName( filePath );
		foreach ( var suffix in new[] { $".{kind}.yaml", $".{kind}.yml", ".yaml", ".yml", ".json" } )
		{
			if ( fileName.EndsWith( suffix, StringComparison.OrdinalIgnoreCase ) )
				return fileName[..^suffix.Length];
		}

		return Path.GetFileNameWithoutExtension( fileName );
	}

	private static SourceSummary TryReadSourceSummary( string expectedKind, string absolutePath )
	{
		try
		{
			var values = new Dictionary<string, string>( StringComparer.OrdinalIgnoreCase );
			foreach ( var raw in File.ReadLines( absolutePath ) )
			{
				if ( string.IsNullOrWhiteSpace( raw ) )
					continue;
				if ( char.IsWhiteSpace( raw[0] ) )
					continue;

				var line = raw.Trim();
				if ( line.StartsWith( "#" ) )
					continue;

				var colon = line.IndexOf( ':' );
				if ( colon <= 0 )
					continue;

				var key = line[..colon].Trim();
				if ( key == "definition" )
					break;

				var value = UnquoteYamlScalar( line[(colon + 1)..].Trim() );
				values[key] = value;
			}

			if ( !values.TryGetValue( "kind", out var kind ) || kind != expectedKind )
				return null;

			var fallbackId = SourceIdFromFileName( expectedKind, Path.GetFileName( absolutePath ) );
			values.TryGetValue( "id", out var id );
			if ( string.IsNullOrWhiteSpace( id ) )
				id = fallbackId;
			if ( string.IsNullOrWhiteSpace( id ) )
				return null;

			values.TryGetValue( "name", out var name );
			values.TryGetValue( "description", out var description );
			return new SourceSummary
			{
				Kind = kind,
				Id = id,
				Name = name ?? "",
				Description = description ?? "",
				Path = absolutePath
			};
		}
		catch
		{
			return null;
		}
	}

	private static string SourceFolderForKind( string kind ) => kind switch
	{
		"collection" => CollectionsPath,
		"endpoint" => EndpointsPath,
		"workflow" => WorkflowsPath,
		"test" => TestsPath,
		"library" => LibrariesPath,
		_ => SyncToolsPath
	};

	private static string SourceFileNameForResource( string kind, string id, string sourcePath )
	{
		var incomingName = Path.GetFileName( (sourcePath ?? "").Replace( '\\', '/' ) );
		if ( incomingName.EndsWith( $".{kind}.yaml", StringComparison.OrdinalIgnoreCase )
			|| incomingName.EndsWith( $".{kind}.yml", StringComparison.OrdinalIgnoreCase ) )
			return incomingName;

		return $"{SafeResourceFileName( id )}.{kind}.yml";
	}

	private static string SourceIdFromFileName( string kind, string fileName )
	{
		foreach ( var suffix in new[] { $".{kind}.yaml", $".{kind}.yml" } )
		{
			if ( fileName.EndsWith( suffix, StringComparison.OrdinalIgnoreCase ) )
				return fileName[..^suffix.Length];
		}
		return Path.GetFileNameWithoutExtension( fileName );
	}

	private static string SafeResourceFileName( string value )
	{
		var chars = (value ?? "unknown")
			.Select( ch => char.IsLetterOrDigit( ch ) || ch is '_' or '-' or '.' ? ch : '_' )
			.ToArray();
		var safe = new string( chars ).Trim( '.' );
		return string.IsNullOrWhiteSpace( safe ) ? "unknown" : safe;
	}

	private static string UnquoteYamlScalar( string value )
	{
		if ( value.Length >= 2 )
		{
			var first = value[0];
			var last = value[^1];
			if ( (first == '"' && last == '"') || (first == '\'' && last == '\'') )
				return value[1..^1];
		}
		return value;
	}

	private static string DecodeYamlScalar( string value )
	{
		if ( value.Length >= 2 && value[0] == '"' && value[^1] == '"' )
		{
			try { return JsonSerializer.Deserialize<string>( value ); }
			catch { return value[1..^1]; }
		}

		if ( value.Length >= 2 && value[0] == '\'' && value[^1] == '\'' )
			return value[1..^1].Replace( "''", "'" );

		return value;
	}

	private static string SerializeIndentedDefinitionYamlAsJson( string absolutePath, string[] lines, int startLine )
	{
		var index = startLine;
		var parsed = ParseYamlNode( absolutePath, lines, ref index, 2 );
		return parsed is null ? "" : JsonSerializer.Serialize( parsed );
	}

	private static object ParseYamlNode( string absolutePath, string[] lines, ref int index, int indent )
	{
		SkipBlankLines( lines, ref index );
		if ( index >= lines.Length )
			return null;

		var line = lines[index];
		var actualIndent = CountLeadingSpaces( line );
		if ( actualIndent < indent )
			return null;

		var trimmed = line[actualIndent..];
		return trimmed.StartsWith( "-", StringComparison.Ordinal )
			? ParseYamlList( absolutePath, lines, ref index, actualIndent )
			: ParseYamlMap( absolutePath, lines, ref index, actualIndent );
	}

	private static Dictionary<string, object> ParseYamlMap( string absolutePath, string[] lines, ref int index, int indent )
	{
		var result = new Dictionary<string, object>( StringComparer.OrdinalIgnoreCase );

		while ( index < lines.Length )
		{
			if ( IsBlankOrCommentLine( lines[index] ) )
			{
				index++;
				continue;
			}

			var currentIndent = CountLeadingSpaces( lines[index] );
			if ( currentIndent < indent )
				break;
			if ( currentIndent > indent )
				throw new FormatException( $"Unexpected indentation in {absolutePath} at line {index + 1}." );

			var content = lines[index][indent..];
			if ( content.StartsWith( "-", StringComparison.Ordinal ) )
				break;

			var colon = FindTopLevelColon( content );
			if ( colon <= 0 )
				throw new FormatException( $"Invalid YAML mapping entry in {absolutePath} at line {index + 1}: {content}" );

			var key = content[..colon].Trim();
			var valueText = content[(colon + 1)..].Trim();
			index++;

			if ( IsBlockScalarHeader( valueText ) )
			{
				result[key] = ParseYamlBlockScalar( lines, ref index, indent + 2, valueText );
			}
			else if ( string.IsNullOrEmpty( valueText ) )
			{
				var child = ParseYamlNode( absolutePath, lines, ref index, indent + 2 );
				result[key] = child ?? new Dictionary<string, object>( StringComparer.OrdinalIgnoreCase );
			}
			else
			{
				result[key] = ParseYamlScalar( valueText );
			}
		}

		return result;
	}

	private static List<object> ParseYamlList( string absolutePath, string[] lines, ref int index, int indent )
	{
		var result = new List<object>();

		while ( index < lines.Length )
		{
			if ( IsBlankOrCommentLine( lines[index] ) )
			{
				index++;
				continue;
			}

			var currentIndent = CountLeadingSpaces( lines[index] );
			if ( currentIndent < indent )
				break;
			if ( currentIndent > indent )
				throw new FormatException( $"Unexpected list indentation in {absolutePath} at line {index + 1}." );

			var content = lines[index][indent..];
			if ( !content.StartsWith( "-", StringComparison.Ordinal ) )
				break;

			var itemText = content[1..].TrimStart();
			index++;

			if ( string.IsNullOrEmpty( itemText ) )
			{
				var child = ParseYamlNode( absolutePath, lines, ref index, indent + 2 );
				result.Add( child ?? new Dictionary<string, object>( StringComparer.OrdinalIgnoreCase ) );
			}
			else if ( TryParseInlineMapEntry( itemText, out var inlineMap ) )
			{
				var continuation = ParseYamlNode( absolutePath, lines, ref index, indent + 2 );
				if ( continuation is Dictionary<string, object> continuationMap )
				{
					foreach ( var pair in continuationMap )
						inlineMap[pair.Key] = pair.Value;
				}

				result.Add( inlineMap );
			}
			else
			{
				result.Add( ParseYamlScalar( itemText ) );
			}
		}

		return result;
	}

	private static bool IsBlockScalarHeader( string value )
	{
		return value is "|" or "|-" or "|+" or ">" or ">-" or ">+";
	}

	private static string ParseYamlBlockScalar( string[] lines, ref int index, int minIndent, string header )
	{
		var blockLines = new List<string>();
		var blockIndent = -1;
		var chompStrip = header.EndsWith( "-", StringComparison.Ordinal );
		var folded = header.StartsWith( ">", StringComparison.Ordinal );

		while ( index < lines.Length )
		{
			var line = lines[index];
			if ( string.IsNullOrWhiteSpace( line ) )
			{
				blockLines.Add( "" );
				index++;
				continue;
			}

			var currentIndent = CountLeadingSpaces( line );
			if ( currentIndent < minIndent )
				break;

			if ( blockIndent < 0 )
				blockIndent = currentIndent;
			var trimIndent = Math.Min( currentIndent, blockIndent );
			blockLines.Add( line.Length >= trimIndent ? line[trimIndent..] : "" );
			index++;
		}

		var text = folded ? FoldYamlBlockLines( blockLines ) : string.Join( "\n", blockLines );
		return chompStrip ? text.TrimEnd( '\n' ) : text + "\n";
	}

	private static string FoldYamlBlockLines( List<string> blockLines )
	{
		var paragraphs = new List<string>();
		var current = new List<string>();
		foreach ( var line in blockLines )
		{
			if ( string.IsNullOrEmpty( line ) )
			{
				if ( current.Count > 0 )
				{
					paragraphs.Add( string.Join( " ", current ) );
					current.Clear();
				}
				paragraphs.Add( "" );
				continue;
			}
			current.Add( line );
		}
		if ( current.Count > 0 )
			paragraphs.Add( string.Join( " ", current ) );
		return string.Join( "\n", paragraphs );
	}

	private static object ParseYamlScalar( string value )
	{
		if ( value == "{}" )
			return new Dictionary<string, object>( StringComparer.OrdinalIgnoreCase );
		if ( value == "[]" )
			return new List<object>();
		if ( LooksLikeFlowMap( value ) )
			return ParseFlowMap( value );
		if ( LooksLikeFlowList( value ) )
			return ParseFlowList( value );
		if ( string.Equals( value, "null", StringComparison.OrdinalIgnoreCase ) )
			return null;
		if ( string.Equals( value, "true", StringComparison.OrdinalIgnoreCase ) )
			return true;
		if ( string.Equals( value, "false", StringComparison.OrdinalIgnoreCase ) )
			return false;

		var decoded = DecodeYamlScalar( value );
		if ( decoded != value )
			return decoded;

		if ( long.TryParse( value, out var integer ) )
			return integer;
		if ( double.TryParse( value, out var number ) )
			return number;

		return value;
	}

	private static bool TryParseInlineMapEntry( string value, out Dictionary<string, object> map )
	{
		map = null;
		var colon = FindTopLevelColon( value );
		if ( colon <= 0 )
			return false;

		map = new Dictionary<string, object>( StringComparer.OrdinalIgnoreCase );
		var key = value[..colon].Trim();
		var valueText = value[(colon + 1)..].Trim();
		map[key] = string.IsNullOrEmpty( valueText )
			? new Dictionary<string, object>( StringComparer.OrdinalIgnoreCase )
			: ParseYamlScalar( valueText );
		return true;
	}

	private static bool LooksLikeFlowMap( string value )
	{
		return value.Length >= 2
			&& value[0] == '{'
			&& value[^1] == '}'
			&& FindTopLevelColon( value[1..^1] ) > 0;
	}

	private static bool LooksLikeFlowList( string value )
	{
		return value.Length >= 2 && value[0] == '[' && value[^1] == ']';
	}

	private static Dictionary<string, object> ParseFlowMap( string value )
	{
		var result = new Dictionary<string, object>( StringComparer.OrdinalIgnoreCase );
		var inner = value[1..^1].Trim();
		if ( string.IsNullOrWhiteSpace( inner ) )
			return result;

		foreach ( var entry in SplitTopLevel( inner, ',' ) )
		{
			if ( string.IsNullOrWhiteSpace( entry ) )
				continue;

			var colon = FindTopLevelColon( entry );
			if ( colon <= 0 )
				throw new FormatException( $"Invalid YAML flow mapping entry: {entry}" );

			var key = DecodeYamlScalar( entry[..colon].Trim() );
			var scalar = entry[(colon + 1)..].Trim();
			result[key] = string.IsNullOrEmpty( scalar ) ? null : ParseYamlScalar( scalar );
		}

		return result;
	}

	private static List<object> ParseFlowList( string value )
	{
		var result = new List<object>();
		var inner = value[1..^1].Trim();
		if ( string.IsNullOrWhiteSpace( inner ) )
			return result;

		foreach ( var entry in SplitTopLevel( inner, ',' ) )
			result.Add( ParseYamlScalar( entry.Trim() ) );

		return result;
	}

	private static List<string> SplitTopLevel( string value, char separator )
	{
		var parts = new List<string>();
		var start = 0;
		var depth = 0;
		var quote = '\0';
		var escape = false;

		for ( var i = 0; i < value.Length; i++ )
		{
			var ch = value[i];
			if ( quote != '\0' )
			{
				if ( escape )
				{
					escape = false;
					continue;
				}
				if ( ch == '\\' && quote == '"' )
				{
					escape = true;
					continue;
				}
				if ( ch == quote )
					quote = '\0';
				continue;
			}

			if ( ch is '"' or '\'' )
			{
				quote = ch;
				continue;
			}
			if ( ch is '{' or '[' or '(' )
			{
				depth++;
				continue;
			}
			if ( ch is '}' or ']' or ')' )
			{
				depth = Math.Max( 0, depth - 1 );
				continue;
			}
			if ( ch == separator && depth == 0 )
			{
				parts.Add( value[start..i] );
				start = i + 1;
			}
		}

		parts.Add( value[start..] );
		return parts;
	}

	private static int FindTopLevelColon( string value )
	{
		var depth = 0;
		var quote = '\0';
		var escape = false;

		for ( var i = 0; i < value.Length; i++ )
		{
			var ch = value[i];
			if ( quote != '\0' )
			{
				if ( escape )
				{
					escape = false;
					continue;
				}
				if ( ch == '\\' && quote == '"' )
				{
					escape = true;
					continue;
				}
				if ( ch == quote )
					quote = '\0';
				continue;
			}

			if ( ch is '"' or '\'' )
			{
				quote = ch;
				continue;
			}
			if ( ch is '{' or '[' or '(' )
			{
				depth++;
				continue;
			}
			if ( ch is '}' or ']' or ')' )
			{
				depth = Math.Max( 0, depth - 1 );
				continue;
			}
			if ( ch == ':' && depth == 0 )
				return i;
		}

		return -1;
	}

	private static void SkipBlankLines( string[] lines, ref int index )
	{
		while ( index < lines.Length && IsBlankOrCommentLine( lines[index] ) )
			index++;
	}

	private static bool IsBlankOrCommentLine( string value )
	{
		return string.IsNullOrWhiteSpace( value )
			|| value.TrimStart().StartsWith( "#", StringComparison.Ordinal );
	}

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