using System;
using GlitchyEngine;
using System.IO;
using Bon;
using GlitchyEngine.Content;
using GlitchyEditor.Assets;
using GlitchyEditor.Assets.Importers;

namespace GlitchyEditor;

[BonTarget]
class AssetConfig
{
    [BonIgnore]
	public bool IgnoreFile = false;

	[BonInclude]
	public String AssetLoader ~ delete _;

	[BonInclude]
	public AssetLoaderConfig Config ~ delete _;
	
	[BonInclude]
	public String Importer ~ delete _;
	[BonInclude]
	public AssetImporterConfig ImporterConfig ~ delete _;
	[BonInclude]
	public String Processor ~ delete _;
	[BonInclude]
	public AssetProcessorConfig ProcessorConfig ~ delete _;
	[BonInclude]
	public String Exporter ~ delete _;
	[BonInclude]
	public AssetExporterConfig ExporterConfig ~ delete _;

	[BonInclude]
	public AssetHandle AssetHandle = .Invalid;
}

/// Represents an unprocessed asset as it lies in the asset hierarchy.
class AssetFile
{
	private EditorContentManager _contentManager;

	private String _assetConfigPath ~ delete:append _;
	private AssetNode _assetFile;

	private AssetConfig _assetConfig ~ delete _;

	private Asset _loadedAsset;

	private DateTime _lastAssetEditTime;
	private DateTime _lastConfigEditTime;

	public AssetNode AssetFile => _assetFile;
	public StringView AssetConfigPath => _assetConfigPath;

	public const String ConfigFileExtension = ".ass";

	public AssetConfig AssetConfig => _assetConfig;

	public Asset LoadedAsset => _loadedAsset;

	public EditorContentManager ContentManager => _contentManager;

	public bool UseNewAssetPipeline => _assetConfig?.Importer != null;

	[AllowAppend]
	public this(EditorContentManager contentManager, AssetNode assetNode)
	{
		String configPathBuffer = append String(assetNode.Path.Length + ConfigFileExtension.Length);

		configPathBuffer..Append(assetNode.Path).Append(ConfigFileExtension);
		_assetConfigPath = configPathBuffer;

		_contentManager = contentManager;

		_assetFile = assetNode;

		_lastAssetEditTime = File.GetLastWriteTimeUtc(_assetFile.Path);
	}

	public static AssetFile LoadOrCreateAssetFile(EditorContentManager contentManager, AssetNode assetNode)
	{
		AssetFile assetFile = new AssetFile(contentManager, assetNode);

		assetFile.CheckForReprocessing();

		return assetFile;
	}

	public void CheckForReprocessing()
	{
		LoadOrCreateAssetConfig();

		// TODO: Remove this check once we only use the new processing pipeline
		if (!UseNewAssetPipeline)
			return;

		CachedAsset cacheEntry = _contentManager.AssetCache.GetCacheEntry(_assetConfig.AssetHandle);
		
		_lastAssetEditTime = File.GetLastWriteTimeUtc(_assetFile.Path);
		_lastConfigEditTime = File.GetLastWriteTimeUtc(_assetConfigPath);

		if (cacheEntry == null ||
			cacheEntry.CreationTimestamp < _lastAssetEditTime ||
			cacheEntry.CreationTimestamp < _lastConfigEditTime)
		{
			_contentManager.AssetConverter.QueueForProcessing(this);
		}
	}

	/// Loads the asset config (.ass) file or creates it.
	private void LoadOrCreateAssetConfig()
	{
		if (File.Exists(_assetConfigPath))
		{
			LoadAssetConfig();
		}
		else if (_assetConfig == null)
		{
			CreateDefaultAssetLoader();
		}

		if (File.GetLastWriteTimeUtc(_assetConfigPath) not case .Ok(out _lastConfigEditTime))
		{
			_lastConfigEditTime = .MinValue;
		}
	}

	private void GenerateAssetHandle()
	{
		_assetConfig.AssetHandle = .();
	}

	private void CreateDefaultAssetLoader()
	{
		String fileExtension = Path.GetExtension(_assetFile.Path, .. scope .());

		Log.EngineLogger.Info($"Creating asset config for {_assetFile.Path}");

		_assetConfig = new AssetConfig();
		
		GenerateAssetHandle();

		IAssetImporter assetImporter = _contentManager.GetAssetImporter(fileExtension);
		//IAssetProcessor assetProcessor = ?; //_contentManager.GetAssetProcessor(assetImporter.);
		//IAssetExporter assetExporter = ?; //_contentManager.GetAssetProcessor(assetImporter.);

		// TODO!
		//if (assetPipeline case .Err)
		//	return;

		var assetLoader = _contentManager.GetDefaultAssetLoader(fileExtension);
		
		if (assetLoader != null || assetImporter != null)
		{
			_assetConfig.AssetLoader = new String();
			assetLoader.GetType().GetName(_assetConfig.AssetLoader);

			_assetConfig.Config = assetLoader?.GetDefaultConfig();
			_assetConfig.Config?.[Friend]_changed = true;

			_assetConfig.Importer = new String();
			assetImporter?.GetType()?.GetName(_assetConfig.Importer);
		}
		
		// We might not have a loader, but we want to retain the AssetHandle:
		// e. g. a .txt File can't be reasonably imported, but might still need to be packed into the final
		// application bundle and be read in a script via AssetHandle.
		SaveAssetConfig();
	}

	private void LoadAssetConfig()
	{
		AssetConfig newAssetConfig = new AssetConfig();

		if (Bon.DeserializeFromFile(ref newAssetConfig, _assetConfigPath) case .Err)
		{
			Log.EngineLogger.Error($"Failed to load asset config {_assetConfigPath}");

			delete newAssetConfig;

			return;
		}

		delete _assetConfig;
		_assetConfig = newAssetConfig;
	}

	public void SaveAssetConfigIfChanged()
	{
		if (_assetConfig.Config?.Changed == true ||
			_assetConfig.ImporterConfig?.Changed == true ||
			_assetConfig.ProcessorConfig?.Changed == true ||
			_assetConfig.ExporterConfig?.Changed == true)
		{
			SaveAssetConfig();
		}
	}

	public void SaveAssetConfig()
	{
		//BonEnvironment bonEnv = scope .();

		var oldFlags = gBonEnv.serializeFlags;

		gBonEnv.serializeFlags |= .IncludeDefault | .Verbose;

		Bon.SerializeIntoFile(_assetConfig, _assetConfigPath);

		_assetConfig.Config?.[Friend]_changed = false;
		_assetConfig.ImporterConfig?.[Friend]_changed = false;
		_assetConfig.ProcessorConfig?.[Friend]_changed = false;
		_assetConfig.ExporterConfig?.[Friend]_changed = false;

		gBonEnv.serializeFlags = oldFlags;
	}
}
