editor/AdvancedMappingTools/UI/SettingsPresetService.cs
using System;
using System.IO;

namespace AdvancedMappingTools;
/// <summary>
/// Named generator presets on disk. They use the same JSON the addon stores on the root
/// part, so a preset can also be copied between machines as a plain file.
/// </summary>
public static class SettingsPresetService
{
	public static string Root =>
		Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.MyDocuments ), "DavelixConstruct", "Presets" );

	public static string Folder( string generatorId ) =>
		Path.Combine( Root, Clean( generatorId ) );

	public static List<string> List( string generatorId )
	{
		var folder = Folder( generatorId );

		if ( !Directory.Exists( folder ) ) return [];

		return [.. Directory.GetFiles( folder, "*.json" )
			.Select( Path.GetFileNameWithoutExtension )
			.OrderBy( x => x )];
	}

	public static string NextName( string generatorId )
	{
		var used = List( generatorId ).ToHashSet( StringComparer.OrdinalIgnoreCase );

		for ( var i = 1; i < 1000; i++ )
		{
			var name = $"preset-{i:000}";

			if ( !used.Contains( name ) ) return name;
		}

		return $"preset-{DateTime.Now:HHmmss}";
	}

	public static void Save( string generatorId, string name, string json )
	{
		var folder = Folder( generatorId );
		Directory.CreateDirectory( folder );
		StoreSafety.Write( Path.Combine( folder, Clean( name ) + ".json" ), json );
	}

	public static string Read( string generatorId, string name )
	{
		var file = Path.Combine( Folder( generatorId ), Clean( name ) + ".json" );
		try
		{
			return File.Exists( file ) ? File.ReadAllText( file ) : null;
		}
		catch ( Exception ex )when ( ex is IOException or UnauthorizedAccessException )
		{
			StoreSafety.Warn( file, ex.Message );
			return null;
		}
	}

	public static GeneratorSettings Load( IPartGenerator generator, string name )
	{
		var json = Read( generator.Id, name );
		try
		{
			return string.IsNullOrEmpty( json ) ? null : GeneratorRegistry.Load( generator, json );
		}
		catch ( Exception ex )when ( ex is System.Text.Json.JsonException or ArgumentException )
		{
			StoreSafety.Warn( Path.Combine( Folder( generator.Id ), Clean( name ) + ".json" ), ex.Message );
			return null;
		}
	}

	public static void Delete( string generatorId, string name )
	{
		var file = Path.Combine( Folder( generatorId ), Clean( name ) + ".json" );

		if ( File.Exists( file ) ) File.Delete( file );
	}

	static string Clean( string name )
	{
		if ( string.IsNullOrWhiteSpace( name ) ) return "preset";

		var invalid = Path.GetInvalidFileNameChars();

		return new string( name.Select( c => invalid.Contains( c ) ? '_' : c ).ToArray() ).Trim();
	}
}