using Sandbox.Engine;
using System.Diagnostics;

namespace Sandbox;

internal static class ResourceLoader
{


	internal static void LoadAllGameResource( BaseFileSystem fileSystem )
	{
		var sw = Stopwatch.StartNew();
		var types = Game.TypeLibrary.GetAttributes<AssetTypeAttribute>().DistinctBy( x => x.Extension )
			.ToDictionary( x => $".{x.Extension}_c", x => x, StringComparer.OrdinalIgnoreCase );

		var allFiles = fileSystem.FindFile( "/", "*", true ).ToArray();

		var allResources = new List<GameResource>();

		Clear();
		foreach ( var file in allFiles )
		{
			var extension = System.IO.Path.GetExtension( file );

			if ( !types.TryGetValue( extension, out var type ) )
				continue;

			try
			{
				var se = Game.Resources.LoadGameResource( type, file, fileSystem, true );
				if ( se != null ) allResources.Add( se );
			}
			catch ( Exception ex )
			{
				Log.Warning( ex, $"Exception when trying to load {file}" );
			}
		}

		//
		// When we're loading a bunch of GameResource we defer their PostLoad until everything is loaded.
		// Everyone is gonna wanna do Resource.Get<>() within their PostLoad and not care about load order.
		// This keeps things intuitive for end users.
		//
		foreach ( var resource in allResources )
		{
			resource.PostLoadInternal();
		}

		foreach ( var type in types )
		{
			AddWatcherForType( type.Value );
		}

		// TODO: Check for edited but not saved OR recompiled assets and load in their values on server/client
		// like editing an asset while the gamemode is running would?
	}





	static Dictionary<string, FileWatch> Watchers = new();

	static void AddWatcherForType( AssetTypeAttribute type )
	{
		if ( Watchers.TryGetValue( type.Name, out var watcher ) )
		{
			watcher.Dispose();
		}

		watcher = EngineFileSystem.Mounted.Watch( $"*.{type.Extension}_c" );
		watcher.OnChanges += ( w ) => OnAssetFilesChanged( w, type );

		Watchers[type.Name] = watcher;
	}

	private static void OnAssetFilesChanged( FileWatch watch, AssetTypeAttribute type )
	{
		foreach ( var change in watch.Changes )
		{
			OnAssetFileChanged( change, type );
		}
	}

	static void OnAssetFileChanged( string file, AssetTypeAttribute type )
	{
		var fs = EngineFileSystem.Mounted;

		if ( !file.EndsWith( "_c" ) )
			file += "_c";

		//
		// Asset doesn't exist, maybe just added?
		//
		if ( !ResourceLibrary.TryGet<GameResource>( file.Trim( '/' ), out var asset ) || asset.IsPromise )
		{
			// file wasn't found, so I don't know what was happening.
			if ( !fs.FileExists( file ) )
				return;

			Log.Info( $"Detected Added File {file}" );
			Game.Resources.LoadGameResource( type, file, fs );
			return;
		}

		//
		// File was removed, tell the asset system it died
		//
		if ( !fs.FileExists( file ) )
		{
			Log.Info( $"Detected Asset File Deleted {file}" );

			// Removes from ResourceLibrary
			asset.DestroyInternal();
			return;
		}

		Span<byte> data = fs.ReadAllBytes( file );

		if ( data.Length <= 3 )
		{
			Log.Warning( $"Couldn't load json data from {file}" );
			return;
		}

		bool hasCompiledChanges = asset.TryLoadFromData( data );
		bool externalChanges = false;
		if ( hasCompiledChanges )
		{
			// check for source file changes
			if ( fs.FileExists( asset.ResourcePath ) )
			{
				var jsonBlob = fs.ReadAllText( asset.ResourcePath );
				if ( string.IsNullOrEmpty( jsonBlob ) ) return;

				var sourceHash = jsonBlob.FastHash();
				if ( sourceHash != asset.LastSavedSourceHash && asset.LastSavedSourceHash != 0 )
				{
					IToolsDll.Current?.RunEvent<ResourceLibrary.IEventListener>( i => i.OnExternalChanges( asset ) );
					externalChanges = true;
				}
			}
		}

		asset.PostReloadInternal();

		if ( externalChanges )
		{
			IToolsDll.Current?.RunEvent<ResourceLibrary.IEventListener>( i => i.OnExternalChangesPostLoad( asset ) );
		}
	}

	internal static void Clear()
	{
		// Dispose of watchers too
		foreach ( var watcher in Watchers ) watcher.Value.Dispose();
		Watchers.Clear();
	}
}
