Editor/ShaderGraphPlus/Nodes Core/SubgraphInput.cs
using Editor;

namespace ShaderGraphPlus;

public enum SubgraphPortType
{
	[Hide]
	Invalid,
	[Icon( "check_box" )]
	Bool,
	[Icon( "looks_one" )]
	Int,
	[Icon( "looks_one" )]
	Float,
	[Title( "Float2" ), Icon( "looks_two" )]
	Vector2,
	[Title( "Float3" ), Icon( "looks_3" )]
	Vector3,
	[Title( "Float4" ), Icon( "looks_4" )]
	Vector4,
	[Title( "Color" ), Icon( "palette" )]
	Color,
	[Title( "Float2x2" ), Icon( "apps" )]
	Float2x2,
	[Title( "Float3x3" ), Icon( "apps" )]
	Float3x3,
	[Title( "Float4x4" ), Icon( "apps" )]
	Float4x4,
	[Title( "Gradient" ), Icon( "gradient" )]
	Gradient,
	[Title( "Sampler State" ), Icon( "colorize" )]
	SamplerState,
	[Title( "Texture2D" ), Icon( "texture" )]
	Texture2DObject,
	[Title( "TextureCube" ), Icon( "view_in_ar" )]
	TextureCubeObject
}

/// <summary>
/// Input of a Subgraph.
/// </summary>
[Title( "Subgraph Input" ), Icon( "input" ), SubgraphOnly]
[InternalNode]
public sealed class SubgraphInput : ShaderNodePlus, IParameterNode, IBlackboardNode, IErroringNode
{
	[Hide]
	public override string Title => string.IsNullOrWhiteSpace( Name ) ?
	$"Subgraph Input" :
	$"{Name} ({PortType})";

	[JsonIgnore, Hide, Browsable( false )]
	public override Color NodeTitleColor => ShaderGraphPlusTheme.NodeHeaderColors.SubgraphNode;

	[JsonIgnore, Hide, Browsable( false )]
	public override bool AutoSize => true;

	[Hide, JsonIgnore]
	public override bool CanPreview => false;

	//[Hide, JsonIgnore]
	//private bool IsSubgraph => (Graph is ShaderGraphPlus shaderGraph && shaderGraph.IsSubgraph);

	[Hide]
	public Guid ParameterIdentifier { get; set; }

	//[Hide]
	//private bool IsPreviewInputEnabled => InputType != SubgraphPortType.Texture2DObject;

	[Input, Title( "Preview" ), Hide]
	public NodeInput PreviewInput { get; set; }

	/// <summary>
	/// The name of the input parameter
	/// </summary>
	[Hide, JsonIgnore]
	[Title( "Input Name" )]
	public string Name => GetParameter().Name;

	/// <summary>
	/// Description of what this input does
	/// </summary>
	[Hide, JsonIgnore]
	public string InputDescription => GetParameter().Description;

	/// <summary>
	/// The type of the input parameter
	/// </summary>
	[Hide, JsonIgnore]
	public SubgraphPortType PortType => GetParameter().PortType;

	//[Editor( "subgraphInputDefaultValue" )]
	[Hide, JsonIgnore]
	public object DefaultValue
	{
		get => GetParameter().GetValue();
		set
		{
			if ( Graph is ShaderGraphPlus graph )
			{
				graph.UpdateParameterValue( ParameterIdentifier, value );

				Update();
			}
		}
	}

	/// <summary>
	/// Whether this input is required (must have a connection in order to compile)
	/// </summary>
	[Hide, JsonIgnore]
	public bool IsRequired => GetParameter().IsRequired;

	/// <summary>
	/// The order of this input port on the subgraph node
	/// </summary>
	[Hide, JsonIgnore]
	public int PortOrder => GetParameter().PortOrder;

	public SubgraphInput()
	{
	}

	[Hide, JsonIgnore]
	int _lastNameHashCode = 0;

	public override void OnFrame()
	{
		var nameHashCode = 0;
		nameHashCode += GetParameter().Name.GetHashCode();

		if ( nameHashCode != _lastNameHashCode )
		{
			_lastNameHashCode = nameHashCode;
			Update();
		}
	}

	/// <summary>
	/// Output for the input value
	/// </summary>
	[Output, Title( "Value" ), Hide]
	public NodeResult.Func Result => ( GraphCompiler compiler ) =>
	{
		// In subgraphs, check if preview input is connected
		if ( compiler.Graph.IsSubgraph && PreviewInput.IsValid )
		{
			return compiler.Result( PreviewInput );
		}

		// Use the appropriate default value based on input type
		var outputValue = DefaultValue;
		var resultType = PortType switch
		{
			SubgraphPortType.Bool => ResultType.Bool,
			SubgraphPortType.Int => ResultType.Int,
			SubgraphPortType.Float => ResultType.Float,
			SubgraphPortType.Vector2 => ResultType.Vector2,
			SubgraphPortType.Vector3 => ResultType.Vector3,
			SubgraphPortType.Vector4 => ResultType.Vector4,
			SubgraphPortType.Color => ResultType.Vector4,
			SubgraphPortType.Float2x2 => ResultType.Float2x2,
			SubgraphPortType.Float3x3 => ResultType.Float3x3,
			SubgraphPortType.Float4x4 => ResultType.Float4x4,
			SubgraphPortType.Gradient => ResultType.Gradient,
			SubgraphPortType.SamplerState => ResultType.Sampler,
			SubgraphPortType.Texture2DObject => ResultType.Texture2D,
			SubgraphPortType.TextureCubeObject => ResultType.TextureCube,
			_ => throw new NotImplementedException( $"Unknown InputType \"{PortType}\"" ),
		};

		// If we're in a subgraph context, just return the value directly
		if ( compiler.Graph.IsSubgraph )
		{
			if ( PortType == SubgraphPortType.Texture2DObject || PortType == SubgraphPortType.TextureCubeObject )
			{
				return new NodeResult( resultType, ProcessTexture2D( compiler, (TextureInput)outputValue, PortType == SubgraphPortType.Texture2DObject ), true );
			}

			if ( PortType == SubgraphPortType.Gradient )
			{
				var gradientValue = (Gradient)outputValue;

				if ( gradientValue.Colors.Count > 8 )
				{
					return NodeResult.Error( $"{DisplayInfo.Name} has {gradientValue.Colors.Count} color keys which is greater than the maximum amount of 8 allowed color keys." );
				}
				else
				{
					// Register gradient with the compiler.
					var result = compiler.RegisterGradient( gradientValue, Name );

					return new NodeResult( ResultType.Gradient, result, constant: true );
				}
			}

			return compiler.ResultValue( outputValue );
		}
		else
		{
			if ( PortType == SubgraphPortType.Texture2DObject || PortType == SubgraphPortType.TextureCubeObject )
			{
				return new NodeResult( resultType, ProcessTexture2D( compiler, (TextureInput)outputValue, PortType == SubgraphPortType.Texture2DObject ), true );
			}

			if ( PortType == SubgraphPortType.Gradient )
			{
				var gradientValue = (Gradient)outputValue;

				if ( gradientValue.Colors.Count > 8 )
				{
					return NodeResult.Error( $"{DisplayInfo.Name} has {gradientValue.Colors.Count} color keys which is greater than the maximum amount of 8 allowed color keys." );
				}
				else
				{
					// Register gradient with the compiler.
					var result = compiler.RegisterGradient( gradientValue, Name );

					return new NodeResult( ResultType.Gradient, result, constant: true );
				}
			}

			// For normal graphs, use ResultParameter to create a material parameter
			return compiler.ResultParameter( Name, outputValue, default, default, false, IsRequired, default );
		}
	};

	private string ProcessTexture2D( GraphCompiler compiler, TextureInput input, bool isTexture2D )
	{
		input.Type = isTexture2D ? TextureType.Tex2D : TextureType.TexCube;

		return compiler.ResultTexture( input, null, true );
	}

	private IBlackboardSubgraphInputParameter GetParameter()
	{
		if ( Graph is ShaderGraphPlus graph )
		{
			var parameter = graph.FindParameter( ParameterIdentifier );

			if ( parameter is IBlackboardSubgraphInputParameter subgraphInputParameter )
			{
				return subgraphInputParameter;
			}

			return default;
		}

		return default;
	}

	public NodeResult GetResult( GraphCompiler compiler )
	{
		return Result.Invoke( compiler );
	}

	public List<string> GetWarnings()
	{
		var warnings = new List<string>();

		return warnings;
	}

	public List<string> GetErrors()
	{
		List<string> errors = new List<string>();

		return errors;
	}
}