namespace NodeWar.scripts;

using Godot;

[Tool]
[GlobalClass]
public partial class LinePath2D : Path2D
{
	private const bool ShouldCreateDefaultCurve = true;

	private const bool ShouldCreateDefaultProfile = true;

	// Size in pixels of the default curve
	private const int DefaultCurveSize = 400;

	// Size in pixels of the default curve width
	private const float DefaultCurveWidth = 25.0f;

	private Line2D _line = new();

	[ExportCategory("LinePath2D")]
	[Export]
	public Curve2D LinePathCurve
	{
		get => Curve;
		set
		{
			if (Curve != null)
				Curve.Changed -= BuildLine;

			Curve = value;

			if (Curve != null)
				Curve.Changed += BuildLine;

			BuildLine();
		}
	}

	/// <summary>
	/// Sets the width of the curve.
	/// </summary>
	[Export]
	public float Width
	{
		get => _line?.Width ?? DefaultCurveWidth;
		set
		{
			if (_line == null)
				return;

			_line.Width = value;
		}
	}

	/// <summary>
	/// Use this Curve to modify the line width profile.
	/// </summary>
	[Export]
	public Curve WidthProfile
	{
		get => _line?.WidthCurve;
		set
		{
			if (_line == null)
				return;

			_line.WidthCurve = value;
		}
	}

	[ExportGroup("Fill", "Fill")]
	[Export]
	public Color FillDefaultColor
	{
		get => _line?.DefaultColor ?? Colors.White;
		set
		{
			if (_line != null)
				_line.DefaultColor = value;
		}
	}

	/// <summary>
	/// Fill the path with a gradient.
	/// </summary>
	[Export]
	public Gradient FillGradient
	{
		get => _line?.Gradient;
		set
		{
			if (_line != null)
				_line.Gradient = value;
		}
	}

	/// <summary>
	/// Fill the path with a texture.
	/// </summary>
	[Export]
	public Texture2D FillTexture
	{
		get => _line?.Texture;
		set
		{
			if (_line != null)
				_line.Texture = value;
		}
	}

	/// <summary>
	/// Change the texture fill mode.
	/// </summary>
	[Export]
	public Line2D.LineTextureMode FillTextureMode
	{
		get => _line?.TextureMode ?? Line2D.LineTextureMode.None;
		set
		{
			if (_line != null)
				_line.TextureMode = value;
		}
	}

	[ExportGroup("Capping", "Cap")]
	[Export]
	public Line2D.LineJointMode CapJointMode
	{
		get => _line?.JointMode ?? Line2D.LineJointMode.Sharp;
		set
		{
			if (_line != null)
				_line.JointMode = value;
		}
	}

	/// <summary>
	/// The style of the beginning of the polyline.
	/// </summary>
	[Export]
	public Line2D.LineCapMode CapBeginCap
	{
		get => _line?.BeginCapMode ?? Line2D.LineCapMode.None;
		set
		{
			if (_line != null)
				_line.BeginCapMode = value;
		}
	}

	/// <summary>
	/// The style of the ending of the polyline.
	/// </summary>
	[Export]
	public Line2D.LineCapMode CapEndCap
	{
		get => _line?.EndCapMode ?? Line2D.LineCapMode.None;
		set
		{
			if (_line != null)
				_line.EndCapMode = value;
		}
	}

	/// <summary>
	/// If true and the polyline has more than two segments,
	/// the first and the last point will be connected by a segment.
	/// </summary>
	[Export]
	public bool CapCloseCurve
	{
		get => _line?.Closed ?? false;
		set
		{
			if (_line != null)
				_line.Closed = value;
		}
	}

	[ExportGroup("Border", "Border")]
	[Export]
	public float BorderSharpLimit
	{
		get => _line?.SharpLimit ?? 2.0f;
		set
		{
			if (_line != null)
				_line.SharpLimit = value;
		}
	}

	/// <summary>
	/// The smoothness of the rounded joints and caps.
	/// </summary>
	[Export]
	public int BorderRoundPrecision
	{
		get => _line?.RoundPrecision ?? 8;
		set
		{
			if (_line != null)
				_line.RoundPrecision = value;
		}
	}

	/// <summary>
	/// If true the polyline border will be antialiased.
	/// Note: Antialiased polylines are not accelerated by batching.
	/// </summary>
	[Export]
	public bool BorderAntialiased
	{
		get => _line?.Antialiased ?? false;
		set
		{
			if (_line != null)
				_line.Antialiased = value;
		}
	}

	public override void _Ready()
	{
		ClearDuplicatedInternalChildren();

		_line.SetMeta("__lp2d_internal__", true);
		AddChild(_line);

		if (Curve == null || Curve.PointCount < 2)
			Curve = CreateDefaultCurve(DefaultCurveSize);

		_line.WidthCurve ??= CreateDefaultProfile(DefaultCurveWidth);

		BuildLine();

		if (Curve != null)
		{
			Curve.Changed -= BuildLine;
			Curve.Changed += BuildLine;
		}
	}

	public override void _ExitTree()
	{
		if (Curve != null)
			Curve.Changed -= BuildLine;
	}

	private Curve2D CreateDefaultCurve(int size)
	{
		if (!ShouldCreateDefaultCurve)
			return null;

		var curve = new Curve2D();

		curve.AddPoint(Vector2.Zero, Vector2.Zero, new Vector2(size, 0));

		curve.AddPoint(new Vector2(size, size), new Vector2(-size, 0), Vector2.Zero);

		return curve;
	}

	private static Curve CreateDefaultProfile(float size)
	{
		if (!ShouldCreateDefaultProfile)
			return null;

		var curve = new Curve();

		curve.AddPoint(Vector2.Zero);
		curve.AddPoint(new Vector2(0.5f, 1.0f));
		curve.AddPoint(new Vector2(1.0f, 0.0f));

		return curve;
	}

	private void BuildLine()
	{
		if (_line == null)
			return;

		if (Curve == null || Curve.PointCount < 2)
		{
			_line.ClearPoints();
			return;
		}

		_line.Points = Curve.GetBakedPoints();
	}

	private void ClearDuplicatedInternalChildren()
	{
		foreach (var child in GetChildren())
		{
			if (child.GetMeta("__lp2d_internal__", false).AsBool())
				child.QueueFree();
		}
	}
}
