Editor/Graph/ParticlePreview.cs
using System;
using Editor;
using Sandbox;
using System.Collections.Generic;
using System.Linq;

namespace fxbox.Graph;

/// <summary>
/// Widget that renders a real-time preview of the particle system using native components
/// </summary>
public class ParticlePreview : SceneRenderingWidget
{
    private ParticleResource _resource;
    private GameObject _particleSystemObject;
    private FXBoxNativeParticleSystem _particleSystem;
    private bool _isPlaying = true;
    private float _playbackSpeed = 1.0f;

    private Vector2 _lastCursorPos;
    private Vector2 _angles = new Vector2(45, 30);
    private float _distance = 500f;
    private float _actualDistance = 500f;
    private Vector3 _targetPosition = Vector3.Zero;
    private bool _isOrbiting = false;

    public float PlaybackSpeed
    {
        get => _playbackSpeed;
        set => _playbackSpeed = value;
    }

    public ParticlePreview(Widget parent) : base(parent)
    {
        MouseTracking = true;
        FocusMode = FocusMode.Click;

        Scene = Scene.CreateEditorScene();

        using (Scene.Push())
        {
            // Setup camera
            var cameraGo = new GameObject(true, "camera");
            var camera = cameraGo.GetOrAddComponent<CameraComponent>();
            camera.BackgroundColor = new Color(0.1f, 0.1f, 0.15f);
            camera.ZFar = 10000;
            camera.FieldOfView = 60;
            Camera = camera;

            // Add lighting
            var sunGo = new GameObject(true, "sun");
            var sun = sunGo.GetOrAddComponent<DirectionalLight>();
            sun.WorldRotation = Rotation.FromPitch(50);
            sun.LightColor = Color.White;

            var ambientGo = new GameObject(true, "ambient");
            var ambient = ambientGo.GetOrAddComponent<AmbientLight>();
            ambient.Color = Color.Gray * 0.3f;
        }

        UpdateCameraPosition();
    }

    public void LoadParticleSystem(ParticleResource resource)
    {
        _resource = resource;
        
        // Clean up old system
        if (_particleSystemObject.IsValid())
        {
            _particleSystemObject.Destroy();
        }

        if (_resource != null)
        {
            using (Scene.Push())
            {
                // Create new particle system object
                _particleSystemObject = new GameObject(true, "ParticleSystem");
                _particleSystem = _particleSystemObject.AddComponent<FXBoxNativeParticleSystem>();
                _particleSystem.ParticleSystem = _resource;
                _particleSystem.PlayOnStart = true;
                
                // Initialize the system
                _particleSystem.UpdateEmitters();
            }
            
            Log.Info($"Loaded particle system with {_resource.Emitters?.Count ?? 0} emitters");
        }
    }

    public void SetPlaying(bool playing)
    {
        _isPlaying = playing;
        
        if (_particleSystemObject.IsValid())
        {
            // Enable/disable all particle effects
            foreach (var effect in _particleSystemObject.Children)
            {
                var particleEffect = effect.GetComponent<ParticleEffect>();
                if (particleEffect.IsValid())
                {
                    particleEffect.Enabled = playing;
                }
            }
        }
    }

    public void TogglePlayback()
    {
        _isPlaying = !_isPlaying;
        SetPlaying(_isPlaying);
        Log.Info($"Playback: {(_isPlaying ? "Playing" : "Paused")}");
    }

    public void Restart()
    {
        if (_particleSystemObject.IsValid())
        {
            // Restart by rebuilding the entire system
            LoadParticleSystem(_resource);
            Log.Info("Particle system restarted");
        }
    }

    protected override void PreFrame()
    {
        Scene.EditorTick(RealTime.Now, RealTime.Delta);

        DrawGizmos();
        
        // Force continuous updates
        Update();
    }

    private void DrawGizmos()
    {
        if (_resource == null) return;

        // Draw spawn shape for first emitter
        if (_resource.Emitters.Count > 0)
        {
            var emitter = _resource.Emitters[0];
            var posModule = emitter.InitializeModules.OfType<InitializePositionModule>().FirstOrDefault();
            
            if (posModule != null)
            {
                Gizmo.Draw.Color = Color.Yellow.WithAlpha(0.3f);
                Gizmo.Draw.LineThickness = 2;
                
                switch (posModule.Shape)
                {
                    case InitializePositionModule.SpawnShape.Sphere:
                        Gizmo.Draw.LineSphere(new Sphere(Vector3.Zero, posModule.Radius));
                        break;
                    case InitializePositionModule.SpawnShape.Box:
                        Gizmo.Draw.LineBBox(BBox.FromPositionAndSize(Vector3.Zero, posModule.BoxSize));
                        break;
                    case InitializePositionModule.SpawnShape.Cone:
                        DrawConeGizmo(posModule);
                        break;
                    case InitializePositionModule.SpawnShape.Circle:
                        Gizmo.Draw.LineCircle(Vector3.Zero, Vector3.Forward, posModule.Radius);
                        break;
                    case InitializePositionModule.SpawnShape.Line:
                        Gizmo.Draw.Line(posModule.LineStart, posModule.LineEnd);
                        break;
                }
            }
        }

        DrawGrid();
    }

    private void DrawConeGizmo(InitializePositionModule module)
    {
        var height = module.Radius;
        var radius = MathF.Tan(module.ConeAngle.DegreeToRadian()) * height;
        
        // Draw cone base
        Gizmo.Draw.LineCircle(Vector3.Forward * height, Vector3.Forward, radius);
        
        // Draw cone lines
        var points = 8;
        for (int i = 0; i < points; i++)
        {
            var angle = (i / (float)points) * 360f;
            var dir = new Vector3(
                MathF.Cos(angle.DegreeToRadian()) * radius,
                MathF.Sin(angle.DegreeToRadian()) * radius,
                height
            );
            Gizmo.Draw.Line(Vector3.Zero, dir);
        }
    }

    private void DrawGrid()
    {
        if (_resource?.PreviewSettings?.ShowGrid ?? true)
        {
            Gizmo.Draw.Color = Color.White.WithAlpha(0.1f);
            Gizmo.Draw.LineThickness = 1;
            
            // Draw XY grid
            for (int x = -500; x <= 500; x += 100)
            {
                Gizmo.Draw.Line(new Vector3(x, -500, 0), new Vector3(x, 500, 0));
            }
            for (int y = -500; y <= 500; y += 100)
            {
                Gizmo.Draw.Line(new Vector3(-500, y, 0), new Vector3(500, y, 0));
            }
        }

        if (_resource?.PreviewSettings?.ShowGround ?? true)
        {
            Gizmo.Draw.Color = Color.Gray.WithAlpha(0.2f);
            Gizmo.Draw.LineBBox(new BBox(new Vector3(-500, -500, -1), new Vector3(500, 500, 0)));
        }
        UpdateCameraPosition();
    }

    private void UpdateCameraPosition()
    {
        if (!Camera.IsValid()) return;

        Camera.WorldRotation = new Angles(_angles.y, -_angles.x, 0);
        _actualDistance = _actualDistance.LerpTo( _distance, Time.Delta * 15f );
        Camera.WorldPosition = _targetPosition + Camera.WorldRotation.Backward * _actualDistance;
    }

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

        if (e.LeftMouseButton)
        {
            _isOrbiting = true;
            _lastCursorPos = e.ScreenPosition;
        }
    }

    protected override void OnMouseReleased(MouseEvent e)
    {
        base.OnMouseReleased(e);

        if (e.LeftMouseButton)
        {
            _isOrbiting = false;
        }
    }

    protected override void OnMouseMove(MouseEvent e)
    {
        base.OnMouseMove(e);

        if (_isOrbiting)
        {
            var delta = e.ScreenPosition - _lastCursorPos;
            _angles.x += delta.x * 0.3f;
            _angles.y += delta.y * 0.3f;
            _angles.y = _angles.y.Clamp(-90, 90);
            
            //UpdateCameraPosition();
            _lastCursorPos = e.ScreenPosition;
        }
    }

    protected override void OnMouseWheel( WheelEvent e )
    {
	    base.OnMouseWheel(e);
	    _distance -= e.Delta * 1f;
	    _distance = _distance.Clamp(50, 5000);
    }
    
    protected override void OnPaint()
    {
        base.OnPaint();

        // Draw overlay info
        Paint.SetPen(Theme.Text);
        Paint.SetDefaultFont();
        // Count particles from all emitter objects
        int totalParticles = 0;
        int totalMax = _resource?.Emitters.Sum(e => e.MaxParticles) ?? 0;
        
        if (_particleSystemObject.IsValid())
        {
            foreach (var child in _particleSystemObject.Children)
            {
                var effect = child.GetComponent<ParticleEffect>();
                if (effect.IsValid())
                {
                    totalParticles += effect.Particles.Count;
                }
            }
        }
        
        var text = $"Particles: {totalParticles} / {totalMax}";
        Paint.DrawText(new Rect(10, 10, 200, 30), text, TextFlag.LeftTop);
        
        var stateText = _isPlaying ? "PLAYING" : "PAUSED";
        Paint.DrawText(new Rect(10, 40, 200, 30), stateText, TextFlag.LeftTop);
        
        // Draw emitter count
        var emitterText = $"Emitters: {_resource?.Emitters.Count ?? 0}";
        Paint.DrawText(new Rect(10, 70, 200, 30), emitterText, TextFlag.LeftTop);
    }
}