Editor/ShaderGraphPlus/Blackboard/Parameters/Core/BlackboardParameter.cs
namespace ShaderGraphPlus;

public record struct BlackboardConfig( string Name, Color Color );

public interface IBlackboardParameter
{
	Guid Identifier { get; }

	DisplayInfo DisplayInfo { get; }

	string Name { get; set; }

	public object GetValue();

	public void SetValue( object value );

	public IGraphNode ToNode();
}

public interface IBlackboardMaterialParameter : IBlackboardParameter
{
	bool IsAttribute { get; set; }
	public IParameterUI GetParameterUI();
}

public interface IRangedBlackboardMaterialParameter : IBlackboardParameter
{
	public object GetRangeMin();
	public object GetRangeMax();
}

public interface IBlackboardSubgraphParameter : IBlackboardParameter
{
	string Description { get; set; }
	int PortOrder { get; set; }

	abstract SubgraphPortType PortType { get; }
}

public interface IBlackboardSubgraphInputParameter : IBlackboardSubgraphParameter
{
	/// <summary>
	/// Whether this input is required (must have a connection in order to compile)
	/// </summary>
	bool IsRequired { get; set; }
}

public interface IBlackboardSubgraphOutputParameter : IBlackboardSubgraphParameter
{
	bool IsValid { get; }

	SubgraphOutputPreviewType Preview { get; set; }

	bool CannotPreviewOutputType { get; }
}

public abstract class BlackboardParameter : IBlackboardParameter, IValid
{
	[Hide, Browsable( false )]
	public Guid Identifier { get; set; }

	[JsonIgnore, Hide]
	public INodeGraph _graph;
	[Browsable( false )]
	[JsonIgnore, Hide]
	public INodeGraph Graph
	{
		get => _graph;
		set
		{
			_graph = value;
		}
	}

	[JsonIgnore, Hide, Browsable( false )]
	public DisplayInfo DisplayInfo { get; }

	[Hide, JsonIgnore, Browsable( false )]
	public virtual bool IsValid => true;

	public virtual string Name { get; set; }

	public BlackboardParameter()
	{
		DisplayInfo = DisplayInfo.For( this );
		NewIdentifier();

		Name = "";
	}

	public Guid NewIdentifier()
	{
		Identifier = Guid.NewGuid();
		return Identifier;
	}

	public abstract object GetValue();

	public abstract void SetValue( object value );

	/// <summary>
	/// Check parameter for any issues.
	/// </summary>
	/// <param name="issues">Any issues that are found.</param>
	/// <returns>False when check has failed otherwise returns true when check has passed.</returns>
	public virtual bool CheckParameter( out List<string> issues )
	{
		var graph = _graph as ShaderGraphPlus;
		issues = new List<string>();

		if ( string.IsNullOrWhiteSpace( Name ) )
		{
			issues.Add( $"Parameter with identifier \"{Identifier}\" must have name!" );

			return false;
		}

		var cleanedName = Name.Replace( " ", "" );

		foreach ( var parameter in graph.Parameters )
		{
			if ( parameter == this )
				continue;

			var cleanedComparsionName = parameter.Name.Replace( " ", "" );

			// Check for exact matches and matches with the spaces removed.
			if ( parameter.Name == Name || cleanedComparsionName == cleanedName )
			{
				issues.Add( $"Parameter with name \"{Name}\" already exists!" );

				return false;
			}
		}

		return true;
	}

	public abstract IGraphNode ToNode();

	public static IEnumerable<IBlackboardParameterType> GetRelevantParameters( Dictionary<string, IBlackboardParameterType> availableParameters, bool isSubgraph )
	{
		return availableParameters.Values.Where( x =>
		{
			if ( x is ClassBlackboardParameterType classParameterType )
			{
				var targetType = classParameterType.Type.TargetType;

				// Only show material parameters when not in a subgraph
				if ( isSubgraph && targetType == typeof( BoolParameter ) ) return false;
				if ( isSubgraph && targetType == typeof( IntParameter ) ) return false;
				if ( isSubgraph && targetType == typeof( FloatParameter ) ) return false;
				if ( isSubgraph && targetType == typeof( Float2Parameter ) ) return false;
				if ( isSubgraph && targetType == typeof( Float3Parameter ) ) return false;
				if ( isSubgraph && targetType == typeof( Float4Parameter ) ) return false;
				if ( isSubgraph && targetType == typeof( ColorParameter ) ) return false;
				if ( isSubgraph && targetType == typeof( Texture2DParameter ) ) return false;
				if ( isSubgraph && targetType == typeof( TextureCubeParameter ) ) return false;
				if ( isSubgraph && targetType == typeof( ShaderFeatureBooleanParameter ) ) return false;
				if ( isSubgraph && targetType == typeof( ShaderFeatureEnumParameter ) ) return false;
				if ( isSubgraph && targetType == typeof( SamplerStateParameter ) ) return false;

				// Only show subgraph input parameters when in a subgraph
				if ( !isSubgraph && targetType == typeof( BoolSubgraphInputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( IntSubgraphInputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( FloatSubgraphInputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Float2SubgraphInputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Float3SubgraphInputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Float4SubgraphInputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( ColorSubgraphInputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Texture2DSubgraphInputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( TextureCubeSubgraphInputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Float2x2SubgraphInputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Float3x3SubgraphInputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Float4x4SubgraphInputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( GradientSubgraphInputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( SamplerStateSubgraphInputParameter ) ) return false;

				// Only show subgraph output parameters when in a subgraph
				if ( !isSubgraph && targetType == typeof( BoolSubgraphOutputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( IntSubgraphOutputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( FloatSubgraphOutputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Float2SubgraphOutputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Float3SubgraphOutputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Float4SubgraphOutputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( ColorSubgraphOutputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Texture2DSubgraphOutputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( TextureCubeSubgraphOutputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Float2x2SubgraphOutputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Float3x3SubgraphOutputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( Float4x4SubgraphOutputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( GradientSubgraphOutputParameter ) ) return false;
				if ( !isSubgraph && targetType == typeof( SamplerStateSubgraphOutputParameter ) ) return false;
			}

			return true;
		} );
	}
}

public abstract class BlackboardMaterialParameter<T, Y> : BlackboardParameter, IBlackboardMaterialParameter where Y : IParameterUI
{
	[InlineEditor( Label = false ), Group( "Value" )]
	public T Value { get; set; }

	[InlineEditor( Label = false ), Group( "UI" )]
	public Y UI { get; set; }

	public bool IsAttribute { get; set; }

	public BlackboardMaterialParameter() : base()
	{
		IsAttribute = false;
	}

	public override object GetValue()
	{
		return Value;
	}

	public IParameterUI GetParameterUI()
	{
		return UI;
	}

	public override void SetValue( object value )
	{
		if ( value.GetType() != typeof( T ) )
		{
			throw new InvalidCastException( $"Cannot cast {value.GetType()} to {typeof( T )}" );
		}

		Value = (T)value;
	}
}

public abstract class BlackboardSubgraphInputParameter<T> : BlackboardParameter, IBlackboardSubgraphInputParameter
{
	[Title( "Input Name" )]
	public override string Name { get; set; }

	/// <summary>
	/// Description of what this input does
	/// </summary>
	[Title( "Input Description" )]
	[TextArea]
	public string Description { get; set; } = "";

	[InlineEditor( Label = false ), Group( "Value" )]
	public virtual T Value { get; set; }

	/// <summary>
	/// Whether this input is required (must have a connection in order to compile)
	/// </summary>
	public virtual bool IsRequired { get; set; } = false;

	/// <summary>
	/// The order of this input port.
	/// </summary>
	[Title( "Order" )]
	public int PortOrder { get; set; } = 0;

	[Hide, JsonIgnore]
	public abstract SubgraphPortType PortType { get; }

	public BlackboardSubgraphInputParameter() : base()
	{
	}

	public override object GetValue()
	{
		return Value;
	}

	public override void SetValue( object value )
	{
		if ( value.GetType() != typeof( T ) )
		{
			throw new InvalidCastException( $"Cannot cast {value.GetType()} to {typeof( T )}" );
		}

		Value = (T)value;
	}

	public override IGraphNode ToNode()
	{
		return new SubgraphInput()
		{
			ParameterIdentifier = Identifier,
		};
	}
}

public abstract class BlackboardSubgraphOutputParameter<T> : BlackboardParameter, IBlackboardSubgraphOutputParameter
{
	[Title( "Output Name" )]
	public override string Name { get; set; }

	/// <summary>
	/// Description of what this output does
	/// </summary>
	[Title( "Output Description" )]
	[TextArea]
	public string Description { get; set; } = "";

	/// <summary>
	/// The order of this output port
	/// </summary>
	[Title( "Order" )]
	public int PortOrder { get; set; } = 0;

	[Hide, JsonIgnore]
	public abstract SubgraphPortType PortType { get; }

	[HideIf( nameof( CannotPreviewOutputType ), true )]
	public SubgraphOutputPreviewType Preview { get; set; }

	[JsonIgnore, Hide, Browsable( false )]
	public bool CannotPreviewOutputType
	{
		get
		{
			return (PortType == SubgraphPortType.Bool ||
				PortType == SubgraphPortType.Float2x2 ||
				PortType == SubgraphPortType.Float3x3 ||
				PortType == SubgraphPortType.Float4x4 ||
				PortType == SubgraphPortType.Gradient ||
				PortType == SubgraphPortType.Texture2DObject ||
				PortType == SubgraphPortType.TextureCubeObject ||
				PortType == SubgraphPortType.SamplerState);
		}
	}

	public BlackboardSubgraphOutputParameter() : base()
	{
		Preview = SubgraphOutputPreviewType.None;
	}

	public override object GetValue()
	{
		return null;
	}

	public override void SetValue( object value )
	{
	}

	public override IGraphNode ToNode()
	{
		return new SubgraphOutput()
		{
			ParameterIdentifier = Identifier,
		};
	}
}

public abstract class BlackboardTextureMaterialParameter : BlackboardParameter
{
	[Hide]
	private TextureInput _value;
	[InlineEditor( Label = false ), Group( "Value" )]
	public TextureInput Value
	{
		get => _value with { Name = Name };
		set
		{
			_value = value;
		}
	}

	public BlackboardTextureMaterialParameter() : base()
	{
	}

	public override object GetValue()
	{
		return Value;
	}

	public override void SetValue( object value )
	{
		if ( value.GetType() != typeof( TextureInput ) )
		{
			throw new InvalidCastException( $"Cannot cast {value.GetType()} to {typeof( TextureInput )}" );
		}

		Value = (TextureInput)value;
	}

	public IParameterUI GetParameterUI()
	{
		throw new NotImplementedException();
	}
}