using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

namespace Plugins.Animate_UI_Materials.Editor
{
  using PropertyType = ShaderUtil.ShaderPropertyType;

  [CustomEditor(typeof(GraphicPropertyOverride), true)]
  [CanEditMultipleObjects]
  public class GraphicPropertyOverrideEditor : UnityEditor.Editor
  {
    SerializedProperty _propertyName;
    SerializedProperty _propertyValue;

    void OnEnable()
    {
      // Get the serializedProperty of the shader property name
      _propertyName = serializedObject.FindProperty("propertyName");
      // Get the serializedProperty of the shader property propertyValue
      _propertyValue = serializedObject.FindProperty("propertyValue");
    }

    public override void OnInspectorGUI()
    {
      serializedObject.Update();

      // Draw the script header
      using (new EditorGUI.DisabledScope(true))
        EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Script"));

      // Start change check
      using EditorGUI.ChangeCheckScope scope = new();

      // If single target (not multi-editing)
      if (targets.Length == 1)
      {
        // Draw a warning if the material cannot be found
        if (!GetTargetMaterial())
          EditorGUILayout.HelpBox(
            "The parent doesn't contain a GraphicMaterialOverride. Add one to continue",
            MessageType.Error);

        string[] properties = GetPropertyNames().ToArray();
        // Draw a dropdown list of float properties in the material
        EditorGUI.BeginChangeCheck();

        int currentIndex = GetPropertyIndexDropdown();
        int index = EditorGUILayout.Popup("Property Name", currentIndex, properties);
        if (EditorGUI.EndChangeCheck()) _propertyName.stringValue = properties[index];
      }
      else
      {
        EditorGUILayout.LabelField("Cannot multi-edit property name");
      }
      
      DrawPropertiesExcluding(serializedObject, "m_Script", "propertyName", "propertyValue");
      
      DrawValueProperty(_propertyValue);

      // If change happened, apply modified properties
      if (scope.changed) serializedObject.ApplyModifiedProperties();
    }

    /// <summary>
    /// Overridable method to draw the value property
    /// Overriden by GraphicPropertyOverrideRange to display ranges
    /// </summary>
    protected virtual void DrawValueProperty(SerializedProperty property)
    {
      EditorGUILayout.PropertyField(property);
    }

    /// <summary>
    /// Try to get the material of the associated Graphic object
    /// </summary>
    /// <returns>The material being overriden or null</returns>
    protected Material GetTargetMaterial()
    {
      GraphicPropertyOverride animated = (GraphicPropertyOverride)target;

      return animated.transform.parent.TryGetComponent(out Graphic graphic) ? graphic.material : null;
    }

    /// <summary>
    /// Get the property type from the target object
    /// </summary>
    /// <returns></returns>
    protected PropertyType GetPropertyType()
    {
      GraphicPropertyOverride animated = (GraphicPropertyOverride)target;

      return animated switch
      {
        GraphicPropertyOverrideColor          => PropertyType.Color,
        GraphicPropertyOverrideFloat          => PropertyType.Float,
        GraphicPropertyOverrideVector         => PropertyType.Vector,
        GraphicPropertyOverrideInt            => PropertyType.Int,
        GraphicPropertyOverrideRange          => PropertyType.Range,
        GraphicPropertyOverrideTexture        => PropertyType.TexEnv,
        GraphicPropertyOverrideScaleAndOffset => PropertyType.TexEnv,
        _ when target != null                 => throw new Exception($"Unknown type {target.GetType()}"),
        _                                     => throw new Exception($"Target is null"),
      };
    }

    /// <summary>
    /// Get the index inside the filtered dropdown
    /// </summary>
    /// <returns>The index, or -1 in case of invalid property</returns>
    int GetPropertyIndexDropdown()
    {
      string propertyName = _propertyName.stringValue;
      return GetPropertyNames()
        .FindIndex(dropdownOptionName => dropdownOptionName == propertyName);
    }

    /// <summary>
    /// Get the index of the property in the shade
    /// </summary>
    /// <returns>The index of the property or -1 in vase of invalid property</returns>
    protected int GetPropertyIndex()
    {
      Material material = GetTargetMaterial();
      string propertyName = _propertyName.stringValue;
      return ShaderPropertyInfo.GetMaterialProperties(material)
                               .Find(p => p.name == propertyName)
                               ?.index ??
             -1;
    }

    /// <summary>
    /// Get all matching properties from the shader
    /// </summary>
    /// <returns></returns>
    List<string> GetPropertyNames()
    {
      Material material = GetTargetMaterial();
      PropertyType type = GetPropertyType();

      return ShaderPropertyInfo.GetMaterialProperties(material)
                               .Where(p => p.type == type)
                               .Select(p => p.name)
                               .ToList();
    }
    
    /// <summary>
    ///   Override the reset context menu to implement the reset function
    ///   Needed instead of "MonoBehavior.Reset" on GraphicMaterialOverride because Reset is called in other contexts
    /// </summary>
    [MenuItem("CONTEXT/GraphicPropertyOverride/Reset")]
    static void ResetPropertyValue(MenuCommand b)
    {
      if (b.context is not GraphicPropertyOverride propertyOverride) return;

      Undo.RecordObject(propertyOverride, "Reset material override");
      propertyOverride.ResetPropertyToDefault(); 
      PrefabUtility.RecordPrefabInstancePropertyModifications(propertyOverride);
    }
  }
}