<?php

namespace PHPNative\Framework;

use PHPNative\Async\TaskManager;
use PHPNative\Ui\Component;
use PHPNative\Ui\Window;

class Application
{
    private static null|Application $instance = null;
    protected array $windows = [];
    protected int $nextWindowId = 0;
    protected bool $running = true;
    protected array $closedWindowIds = []; // Track recently closed window IDs

    public function __construct()
    {
        self::$instance = $this;

        // SDL3 uses event queue by default, no need to enable it
    }

    /**
     * Get singleton instance
     */
    public static function getInstance(): null|Application
    {
        return self::$instance;
    }

    /**
     * Create a new window
     *
     * @param string $title Window title
     * @param int $width Window width
     * @param int $height Window height
     * @param int $x Window X position (default: 100)
     * @param int $y Window Y position (default: 100)
     * @return Window The created window instance
     */
    public function createWindow(string $title, int $width = 800, int $height = 600, int $x = 100, int $y = 100): Window
    {
        $window = new Window($title, $width, $height, $x, $y);
        $windowId = $this->nextWindowId++;
        $this->windows[$windowId] = $window;

        return $window;
    }

    /**
     * Add an existing window to the application
     *
     * @param Window $window The window to add
     * @return void
     */
    public function addWindow(Window $window): void
    {
        $windowId = $this->nextWindowId++;
        $this->windows[$windowId] = $window;
    }

    /**
     * Get all windows
     */
    public function getWindows(): array
    {
        return $this->windows;
    }

    /**
     * Get window count
     */
    public function getWindowCount(): int
    {
        return count($this->windows);
    }

    /**
     * Main application loop
     */
    public function run(): void
    {
        $targetFps = 60;
        $targetFrameDuration = $targetFps > 0 ? (1.0 / $targetFps) : 0.0;

        while ($this->running && count($this->windows) > 0) {
            $frameStart = microtime(true);

            // Layout all windows FIRST (sets window references and calculates positions)
            foreach ($this->windows as $windowId => $window) {
                $window->layout();
            }
            // SDL3: Poll all events globally and distribute to the correct windows
            $events = [];
            $mouseMotionCount = 0;
            while ($event = sdl_poll_event()) {
                $events[] = $event;
                if ($event['type'] === SDL_EVENT_MOUSE_MOTION) {
                    $mouseMotionCount++;
                }
            }

            // Process tray events AFTER polling SDL events
            // This ensures tray callbacks are triggered
            if (function_exists('tray_poll')) {
                tray_poll(false);
            }

            // Coalesce mouse motion events: Only keep the last MouseMotion event per window
            // This dramatically reduces the number of events to process
            $coalescedEvents = [];
            $lastMouseMotionPerWindow = [];

            foreach ($events as $event) {
                if ($event['type'] === SDL_EVENT_MOUSE_MOTION) {
                    $windowId = $event['window_id'] ?? null;
                    // Store the last mouse motion event for each window
                    $lastMouseMotionPerWindow[$windowId] = $event;
                } else {
                    // Keep all non-motion events
                    $coalescedEvents[] = $event;
                }
            }

            // Add the last mouse motion event for each window at the end
            foreach ($lastMouseMotionPerWindow as $event) {
                $coalescedEvents[] = $event;
            }

            // Track coalescing effectiveness
            if ($mouseMotionCount > 0) {
                Profiler::increment('sdl_mouse_motion_events', $mouseMotionCount);
                Profiler::increment('coalesced_mouse_motion_events', count($lastMouseMotionPerWindow));
            }

            $events = $coalescedEvents;

            // Distribute events to windows based on window_id
            foreach ($events as $event) {
                $eventWindowId = $event['window_id'] ?? null;

                // Debug event distribution
                if (defined('DEBUG_EVENTS') && DEBUG_EVENTS && $eventWindowId !== null) {
                    $eventType = $event['type'];
                    error_log("[Application] Distributing event type={$eventType} to window_id={$eventWindowId}");
                }

                // Global events (like QUIT) go to all windows
                if ($eventWindowId === null) {
                    foreach ($this->windows as $windowId => $window) {
                        $window->processEvent($event);
                    }
                } else {
                    // Window-specific events go to the correct window
                    $delivered = false;
                    foreach ($this->windows as $windowId => $window) {
                        if ($window->getWindowId() === $eventWindowId) {
                            $window->processEvent($event);
                            $delivered = true;
                            break;
                        }
                    }

                    if (!$delivered) {
                        // Check if this is a recently closed window (expected)
                        if (!in_array($eventWindowId, $this->closedWindowIds)) {
                            if (defined('DEBUG_EVENTS') && DEBUG_EVENTS) {
                                error_log(
                                    "[Application] WARNING: Event for window_id={$eventWindowId} could not be delivered (window not found)",
                                );
                            }
                        } else {
                            // This is a recently closed window, events are expected
                            if (defined('DEBUG_EVENTS') && DEBUG_EVENTS) {
                                error_log(
                                    "[Application] Ignoring event for recently closed window_id={$eventWindowId}",
                                );
                            }
                        }
                    }
                }
            }

            // Update async tasks (global)
            TaskManager::getInstance()->update();

            // Update all windows and their hover states
            // Render all windows (skip windows that are closing)
            foreach ($this->windows as $windowId => $window) {
                if (!$window->shouldClose()) {
                    $window->render();
                }
            }

            $oneWindowIsClosed = false;
            // Remove closed windows
            foreach ($this->windows as $windowId => $window) {
                if ($window->shouldClose()) {
                    // Track this window ID as recently closed
                    $sdlWindowId = $window->getWindowId();
                    if (!in_array($sdlWindowId, $this->closedWindowIds)) {
                        $this->closedWindowIds[] = $sdlWindowId;
                    }

                    // Explicitly destroy the window (closes SDL window, renderer, and text renderer)
                    $window->destroy();
                    unset($this->windows[$windowId]);
                    $oneWindowIsClosed = true;
                }
            }

            // Clean up old closed window IDs after 10 frames (to prevent memory leak)
            // This gives SDL enough time to stop sending events for closed windows
            if (count($this->closedWindowIds) > 10) {
                array_shift($this->closedWindowIds);
            }

            if ($oneWindowIsClosed) {
                foreach ($this->windows as $windowId => $window) {
                    $window->setShouldBeReLayouted(true);
                }
            }

            if ($targetFrameDuration > 0) {
                $elapsed = microtime(true) - $frameStart;
                $timeToSleep = $targetFrameDuration - $elapsed;
                if ($timeToSleep > 0) {
                    usleep((int) ($timeToSleep * 1_000_000));
                }
            }
        }

        // Cleanup remaining windows
        $this->cleanup();
    }

    /**
     * Clean up all resources
     */
    protected function cleanup(): void
    {
        foreach ($this->windows as $window) {
            $window->cleanup();
        }
        $this->windows = [];
    }

    /**
     * Stop the application (close all windows)
     */
    public function quit(): void
    {
        $this->running = false;
    }
}
