Editor/ShaderGraphPlus/Widgets/ControlWidgets/HLSLAssetPathControlWidget.cs
using Editor;
using ShaderGraphPlus.Nodes;
using System.Text;

namespace ShaderGraphPlus;

[CustomEditor( typeof( string ), WithAllAttributes = new[] { typeof( HLSLAssetPathAttribute ) } )]
internal sealed class HLSLAssetPathControlWidget : ControlWidget
{
	public override bool IsControlButton => true;
	public override bool SupportsMultiEdit => false;

	string FilePath;
	string FilePathAbsolute;

	IconButton PreviewButton = null;
	private ContextMenu menu;

	CustomFunctionNode Node;
	SerializedProperty FunctionNameProperty;

	public HLSLAssetPathControlWidget( SerializedProperty property ) : base( property )
	{
		FilePath = property.GetValue<string>();

		Node = property.Parent.Targets.FirstOrDefault() as CustomFunctionNode;

		FunctionNameProperty = Node.GetSerialized().GetProperty( nameof( CustomFunctionNode.Name ) );

		if ( Node is null )
			return;

		HorizontalSizeMode = SizeMode.CanGrow | SizeMode.Expand;
		Cursor = CursorShape.Finger;
		MouseTracking = true;
		AcceptDrops = true;
		IsDraggable = true;
	}

	protected override void DoLayout()
	{
		base.DoLayout();

		if ( PreviewButton.IsValid() )
		{
			PreviewButton.FixedSize = Height - 2;
			PreviewButton.Position = new Vector2( Width - Height + 1, 1 );
		}
	}

	private void DrawContent( Rect rect, string title, string path )
	{
		bool multiline = Height > 32;
		Rect textRect = rect.Shrink( 0, 6 );
		var alpha = IsControlDisabled ? 0.6f : 1f;

		if ( multiline )
		{
			textRect = new Rect( textRect.TopLeft, new Vector2( textRect.Width, textRect.Height / 2 ) );
		}

		Paint.SetPen( Color.White.WithAlpha( 0.9f * alpha ) );
		Paint.SetFont( "Poppins", 8, 450 );
		var t = Paint.DrawText( textRect, title, multiline ? TextFlag.LeftCenter : TextFlag.LeftCenter );

		if ( multiline )
		{
			textRect.Position += new Vector2( 0, textRect.Height );
		}
		else
		{
			textRect.Left = t.Right + 6;
		}

		Paint.SetDefaultFont( 7 );
		Theme.DrawFilename( textRect, path, multiline ? TextFlag.LeftCenter : TextFlag.LeftBottom, Color.White.WithAlpha( 0.5f * alpha ) );
	}

	protected override void PaintControl()
	{
		var rect = new Rect( 0, Size );

		var iconRect = rect.Shrink( 2 );
		iconRect.Width = iconRect.Height;

		var alpha = IsControlDisabled ? 0.6f : 1f;
		var textRect = rect.Shrink( 0, 3 );
		var pickerName = DisplayInfo.ForType( SerializedProperty.PropertyType ).Name;

		//Paint.SetBrush(Theme.Red.Darken(0.8f).WithAlpha(alpha));
		//Paint.DrawRect(iconRect, 2);

		//Paint.SetPen(Theme.Red.WithAlpha(alpha));
		//Paint.DrawIcon(iconRect, "error", Math.Max(16, iconRect.Height / 2));

		DrawContent( rect, $"", FilePath );
	}

	public void GenerateHLSLIncludeBase()
	{
		if ( string.IsNullOrWhiteSpace( Node.Name ) )
		{
			Dialog.AskString( ( string name ) =>
			{
				FunctionNameProperty.SetValue( name );
				Generate( name );
			}, "What would you like to call your function?", title: "Function Name" );
		}
		else
		{
			Generate( Node.Name );
		}
	}

	private void Generate( string functionName )
	{
		string functionHeader = $"void {functionName}({Node.ConstructArguments( Node.FunctionInputs, false )}{(Node.FunctionInputs.Any() ? "," : "")}{Node.ConstructArguments( Node.FunctionOutputs, true )})";
		StringBuilder functionBody = new StringBuilder();

		foreach ( var output in Node.FunctionOutputs )
		{
			var initialValue = "";

			switch ( output.HLSLDataType )
			{
				case "bool":
					initialValue = "false";
					break;
				case "int":
					initialValue = "0";
					break;
				case "float":
					initialValue = "0.0f";
					break;
				case "float2":
					initialValue = "float2( 0.0f, 0.0f )";
					break;
				case "float3":
					initialValue = "float3( 0.0f, 0.0f, 0.0f )";
					break;
				case "float4":
					initialValue = "float4( 0.0f, 0.0f, 0.0f, 0.0f )";
					break;
				case "float2x2":
					initialValue = "float2x2( 0.0f, 0.0f, 0.0f, 0.0f )";
					break;
				case "float3x3":
					initialValue = "float3x3( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f )";
					break;
				case "float4x4":
					initialValue = "float4x4( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f )";
					break;
				default:
					throw new Exception( $"Unknown HLSL DataType `{output.HLSLDataType}`" );
			}

			functionBody.AppendLine( $"{output.Name} = {initialValue};" );
		}

		string result = string.Format( HLSLIncludeTemplate.Contents,
			functionName.ToUpper(),
			functionHeader,
			GraphCompiler.IndentString( functionBody.ToString(), 2 )
		);

		string absolutePath = SaveFile( result );

		if ( absolutePath is null )
			return;

		FilePathAbsolute = absolutePath;

		OpenFile();
	}

	protected override void OnMousePress( MouseEvent e )
	{
		base.OnMouseClick( e );

		if ( string.IsNullOrWhiteSpace( FilePath ) )
		{
			GenerateHLSLIncludeBase();
		}
		else
		{
			if ( e.RightMouseButton )
			{
				if ( !string.IsNullOrWhiteSpace( Node.Source ) )
				{
					FilePathAbsolute = Editor.FileSystem.Content.GetFullPath( $"shaders/{Node.Source}" );

					menu?.Close();
					menu = new ContextMenu();

					menu.AddOption( "Open include", "file_open", action: () => OpenFile() );
					menu.AddOption( "Clear...", "delete", action: () => ClearFile() );

					menu.OpenAt( ScreenRect.BottomLeft );

					e.Accepted = true;
				}
			}
		}
	}

	private void OpenFile()
	{
		Process.Start( new ProcessStartInfo
		{
			FileName = FilePathAbsolute,
			UseShellExecute = true
		} );
	}

	private void ClearFile()
	{
		FilePathAbsolute = "";
		FilePath = "";

		UpdateProperty();
	}

	private string SaveFile( string generatedFile )
	{
		var fd = new FileDialog( null )
		{
			Title = $"Select Path To Save HLSL File",
			DefaultSuffix = $".hlsl"
		};

		fd.Directory = $"{Project.Current.GetAssetsPath()}/shaders";

		fd.SetNameFilter( $"Shader Include (*.hlsl)" );

		if ( !Directory.Exists( $"{Project.Current.GetAssetsPath()}/shaders" ) )
		{
			Directory.CreateDirectory( $"{Project.Current.GetAssetsPath()}/shaders" );
		}

		if ( !fd.Execute() )
			return null;

		System.IO.File.WriteAllText( fd.SelectedFile, generatedFile );

		FilePath = Path.GetRelativePath( Project.Current.GetAssetsPath(), fd.SelectedFile ).Replace( '\\', '/' ).Remove( 0, 8 );

		UpdateProperty();

		return fd.SelectedFile;
	}

	private void UpdateProperty()
	{
		SerializedProperty.SetValue( FilePath );
	}
}