Editor/LibraryDetailProxy.cs
using System;
using System.Text.Json;
using System.Threading.Tasks;
using System.IO;
using static Editor.Label;
using System.Diagnostics;
using Editor;

public class LibraryDetailProxy : Widget
{
	Package Package;
	LibraryProject SelectedLibrary;

	Button InstallButton;
	Button UninstallButton;

	ComboBox LibraryList;
	LibraryProject ComboLibrarySelection;
	Label ComboLibraryLabel;

	Button InstallLibraryButton;
	Button UninstallLibraryButton;

	ImportedLibraries importedLibraries = new ImportedLibraries();

	public string libraryStagingFolder => $@"{Project.Current.RootDirectory}\staging\libraryimporter";

	public string projectAssetsFolder => GetAssetsFolder(Project.Current, Package);
	public string projectCodeFolder => GetCodeFolder(Project.Current, Package);
	public string projectEditorFolder => GetEditorFolder(Project.Current, Package);

	public string selectedLibraryAssetsFolder => GetAssetsFolder(ComboLibrarySelection?.Project, SelectedLibrary?.Project?.Package);
	public string selectedLibraryCodeFolder => GetCodeFolder(ComboLibrarySelection?.Project, SelectedLibrary?.Project?.Package);
	public string selectedLibraryEditorFolder => GetEditorFolder(ComboLibrarySelection?.Project, SelectedLibrary?.Project?.Package);

	public List<string> blacklistedFolders = new List<string> { "bin", "obj", "Properties" };
	public List<string> blacklistedFileTypes = new List<string> { ".csproj" };

	public string importingLibraryToLibraryID => $"{SelectedLibrary.Project.Package.Ident}-{ComboLibrarySelection.Project.Package.Ident}";

	public LibraryDetailProxy( Package package ) : base()
	{
		Package = package;

		Layout = Layout.Column();
		Layout.Margin = 8;
		Layout.Spacing = 4;

		importedLibraries = ImportedLibraries.Load();

		_ = FetchAndBuild();
	}

	async Task FetchAndBuild()
	{
		if ( !this.IsValid() ) return;

		Layout.Clear( true );

		if ( !Sandbox.Package.TryParseIdent( Package.FullIdent, out var ident ) )
			return;

		SelectedLibrary = LibrarySystem.All.FirstOrDefault(x => x.Project.Package.Ident == Package.Ident && x.Project.Package.Org == Package.Org);

		bool hasStagedLibrary = importedLibraries.packageIndentToStagedVersion.ContainsKey(Package.Ident);
		string stagedVersion = "";
		if (hasStagedLibrary)
		{
			stagedVersion = importedLibraries.packageIndentToStagedVersion[Package.Ident];
		}
		bool stagedVersionDifferentFromInstalled = stagedVersion != SelectedLibrary.Version.ToString();

		bool hasImportedLibraryToProject = importedLibraries.packageIndentToProjectImportedVersion.ContainsKey(Package.Ident);
		string importedVersionToProject = "";
		if (hasImportedLibraryToProject)
		{
			importedVersionToProject = importedLibraries.packageIndentToProjectImportedVersion[Package.Ident];
		}
		bool importedVersionDifferentFromInstalled = importedVersionToProject != SelectedLibrary.Version.ToString();

		// do we have this package installed? what is the version?
		if ( ident.org != "local" )
		{
			Package = await Package.FetchAsync( $"{ident.org}.{ident.package}", false ) ?? Package;
			if ( !this.IsValid() ) return;
		}

		// Header, todo, icon and title
		Layout.Add( new Label( $"{Package.Title} from {Package.Org.Title}" ) );
		Layout.Add( new Label( $"Installed Version: {SelectedLibrary.Version}" ) );

		if (!hasStagedLibrary)
		{
			Layout.Add(new Label($"Library has not been staged."));

			InstallButton = new Button("Stage Library (move contents into staging folder)", this) { Pressed = () => MoveLibraryFolderToStaging(SelectedLibrary) };
			Layout.Add(InstallButton);
			Layout.AddStretchCell();
			return;
		}

		Layout.Add(new Label(""));
		Layout.Add(new Label("Staging"));
		Layout.Add(new Label($"Staged Version: {stagedVersion}"));
		if (stagedVersionDifferentFromInstalled)
		{
			InstallButton = new Button("Restage Library (move contents into staging folder)", this) { Pressed = () => MoveLibraryFolderToStaging(SelectedLibrary) };
			Layout.Add(InstallButton);
		}

		Layout.Add(new Label(""));
		Layout.Add(new Label($"Project"));

		if (hasImportedLibraryToProject)
		{
			Layout.Add(new Label($"Imported Version: {importedVersionToProject}"));
		}

		if (hasImportedLibraryToProject && importedVersionDifferentFromInstalled)
		{
			InstallButton = new Button("Reimport to Project", this) { Pressed = () => CopyStagedLibraryToProject(SelectedLibrary) };
			Layout.Add(InstallButton);
		}
		else if (!hasImportedLibraryToProject)
		{
			InstallButton = new Button("Import to Project", this) { Pressed = () => CopyStagedLibraryToProject(SelectedLibrary) };
			Layout.Add(InstallButton);
		}

		if (hasImportedLibraryToProject)
		{
			UninstallButton = new Button("Remove from Project", this) { Pressed = () => DeleteImportedLibraryFolderFromProject(SelectedLibrary) };
			Layout.Add(UninstallButton);
		}

		var otherLibraries = LibrarySystem.All.Where(x => x.Project.Package.Ident != "libraryimporter" && x.Project.Package.Ident != SelectedLibrary.Project.Package.Ident);
		ComboLibrarySelection = otherLibraries.FirstOrDefault();

		if (ComboLibrarySelection == null)
		{
			Layout.AddStretchCell();
			return;
		}

		Layout.Add(new Label(""));
		Layout.Add(new Label($"Library"));

		bool hasImportedLibraryToLibrary = importedLibraries.packageIndentToLibraryImportedVersion.ContainsKey(importingLibraryToLibraryID);
		string importedVersionToLibrary = "";
		if (hasImportedLibraryToLibrary)
		{
			importedVersionToLibrary = importedLibraries.packageIndentToLibraryImportedVersion[importingLibraryToLibraryID];
		}
		bool importedVersionDifferentFromInstalledLibrary = importedVersionToLibrary != SelectedLibrary.Version.ToString();

		ComboLibraryLabel = new Label($"");
		Layout.Add(ComboLibraryLabel);

		if (hasImportedLibraryToLibrary)
		{
			ComboLibraryLabel.Text = $"Imported Version: {importedVersionToLibrary}";
		}
		else
		{
			ComboLibraryLabel.Hide();
		}

		LibraryList = new ComboBox(this);
		Layout.Add(LibraryList);

		foreach (var otherLibrary in otherLibraries)
		{
			LibraryList.AddItem($"{otherLibrary.Project.Package.Title}", null, () => OnComboBoxLibrarySelected(otherLibrary), null, ComboLibrarySelection == otherLibrary);
		}

		InstallLibraryButton = new Button("Reimport to Library", this) { Pressed = () => CopyStagedLibraryToLibrary(SelectedLibrary, ComboLibrarySelection) };
		Layout.Add(InstallLibraryButton);

		if (!hasImportedLibraryToLibrary)
		{
			InstallLibraryButton.Text = "Import to Library";
		}

		if (hasImportedLibraryToLibrary && !importedVersionDifferentFromInstalledLibrary)
		{
			InstallLibraryButton.Hide();
		}

		UninstallLibraryButton = new Button("Remove from Library", this) { Pressed = () => DeleteImportedLibraryFolderFromLibrary() };
		Layout.Add(UninstallLibraryButton);

		if (!hasImportedLibraryToLibrary)
		{
			UninstallLibraryButton.Hide();
		}

		// information about this
		Layout.AddStretchCell();
	}

	void OnComboBoxLibrarySelected(LibraryProject libraryProject)
	{
		ComboLibrarySelection = libraryProject;
		// TODO: Check the state of export

		// Duplicated logic, but fuck it I'm bored of this
		bool hasImportedLibraryToLibrary = importedLibraries.packageIndentToLibraryImportedVersion.ContainsKey(importingLibraryToLibraryID);
		string importedVersionToLibrary = "";
		if (hasImportedLibraryToLibrary)
		{
			importedVersionToLibrary = importedLibraries.packageIndentToLibraryImportedVersion[importingLibraryToLibraryID];
		}
		bool importedVersionDifferentFromInstalledLibrary = importedVersionToLibrary != SelectedLibrary.Version.ToString();


		if (hasImportedLibraryToLibrary)
		{
			if (importedVersionDifferentFromInstalledLibrary)
			{
				InstallLibraryButton.Text = "Reimport to Library";
				InstallLibraryButton.Show();
			}
			else
			{
				InstallLibraryButton.Hide();
			}

			UninstallLibraryButton.Show();
		}
		else
		{
			InstallLibraryButton.Text = "Import to Library";
			InstallLibraryButton.Show();
			UninstallLibraryButton.Hide();
		}
	}

	void CopyStagedLibraryToProject(LibraryProject libraryProject)
	{
		if (libraryProject?.Project?.Package == null)
		{
			Log.Error($"CopyStagedLibraryToProject() error null libraryProject!!!");
			return;
		}

		string libraryRootFolder = @$"{libraryStagingFolder}\{libraryProject.Project.Package.Ident}";

		string libraryCodeFolder = @$"{libraryRootFolder}\code";
		string libraryEditorFolder = @$"{libraryRootFolder}\Editor";
		string libraryAssetsFolder = @$"{libraryRootFolder}\Assets";

		CopyFiles(libraryCodeFolder, projectCodeFolder);
		CopyFiles(libraryEditorFolder, projectEditorFolder);
		CopyFiles(libraryCodeFolder, projectAssetsFolder);

		importedLibraries.packageIndentToProjectImportedVersion[Package.Ident] = libraryProject.Version.ToString();
		importedLibraries.Save();
		_ = FetchAndBuild();
	}

	void CopyStagedLibraryToLibrary(LibraryProject libraryProject, LibraryProject targetLibraryProject)
	{
		if (libraryProject?.Project?.Package == null || targetLibraryProject?.Project?.Package == null)
		{
			Log.Error($"CopyStagedLibraryToLibrary() error null libraryProject or targetLibraryProject!!!");
			return;
		}

		string libraryRootFolder = @$"{libraryStagingFolder}\{libraryProject.Project.Package.Ident}";

		string libraryCodeFolder = @$"{libraryRootFolder}\code";
		string libraryEditorFolder = @$"{libraryRootFolder}\Editor";
		string libraryAssetsFolder = @$"{libraryRootFolder}\Assets";

		CopyFiles(libraryCodeFolder, selectedLibraryCodeFolder);
		CopyFiles(libraryEditorFolder, selectedLibraryCodeFolder);
		CopyFiles(libraryCodeFolder, selectedLibraryAssetsFolder);

		importedLibraries.packageIndentToLibraryImportedVersion[importingLibraryToLibraryID] = libraryProject.Version.ToString();
		importedLibraries.Save();
		_ = FetchAndBuild();
	}

	void MoveLibraryFolderToStaging(LibraryProject libraryProject)
	{
		if (libraryProject?.Project?.Package == null)
		{
			Log.Error($"MoveLibraryFolderToStaging() error null libraryProject!!!");
			return;
		}

		string stageFolderRoot = @$"{libraryStagingFolder}\{libraryProject.Project.Package.Ident}\";
		string libraryRootFolder = $"{libraryProject.Project.RootDirectory.FullName}";

		string libraryCodeFolder = @$"{libraryRootFolder}\code";
		string libraryEditorFolder = @$"{libraryRootFolder}\Editor";
		string libraryAssetsFolder = @$"{libraryRootFolder}\Assets";
		string libraryUnitTestFolder = @$"{libraryRootFolder}\UnitTests";

		string targetCodeFolder = @$"{libraryStagingFolder}\{libraryProject.Project.Package.Ident}\code";
		string targetEditorFolder = @$"{libraryStagingFolder}\{libraryProject.Project.Package.Ident}\Editor";
		string targetAssetsFolder = @$"{libraryStagingFolder}\{libraryProject.Project.Package.Ident}\Assets";

		if (Directory.Exists(stageFolderRoot))
		{
			Directory.Delete(stageFolderRoot, true);
		}

		CopyFiles(libraryCodeFolder, targetCodeFolder, true);
		CopyFiles(libraryEditorFolder, targetEditorFolder, true);
		CopyFiles(libraryAssetsFolder, targetAssetsFolder, true);

		try
		{
			// I don't care about unit tests, so fuck em
			if (Directory.Exists(libraryUnitTestFolder))
			{
				Directory.Delete(libraryUnitTestFolder, true);
			}
		}
		catch
		{
			Log.Error("Could not delete libraryUnitTestFolder, you'll have to do it manually.");
		}

		importedLibraries.packageIndentToStagedVersion[libraryProject.Project.Package.Ident] = libraryProject.Version.ToString();
		importedLibraries.Save();
		_ = FetchAndBuild();
	}

	void CopyFiles(string sourceFolder, string targetFolder, bool moveNotCopy = false)
	{
		var relativePaths = GetRelativeFilePaths(sourceFolder, blacklistedFolders, blacklistedFileTypes);

		//Log.Info($"CopyFiles() relativePaths: {relativePaths.Count()}");

		foreach (var path in relativePaths)
		{
			var sourcePath = @$"{sourceFolder}\{path}";
			var targetPath = @$"{targetFolder}\{path}";

			var targetDirectory = Path.GetDirectoryName(targetPath);
			if (!Directory.Exists(targetDirectory))
			{
				Directory.CreateDirectory(targetDirectory);
			}

			if (moveNotCopy)
			{
				File.Move(sourcePath, targetPath, true);
			}
			else
			{
				File.Copy(sourcePath, targetPath, true);
			}

			//Log.Info($"{(moveNotCopy ? "Move" : "Copy")} sourcePath: {sourcePath} to targetPath: {targetPath}");
		}
	}

	void DeleteImportedLibraryFolderFromProject(LibraryProject libraryProject)
	{
		if (Directory.Exists(projectCodeFolder))
		{
			Directory.Delete(projectCodeFolder, true);
		}
		if (Directory.Exists(projectEditorFolder))
		{
			Directory.Delete(projectEditorFolder, true);
		}
		if (Directory.Exists(projectAssetsFolder))
		{
			Directory.Delete(projectAssetsFolder, true);
		}

		string packageIdent = libraryProject.Project.Package.Ident;
		if (importedLibraries.packageIndentToProjectImportedVersion.ContainsKey(packageIdent))
		{
			importedLibraries.packageIndentToProjectImportedVersion.Remove(packageIdent);
			importedLibraries.Save();
		}
		_ = FetchAndBuild();
	}

	void DeleteImportedLibraryFolderFromLibrary()
	{
		if (Directory.Exists(selectedLibraryCodeFolder))
		{
			Directory.Delete(selectedLibraryCodeFolder, true);
		}
		if (Directory.Exists(selectedLibraryEditorFolder))
		{
			Directory.Delete(selectedLibraryEditorFolder, true);
		}
		if (Directory.Exists(selectedLibraryAssetsFolder))
		{
			Directory.Delete(selectedLibraryAssetsFolder, true);
		}

		if (importedLibraries.packageIndentToLibraryImportedVersion.ContainsKey(importingLibraryToLibraryID))
		{
			importedLibraries.packageIndentToLibraryImportedVersion.Remove(importingLibraryToLibraryID);
			importedLibraries.Save();
		}
		_ = FetchAndBuild();
	}

	void DeleteImportedLibraryFolderInLibrary(string packageIndent)
	{
		string folder = @$"{selectedLibraryCodeFolder}";
		string folderEditor = @$"{selectedLibraryEditorFolder}";

		if (Directory.Exists(folder))
		{
			Directory.Delete(folder, true);
		}
		if (Directory.Exists(folderEditor))
		{
			Directory.Delete(folderEditor, true);
		}

		string id = $"{ComboLibrarySelection.Project.Package.Ident}-{Package.Ident}";
		if (importedLibraries.packageIndentToLibraryImportedVersion.ContainsKey(id))
		{
			importedLibraries.packageIndentToLibraryImportedVersion.Remove(id);
			importedLibraries.Save();
		}
	}

	static IEnumerable<string> GetRelativeFilePaths(string rootDirectory, List<string> blacklistedFolders, List<string> blacklistedFileTypes)
	{
		var filePaths = new List<string>();

		if (!Directory.Exists(rootDirectory))
		{
			return filePaths;
		}

		foreach (var filePath in Directory.EnumerateFiles(rootDirectory, "*.*", SearchOption.AllDirectories))
		{
			// Check if the file is in a blacklisted folder
			var directoryName = Path.GetDirectoryName(filePath);
			if (blacklistedFolders.Any(folder => directoryName.Contains(folder, StringComparison.OrdinalIgnoreCase)))
			{
				continue;
			}

			// Check if the file has a blacklisted extension
			var fileExtension = Path.GetExtension(filePath);
			if (blacklistedFileTypes.Contains(fileExtension, StringComparer.OrdinalIgnoreCase))
			{
				continue;
			}

			// Get the relative path
			var relativePath = Path.GetRelativePath(rootDirectory, filePath);
			filePaths.Add(relativePath);
		}

		return filePaths;
	}

	public string GetAssetsFolder(Project inProject, Package inPackage)
	{
		if (inProject == null || inPackage == null)
			return null;

		string folder = @$"{inProject.GetAssetsPath()}\libraries\{inPackage.Ident}";
		return folder;
	}

	public string GetCodeFolder(Project inProject, Package inPackage)
	{
		if (inProject == null || inPackage == null)
			return null;

		string folder = @$"{inProject.GetCodePath()}\libraries\{inPackage.Ident}";
		return folder;
	}

	public string GetEditorFolder(Project inProject, Package inPackage)
	{
		if (inProject == null || inPackage == null)
			return null;

		string folder = @$"{inProject.GetRootPath()}\Editor\libraries\{inPackage.Ident}";
		return folder;
	}

	protected override Vector2 SizeHint()
	{
		return new Vector2( 300, 100 );
	}
}

public class ImportedLibraries
{
	public Dictionary<string, string> packageIndentToProjectImportedVersion { get; set; } = new Dictionary<string, string>();

	public Dictionary<string, string> packageIndentToLibraryImportedVersion { get; set; } = new Dictionary<string, string>();

	public Dictionary<string, string> packageIndentToStagedVersion { get; set; } = new Dictionary<string, string>();

	static string libraryStagingFolder => $@"{Project.Current.RootDirectory}\staging\libraryimporter";
	static string saveFilePath => @$"{libraryStagingFolder}\imported_libraries.json";

	public void Save()
	{		
		var json = JsonSerializer.Serialize(this, GetSerializerOptions());
		System.IO.File.WriteAllText(saveFilePath, json);
	}

	public static ImportedLibraries Load()
	{
		if (!System.IO.File.Exists(saveFilePath))
		{
			return new ImportedLibraries();
		}
		var fileContents = System.IO.File.ReadAllText(saveFilePath);
		var inst = JsonSerializer.Deserialize<ImportedLibraries>(fileContents, GetSerializerOptions());
		return inst;
	}

	public static JsonSerializerOptions GetSerializerOptions()
	{
		var serializerOptions = new JsonSerializerOptions();
		serializerOptions.WriteIndented = true;
		return serializerOptions;
	}
}