using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using HG;
using UnityEngine;

namespace RoR2.ContentManagement;

[CreateAssetMenu(menuName = "RoR2/ContentAssetRefResolver")]
public class ContentAssetRefResolver : ScriptableObject
{
	[Serializable]
	public struct FieldAssignmentInfo
	{
		public UnityEngine.Object targetObject;

		public string fieldPath;

		public ContentAssetPath contentAssetPath;
	}

	[Serializable]
	public struct ContentAssetPath
	{
		public string contentPackIdentifier;

		public string collectionName;

		public string assetName;
	}

	public bool applyOnEnable = true;

	public FieldAssignmentInfo[] fieldAssignmentInfos;

	private static bool contentPacksLoaded;

	private static Queue<ContentAssetRefResolver> pendingResolutions;

	private static ReadOnlyArray<ReadOnlyContentPack> loadedContentPacks;

	[ContextMenu("Run Test")]
	public void Apply()
	{
		for (int i = 0; i < fieldAssignmentInfos.Length; i++)
		{
			ApplyFieldAssignmentInfo(in fieldAssignmentInfos[i]);
		}
	}

	private object FindContentAsset(in ContentAssetPath contentAssetPath)
	{
		for (int i = 0; i < loadedContentPacks.Length; i++)
		{
			if (loadedContentPacks[i].identifier.Equals(contentAssetPath.contentPackIdentifier, StringComparison.Ordinal))
			{
				ReadOnlyContentPack readOnlyContentPack = loadedContentPacks[i];
				readOnlyContentPack.FindAsset(contentAssetPath.collectionName, contentAssetPath.assetName, out var result);
				return result;
			}
		}
		return null;
	}

	static ContentAssetRefResolver()
	{
		contentPacksLoaded = false;
		pendingResolutions = new Queue<ContentAssetRefResolver>();
		loadedContentPacks = Array.Empty<ReadOnlyContentPack>();
		ContentManager.onContentPacksAssigned += OnContentPacksLoaded;
	}

	private static void OnContentPacksLoaded(ReadOnlyArray<ReadOnlyContentPack> newLoadedContentPacks)
	{
		loadedContentPacks = newLoadedContentPacks;
		contentPacksLoaded = true;
		ApplyQueued();
	}

	private static void ApplyQueued()
	{
		while (pendingResolutions.Count > 0)
		{
			ContentAssetRefResolver contentAssetRefResolver = pendingResolutions.Dequeue();
			try
			{
				contentAssetRefResolver.Apply();
			}
			catch (Exception message)
			{
				Debug.LogError(message);
			}
		}
	}

	private void OnEnable()
	{
		if (applyOnEnable)
		{
			if (contentPacksLoaded)
			{
				Apply();
			}
			else
			{
				pendingResolutions.Enqueue(this);
			}
		}
	}

	private bool ApplyFieldAssignmentInfo(in FieldAssignmentInfo fieldAssignmentInfo)
	{
		object obj2 = fieldAssignmentInfo.targetObject;
		string fieldPath = fieldAssignmentInfo.fieldPath;
		UnityEngine.Object valueToAssign = FindContentAsset(in fieldAssignmentInfo.contentAssetPath) as UnityEngine.Object;
		int currentReadPos = 0;
		try
		{
			if (obj2 == null)
			{
				throw new NullReferenceException("targetObject is null");
			}
			ProcessObject(ref obj2);
			return true;
		}
		catch (Exception ex)
		{
			Debug.LogError($"Could not assign {obj2}.{fieldPath}={valueToAssign}: {ex}");
			return false;
		}
		void HandleInnerValue(ref object innerValue)
		{
			if (currentReadPos < fieldPath.Length)
			{
				if (innerValue == null)
				{
					throw new Exception(fieldPath.Substring(0, currentReadPos) + " is null.");
				}
				if (innerValue is UnityEngine.Object)
				{
					throw new Exception("Assignment cannot propagate through UnityEngine.Object.");
				}
				ProcessObject(ref innerValue);
			}
			else
			{
				innerValue = valueToAssign;
			}
		}
		static bool IsIdentifierChar(char c)
		{
			if (!char.IsLetterOrDigit(c))
			{
				return c == '_';
			}
			return true;
		}
		void ProcessObject(ref object obj)
		{
			Type type = obj.GetType();
			if (obj is IList list)
			{
				int index = TakeArrayIndex();
				object innerValue2 = list[index];
				HandleInnerValue(ref innerValue2);
				list[index] = innerValue2;
			}
			else
			{
				string text2 = TakeIdentifier();
				FieldInfo field = type.GetField(text2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (field == null)
				{
					throw new Exception("Field \"" + type.Name + "." + text2 + "\" could not be found.");
				}
				object innerValue3 = field.GetValue(obj);
				HandleInnerValue(ref innerValue3);
				field.SetValue(obj, innerValue3);
			}
		}
		int TakeArrayIndex()
		{
			char c4 = fieldPath[currentReadPos];
			if (c4 != '[')
			{
				throw new FormatException($"Expected char '[' but got '{c4}'.");
			}
			int num5 = currentReadPos + 1;
			currentReadPos = num5;
			int num6 = TakeInt();
			if (num6 < 0)
			{
				throw new FormatException($"Array index {num6} cannot be negative.");
			}
			char c5 = fieldPath[currentReadPos];
			if (c5 != ']')
			{
				throw new FormatException($"Expected char ']' but got '{c5}'.");
			}
			num5 = currentReadPos + 1;
			currentReadPos = num5;
			return num6;
		}
		string TakeIdentifier()
		{
			int num = currentReadPos;
			string text = null;
			char c2 = fieldPath[currentReadPos];
			if (c2 == '.')
			{
				num++;
				int num2 = currentReadPos + 1;
				currentReadPos = num2;
			}
			else if (currentReadPos != 0)
			{
				throw new Exception($"Expected '.' at {num}, but encountered '{c2}'");
			}
			while (currentReadPos < fieldPath.Length && IsIdentifierChar(fieldPath[currentReadPos]))
			{
				int num2 = currentReadPos + 1;
				currentReadPos = num2;
			}
			text = fieldPath.Substring(num, currentReadPos - num);
			if (text == null)
			{
				throw new FormatException($"Expected identifier at {num}, but encountered end of string.");
			}
			if (text.Length == 0)
			{
				throw new FormatException($"Expected identifier at {num}, but encountered no valid characters (a-zA-Z0-9_).");
			}
			if (char.IsDigit(text[0]))
			{
				throw new FormatException($"Expected identifier at {num}, but an identifier cannot begin with a digit. digit={text[0]} substring={text}");
			}
			return text;
		}
		int TakeInt()
		{
			int num3 = currentReadPos;
			if (num3 >= fieldPath.Length)
			{
				throw new FormatException($"Expected integer at {num3}, but encountered end of string.");
			}
			char c3 = fieldPath[currentReadPos];
			if (c3 != '-' && !char.IsDigit(c3))
			{
				throw new FormatException($"Expected integer at {num3}, but an integer cannot begin with character '{c3}'");
			}
			if (c3 == '-')
			{
				int num4 = currentReadPos + 1;
				currentReadPos = num4;
			}
			while (currentReadPos < fieldPath.Length)
			{
				if (!char.IsDigit(fieldPath[currentReadPos]) || currentReadPos == fieldPath.Length - 1)
				{
					fieldPath.Substring(num3, currentReadPos + 1 - num3);
					break;
				}
				int num4 = currentReadPos + 1;
				currentReadPos = num4;
			}
			string obj3 = fieldPath.Substring(num3, currentReadPos - num3) ?? throw new FormatException($"Expected integer at {num3}, but encountered end of string.");
			if (obj3.Length == 0)
			{
				throw new FormatException($"Expected integer at {num3}, but encountered no valid characters (-0-9).");
			}
			return int.Parse(obj3);
		}
	}
}
