Editor/sandmod.libraryplus/LibrarySystemPlus.cs
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using Editor;
using Sandbox;
using Sandbox.Diagnostics;

namespace LibraryPlus;

public static class LibrarySystemPlus
{
	internal static readonly Logger Log = new("LibraryPlus");

	private static readonly LibraryProject Self = LibrarySystem.All.First( library =>
		library.Project.Package.IsLibraryPlus() );

	internal static readonly DirectoryInfo Root = Self.Project.RootDirectory;

	private static readonly HashSet<PlusLibrary> PlusCache = [];
	private static readonly HashSet<ProjectLibrary> ProjectCache = [];

	static LibrarySystemPlus()
	{
		foreach ( var library in LibrarySystem.All )
		{
			ProjectCache.Add( new ProjectLibrary( library ) );
		}

		var settingsPath = Path.Combine( Root.FullName, "Settings" );
		if ( Directory.Exists( settingsPath ) )
		{
			foreach ( var filePath in Directory.EnumerateFiles( settingsPath, "*.sbproj",
				         SearchOption.AllDirectories ) )
			{
				var configString = File.ReadAllText( filePath );
				var config = JsonSerializer.Deserialize<LibraryConfig>( configString );
				var package = GetPackage( config );
				PlusCache.Add( new PlusLibrary( package ) );
			}
		}
	}

	public static IEnumerable<Library> All
	{
		get
		{
			var uncachedHash = LibrarySystem.All.GetHashCode();
			var cachedHash = ProjectCache.Select( project => project.Project ).GetHashCode();
			if ( uncachedHash != cachedHash )
			{
				ProjectCache.RemoveWhere( cached => LibrarySystem.All.All( uncached => uncached != cached.Project ) );
				foreach ( var library in LibrarySystem.All.Where( uncached =>
					         ProjectCache.All( cached => cached.Project != uncached ) ) )
				{
					ProjectCache.Add( new ProjectLibrary( library ) );
				}
			}

			return PlusCache.Cast<Library>().Concat( ProjectCache );
		}
	}

	public static IEnumerable<PlusLibrary> AllPlus =>
		All.Where( l => l is PlusLibrary ).Cast<PlusLibrary>();

	public static IEnumerable<ProjectLibrary> AllProject =>
		All.Where( l => l is ProjectLibrary ).Cast<ProjectLibrary>();

	public static void Install( string ident, long versionId )
	{
		LibrarySystem.Install( ident, versionId );
	}

	public static void Remove( Library library )
	{
		if ( library is ProjectLibrary projectLibrary )
		{
			projectLibrary.Project.RemoveAndDelete();
			ProjectCache.Remove( projectLibrary );
		}
		else if ( library is PlusLibrary plusLibrary )
		{
			PlusCache.Remove( plusLibrary );
		}

		library.FileSystem.Delete();
	}

	public static void AddReference( Library library, Reference reference )
	{
		library.Config.LibraryReferences.Add( reference.Ident, reference );
		library.SaveConfig();
	}

	public static void RemoveReference( Library library, string reference )
	{
		library.Config.LibraryReferences.Remove( reference );
		library.SaveConfig();
	}

	internal static void Convert( ProjectLibrary library )
	{
		if ( library.IsLibraryPlus() )
		{
			Log.Warning(
				$"{library.Package.ReferenceIdent()} can't be converted to plus library as this would break the library" );
			return;
		}

		// Move to temp library first to update reference files
		// This is done to prevent compiler errors while updating the files
		var tempFileSystem = new TempFileSystem( library.Package.ReferenceIdent() );
		library.FileSystem.Move( tempFileSystem );
		new TempLibrary( library.Package, true ).UpdateReferenceFiles();

		var targetFileSystem = new PlusFileSystem( library.Package.ReferenceIdent() );
		tempFileSystem.Move( targetFileSystem );
		library.Project.RemoveAndDelete();
		PlusCache.Add( new PlusLibrary( library.Package ) );
		ProjectCache.Remove( library );
		UpdatePreprocessors();
	}

	internal static void Convert( PlusLibrary library )
	{
		// Move to temp library first to update reference files
		// This is done to prevent compiler errors while updating the files
		var tempFileSystem = new TempFileSystem( library.Package.ReferenceIdent() );
		library.FileSystem.Move( tempFileSystem );
		new TempLibrary( library.Package, false ).UpdateReferenceFiles();

		var targetFileSystem = new ProjectFileSystem( library.Package.ReferenceIdent() );
		tempFileSystem.Move( targetFileSystem );
		LibrarySystem.Add( library.Package.ReferenceIdent(), new CancellationToken( false ) ).Wait();
		var projectLibrary =
			LibrarySystem.All.First( l => l.Project.Package.ReferenceIdent() == library.Package.ReferenceIdent() );
		ProjectCache.Add( new ProjectLibrary( projectLibrary ) );
		PlusCache.Remove( library );
		UpdatePreprocessors();
	}

	[Event( "hotloaded", Priority = -1 )]
	private static void UpdatePreprocessors()
	{
		foreach ( var library in All )
		{
			library.UpdateReferenceFiles();
		}
	}

	internal static void WriteFileIfNeeded( string path, string contents )
	{
		if ( File.Exists( path ) && File.ReadAllText( path ) == contents )
		{
			return;
		}

		File.WriteAllText( path, contents );
	}

	private static Package GetPackage( LibraryConfig config )
	{
		/*var fetchedPackage = Package.FetchAsync( Package.FormatIdent( config.Org, config.Ident ), true, true ).Result;
		if ( fetchedPackage != null )
		{
			return fetchedPackage;
		}*/

		return new Package
		{
			TypeName = config.Type,
			Ident = config.Ident,
			Org = new Package.Organization
			{
				Ident = config.Org, Title = config.Org, Thumb = "/ui/mainmenu/missing_package.png"
			},
			Title = config.Title,
			Thumb = "/ui/mainmenu/missing_package.png",
			PackageReferences = config.PackageReferences?.ToArray() ?? [],
			EditorReferences = config.EditorReferences?.ToArray() ?? []
		};
	}
}