Editor/ShaderGraphPlus/Nodes Core/SubgraphNode.cs
using Editor;
using Editor.ShaderGraph;

namespace ShaderGraphPlus;

public sealed class SubgraphNode : ShaderNodePlus, IErroringNode, IWarningNode
{
	[JsonIgnore, Hide, Browsable( false )]
	public override Color NodeTitleColor => ShaderGraphPlusTheme.NodeHeaderColors.SubgraphNode;

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

	[Hide]
	public bool IsSubgraph => (Graph is ShaderGraphPlus shaderGraph && shaderGraph.IsSubgraph);

	[Hide]
	public string SubgraphPath { get; set; }

	[Hide, JsonIgnore]
	public ShaderGraphPlus Subgraph { get; set; }

	[Hide]
	private List<IPlugIn> InternalInputs = new();

	[Hide]
	public override IEnumerable<IPlugIn> Inputs => InternalInputs;

	[Hide]
	private List<IPlugOut> InternalOutputs = new();

	[Hide]
	public override IEnumerable<IPlugOut> Outputs => InternalOutputs;

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

	[global::Editor( "subgraphplus.defaultvalues" ), WideMode( HasLabel = false )]
	public Dictionary<string, object> DefaultValues { get; set; } = new();

	[Hide]
	public override DisplayInfo DisplayInfo => new()
	{
		Name = Subgraph?.Title ?? (string.IsNullOrEmpty( Subgraph.Title ) ? "Untitled Subgraph" : Subgraph.Title),
		Description = Subgraph?.Description ?? "",
		Icon = Subgraph?.Icon ?? ""
	};

	public void OnNodeCreated()
	{
		if ( Subgraph is not null ) return;

		if ( SubgraphPath != null )
		{

			Subgraph = new ShaderGraphPlus();
			if ( !Editor.FileSystem.Content.FileExists( SubgraphPath ) ) return;
			var json = Editor.FileSystem.Content.ReadAllText( SubgraphPath );
			Subgraph.Deserialize( json, SubgraphPath, SubgraphPath );
			Subgraph.Path = SubgraphPath;

			CreateInputs();
			CreateOutputs();

			Update();
		}
	}

	[Hide, JsonIgnore]
	internal Dictionary<IPlugIn, (SubgraphInput inputNode, Type inputNodeValueType)> InputReferences = new();

	public void CreateInputs()
	{
		var plugs = new List<IPlugIn>();
		var defaults = new Dictionary<Type, int>();
		InputReferences.Clear();

		// Get all SubgraphInput nodes only - no more legacy IParameterNode support
		var subgraphInputs = Subgraph.Nodes.OfType<SubgraphInput>()
			.Where( x => !string.IsNullOrWhiteSpace( x.Name ) )
			.OrderBy( x => x.PortOrder )
			.ThenBy( x => x.Name );

		foreach ( var subgraphInput in subgraphInputs )
		{
			var type = subgraphInput.PortType switch
			{
				SubgraphPortType.Bool => typeof( bool ),
				SubgraphPortType.Int => typeof( int ),
				SubgraphPortType.Float => typeof( float ),
				SubgraphPortType.Vector2 => typeof( Vector2 ),
				SubgraphPortType.Vector3 => typeof( Vector3 ),
				SubgraphPortType.Vector4 => typeof( Vector4 ),
				SubgraphPortType.Color => typeof( Color ),
				SubgraphPortType.Texture2DObject => typeof( Texture ),
				SubgraphPortType.TextureCubeObject => typeof( Texture ),
				_ => throw new NotImplementedException()
			};

			var info = new PlugInfo()
			{
				Name = subgraphInput.Name,
				Type = type,
				DisplayInfo = new DisplayInfo()
				{
					Name = subgraphInput.Name,
					Fullname = type.FullName
				}
			};

			var plug = new BasePlugIn( this, info, type );
			var oldPlug = InternalInputs.FirstOrDefault( x => x is BasePlugIn plugIn && plugIn.Info.Name == info.Name && plugIn.Info.Type == info.Type ) as BasePlugIn;
			if ( oldPlug is not null )
			{
				oldPlug.Info.Name = info.Name;
				oldPlug.Info.Type = info.Type;
				oldPlug.Info.DisplayInfo = info.DisplayInfo;
				plug = oldPlug;
			}
			plugs.Add( plug );
			InputReferences[plug] = (subgraphInput, type);

			if ( !DefaultValues.ContainsKey( plug.Identifier ) )
			{
				DefaultValues[plug.Identifier] = subgraphInput.DefaultValue;
			}
		}

		InternalInputs = plugs;
	}

	[Hide, JsonIgnore]
	internal Dictionary<IPlugOut, IPlugIn> OutputReferences = new();
	public void CreateOutputs()
	{
		var plugs = new List<IPlugOut>();

		foreach ( var subgraphOutput in Subgraph.Nodes.OfType<SubgraphOutput>().OrderBy( x => x.PortOrder ) )
		{
			var outputType = subgraphOutput.PortType switch
			{
				SubgraphPortType.Bool => typeof( bool ),
				SubgraphPortType.Int => typeof( int ),
				SubgraphPortType.Float => typeof( float ),
				SubgraphPortType.Vector2 => typeof( Vector2 ),
				SubgraphPortType.Vector3 => typeof( Vector3 ),
				SubgraphPortType.Vector4 => typeof( Vector4 ),
				SubgraphPortType.Color => typeof( Color ),
				SubgraphPortType.Float2x2 => typeof( Float2x2 ),
				SubgraphPortType.Float3x3 => typeof( Float3x3 ),
				SubgraphPortType.Float4x4 => typeof( Float4x4 ),
				SubgraphPortType.Gradient => typeof( Gradient ),
				SubgraphPortType.SamplerState => typeof( Sampler ),
				SubgraphPortType.Texture2DObject => typeof( Texture ),
				_ => throw new Exception( $"Unknown PortType \"{subgraphOutput.PortType}\"" )
			};

			if ( outputType is null ) continue;
			var info = new PlugInfo()
			{
				Name = subgraphOutput.OutputName,
				Type = outputType,
				DisplayInfo = new DisplayInfo()
				{
					Name = subgraphOutput.OutputName,
					Fullname = outputType.FullName,
					Description = subgraphOutput.OutputDescription
				}
			};
			var plug = new BasePlugOut( this, info, outputType );
			var oldPlug = InternalOutputs.FirstOrDefault( x => x is BasePlugOut plugOut && plugOut.Info.Name == info.Name && plugOut.Info.Type == info.Type ) as BasePlugOut;
			if ( oldPlug is not null )
			{
				oldPlug.Info.Name = info.Name;
				oldPlug.Info.Type = info.Type;
				oldPlug.Info.DisplayInfo = info.DisplayInfo;
				plugs.Add( oldPlug );
			}
			else
			{
				plugs.Add( plug );
			}
		}
		InternalOutputs = plugs;
	}

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

		foreach ( var node in Subgraph.Nodes )
		{
			if ( node is IWarningNode warningNode )
			{
				warnings.AddRange( warningNode.GetWarnings().Select( x => $"[{DisplayInfo.Name}] {x}" ) );
			}
		}

		return warnings;
	}

	public List<string> GetErrors()
	{
		OnNodeCreated();
		if ( Subgraph is null )
		{
			return new List<string> { $"Cannot find subgraph at \"{SubgraphPath}\"" };
		}

		var errors = new List<string>();

		foreach ( var node in Subgraph.Nodes )
		{
			if ( node is IErroringNode erroringNode )
			{
				errors.AddRange( erroringNode.GetErrors().Select( x => $"[{DisplayInfo.Name}] {x}" ) );
			}
		}

		foreach ( var input in InputReferences )
		{
			var plug = input.Key;
			var parameterNode = input.Value.inputNode;
			var inputName = parameterNode.Name;
			if ( string.IsNullOrWhiteSpace( inputName ) ) inputName = input.Key.DisplayInfo.Name;
			if ( IsSubgraph && plug.Type == typeof( Texture ) && plug.ConnectedOutput is null )
			{
				errors.Add( $"Required Input \"{inputName}\" is missing on Node \"{Subgraph.Title}\"" );
			}
			if ( parameterNode.IsRequired && plug.ConnectedOutput is null )
			{
				errors.Add( $"Required Input \"{inputName}\" is missing on Node \"{Subgraph.Title}\"" );
			}
		}

		return errors;
	}

	public override void OnDoubleClick( MouseEvent e )
	{
		base.OnDoubleClick( e );

		if ( string.IsNullOrEmpty( SubgraphPath ) ) return;

		var shader = AssetSystem.FindByPath( SubgraphPath );
		if ( shader is null ) return;

		shader.OpenInEditor();
	}

}

[CustomEditor( typeof( Dictionary<string, object> ), NamedEditor = "subgraphplus.defaultvalues", WithAllAttributes = [typeof( WideModeAttribute )] )]
internal class SubgraphNodeControlWidget : ControlWidget
{
	public override bool SupportsMultiEdit => false;

	SubgraphNode Node;
	ControlSheet Sheet;

	public SubgraphNodeControlWidget( SerializedProperty property ) : base( property )
	{
		Node = property.Parent.Targets.First() as SubgraphNode;

		Layout = Layout.Column();
		Layout.Spacing = 2;
		Sheet = new ControlSheet();
		Layout.Add( Sheet );

		Rebuild();
	}

	protected override void OnPaint()
	{

	}

	private void Rebuild()
	{
		Sheet.Clear( true );

		foreach ( var inputRef in Node.InputReferences )
		{
			var name = inputRef.Key.Identifier;
			var type = inputRef.Value.inputNodeValueType;
			var inputType = inputRef.Value.inputNode.PortType;
			var getter = () =>
			{
				if ( Node.DefaultValues.ContainsKey( name ) )
				{
					return Node.DefaultValues[name];
				}
				else
				{
					var val = inputRef.Value.inputNode.DefaultValue;
					if ( val is JsonElement el ) return el.GetDouble();
					return val;
				}
			};

			var attributes = new List<Attribute>();
			var properties = new List<SerializedProperty>();
			var displayName = $"Default {name}";

			if ( type == typeof( bool ) )
			{
				Sheet.AddRow( TypeLibrary.CreateProperty<bool>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return bool.Parse( el.GetRawText() );
						}

						return (bool)val;
					}, x => SetDefaultValue( name, x ),
					attributes.ToArray()
				) );
			}
			else if ( type == typeof( int ) )
			{
				Sheet.AddRow( TypeLibrary.CreateProperty<int>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return int.Parse( el.GetRawText() );
						}

						return (int)val;
					}, x => SetDefaultValue( name, x ),
					attributes.ToArray()
				) );
			}
			else if ( type == typeof( float ) )
			{
				Sheet.AddRow( TypeLibrary.CreateProperty<float>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return float.Parse( el.GetRawText() );
						}

						return (float)val;
					}, x => SetDefaultValue( name, x ),
					attributes.ToArray()
				) );
			}
			else if ( type == typeof( Vector2 ) )
			{
				Sheet.AddRow( TypeLibrary.CreateProperty<Vector2>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return Vector2.Parse( el.GetString() );
						}

						return (Vector2)val;
					}, x => SetDefaultValue( name, x ),
					attributes.ToArray()
				) );
			}
			else if ( type == typeof( Vector3 ) )
			{
				Sheet.AddRow( TypeLibrary.CreateProperty<Vector3>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return Vector3.Parse( el.GetString() );
						}

						return (Vector3)val;
					}, x => SetDefaultValue( name, x ),
					attributes.ToArray()
				) );
			}
			else if ( type == typeof( Vector4 ) )
			{
				Sheet.AddRow( TypeLibrary.CreateProperty<Vector4>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return Vector4.Parse( el.GetString() );
						}

						return (Vector4)val;
					}, x => SetDefaultValue( name, x ),
					attributes.ToArray()
				) );
			}
			else if ( type == typeof( Color ) )
			{
				Sheet.AddRow( TypeLibrary.CreateProperty<Color>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return Color.Parse( el.GetString() ) ?? Color.White;
						}

						return (Color)val;
					}, x => SetDefaultValue( name, x ),
					attributes.ToArray()
				) );
			}
			else if ( type == typeof( Float2x2 ) )
			{
				Sheet.AddRow( EditorTypeLibrary.CreateProperty<Float2x2>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return JsonSerializer.Deserialize<Float2x2>( el, ShaderGraphPlus.SerializerOptions() )!;
						}

						return (Float2x2)val;
					}, x => SetDefaultValue( name, x ),
					attributes.ToArray()
				) );
			}
			else if ( type == typeof( Float3x3 ) )
			{
				Sheet.AddRow( EditorTypeLibrary.CreateProperty<Float3x3>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return JsonSerializer.Deserialize<Float3x3>( el, ShaderGraphPlus.SerializerOptions() )!;
						}

						return (Float3x3)val;
					}, x => SetDefaultValue( name, x ),
					attributes.ToArray()
				) );
			}
			else if ( type == typeof( Float4x4 ) )
			{
				Sheet.AddRow( EditorTypeLibrary.CreateProperty<Float4x4>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return JsonSerializer.Deserialize<Float4x4>( el, ShaderGraphPlus.SerializerOptions() )!;
						}

						return (Float4x4)val;
					}, x => SetDefaultValue( name, x ),
					attributes.ToArray()
				) );
			}
			//else if ( !Node.IsSubgraph && type == typeof( Gradient ) )
			else if ( type == typeof( Gradient ) )
			{
				Sheet.AddRow( EditorTypeLibrary.CreateProperty<Gradient>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return JsonSerializer.Deserialize<Gradient>( el, ShaderGraphPlus.SerializerOptions() )!;
						}

						return (Gradient)val;
					}, x => SetDefaultValue( name, x ),
					attributes.ToArray()
				) );
			}
			else if ( !Node.IsSubgraph && type == typeof( Sampler ) )
			{
				attributes.Add( new InlineEditorAttribute() { Label = false } );
				properties.Add( EditorTypeLibrary.CreateProperty<Sampler>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return JsonSerializer.Deserialize<Sampler>( el, ShaderGraphPlus.SerializerOptions() )!;
						}

						return (Sampler)val;
					}, x => SetDefaultValue( name, x ),
					attributes.ToArray()
				) );

				Sheet.AddGroup( displayName, properties.ToArray() );
			}
			/*
			else if ( !Node.IsSubgraph && type == typeof( Texture2DObject ) )
			{
				attributes.Add( new InlineEditorAttribute() { Label = false } );
				properties.Add( EditorTypeLibrary.CreateProperty<TextureInput>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return JsonSerializer.Deserialize<TextureInput>( el, ShaderGraphPlus.SerializerOptions() )! with { ShowName = true, Type = TextureType.Tex2D };
						}

						return ((TextureInput)val) with { ShowName = true, Type = TextureType.Tex2D };
					}, x => SetDefaultValue( name, x ),
					attributes.ToArray()
				) );

				Sheet.AddGroup( displayName, properties.ToArray() );
			}
			else if ( !Node.IsSubgraph && type == typeof( TextureCubeObject ) )
			{
				attributes.Add( new InlineEditorAttribute() { Label = false } );
				properties.Add( EditorTypeLibrary.CreateProperty<TextureInput>(
					displayName, () =>
					{
						var val = getter();

						if ( val is JsonElement el )
						{
							return JsonSerializer.Deserialize<TextureInput>( el, ShaderGraphPlus.SerializerOptions() )! with { ShowName = true, Type = TextureType.TexCube };
						}

						return ((TextureInput)val) with { ShowName = true, Type = TextureType.TexCube };
					}, x =>
					{
						SetDefaultValue( name, x );
					},
					attributes.ToArray()
				) );

				Sheet.AddGroup( displayName, properties.ToArray() );
			}
			*/
		}

	}

	private void SetDefaultValue( string name, object value )
	{
		Node.DefaultValues[name] = value;
		Node.Update();
		Node.IsDirty = true;
	}
}