<?php

namespace PHPNative\Ui;

use PHPNative\Framework\Profiler;
use PHPNative\Framework\TextRenderer;

class Window
{
    private mixed $window = null;
    private mixed $renderer = null;
    private int $windowId = 0;
    private null|Component $rootComponent = null;
    private null|TextRenderer $textRenderer;
    private float $mouseX = 0;
    private float $mouseY = 0;
    private Viewport $viewport;
    private bool $shouldBeReLayouted = true;
    private float $uiScale = 1.0;
    private bool $shouldClose = false;
    private $onResize = null;
    private $onFpsChange = null;
    private float $lastFpsUpdate = 0.0;
    private int $frameCounter = 0;
    private float $currentFps = 0.0;
    private bool $leftButtonDown = false;

    public function __construct(
        private string $title,
        private int $width = 800,
        private int $height = 600,
        private int $x = 100,
        private int $y = 100,
    ) {
        // Initialize SDL if not already done
        static $sdlInitialized = false;
        if (!$sdlInitialized) {
            if (!sdl_init(SDL_INIT_VIDEO)) {
                throw new \Exception('Failed to initialize SDL: ' . sdl_get_error());
            }

            // Initialize TTF
            if (!ttf_init()) {
                throw new \Exception('Failed to initialize TTF: ' . sdl_get_error());
            }

            $sdlInitialized = true;
        }

        // Create window with resizable flag for normal window decorations
        $flags = SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_RESIZABLE;

        $this->window = sdl_create_window($title, $width, $height, $flags);
        if (!$this->window) {
            throw new \Exception('Failed to create window: ' . sdl_get_error());
        }

        // Get window ID for event routing
        $this->windowId = sdl_get_window_id($this->window);
        // Use display scale as UI scale (e.g. 2.0 on HiDPI)
        $scale = sdl_get_window_display_scale($this->window);

        if ($scale > 0.1 && $scale <= 4.0) {
            $this->uiScale = (float) $scale;
        }
        // Enable text input for this window
        sdl_start_text_input($this->window);

        // Create renderer
        $this->renderer = sdl_create_renderer($this->window);
        if (!$this->renderer) {
            throw new \Exception('Failed to create renderer: ' . sdl_get_error());
        }

        // Initialize text renderer
        $this->textRenderer = new TextRenderer($this->renderer);
        if (!$this->textRenderer->init()) {
            error_log('Warning: Failed to initialize text renderer. Text rendering will not be available.');
        }
        $this->textRenderer->setPixelRatio($this->uiScale);
        // Get actual window size
        $size = sdl_get_window_size($this->window);
        $this->width = $size[0];
        $this->height = $size[1];
        $this->viewport = new Viewport(
            windowWidth: $this->width * $this->uiScale,
            windowHeight: $this->height * $this->uiScale,
            width: $this->width * $this->uiScale,
            height: $this->height * $this->uiScale,
            uiScale: $this->uiScale,
        );
        $this->lastFpsUpdate = microtime(true);
    }

    public function getUiScale(): float
    {
        return $this->uiScale;
    }

    public function setRoot(Component $component): self
    {
        if ($this->rootComponent !== null) {
            $this->rootComponent->detachFromWindow();
        }

        $this->rootComponent = $component;
        $this->rootComponent->attachToWindow($this);
        $this->shouldBeReLayouted = true;

        // Layout immediately to prevent black screen on first render
        // This is especially important for windows created during event handling
        $this->layout();

        return $this;
    }

    public function getRoot(): null|Component
    {
        return $this->rootComponent;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    public function getWidth(): int
    {
        return $this->width;
    }

    public function getHeight(): int
    {
        return $this->height;
    }

    public function shouldClose(): bool
    {
        return $this->shouldClose;
    }

    public function close(): void
    {
        $this->shouldClose = true;
    }

    public function getWindowResource(): mixed
    {
        return $this->window;
    }

    public function getRendererResource(): mixed
    {
        return $this->renderer;
    }

    public function getWindowId(): int
    {
        return $this->windowId;
    }

    /**
     * Process a single event for this window
     * Called by Application with pre-filtered events
     */
    public function processEvent(array $event): void
    {
        // Debug output - can be removed later
        if (defined('DEBUG_EVENTS') && DEBUG_EVENTS) {
            $eventTypes = [
                SDL_EVENT_QUIT => 'QUIT',
                SDL_EVENT_WINDOW_CLOSE_REQUESTED => 'WINDOW_CLOSE_REQUESTED',
                SDL_EVENT_KEY_DOWN => 'KEY_DOWN',
                SDL_EVENT_MOUSE_BUTTON_DOWN => 'MOUSE_BUTTON_DOWN',
                SDL_EVENT_MOUSE_BUTTON_UP => 'MOUSE_BUTTON_UP',
                SDL_EVENT_MOUSE_MOTION => 'MOUSE_MOTION',
                SDL_EVENT_WINDOW_RESIZED => 'WINDOW_RESIZED',
            ];
            $typeName = $eventTypes[$event['type']] ?? ('UNKNOWN(' . $event['type'] . ')');
            error_log("[{$this->title}] (ID:{$this->windowId}) Event: {$typeName}");
        }

        switch ($event['type']) {
            case SDL_EVENT_QUIT:
            case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
                $this->shouldClose = true;
                // Drag handling: mouse move while left button is pressed
                if ($this->leftButtonDown && $this->rootComponent) {
                    $this->rootComponent->handleDrag($this->mouseX, $this->mouseY);
                }
                break;

            case SDL_EVENT_KEY_DOWN:
                $keycode = $event['keycode'] ?? 0;
                $mod = $event['mod'] ?? 0;

                // Propagate key event to root component
                if ($this->rootComponent) {
                    $this->rootComponent->handleKeyDown($keycode, $mod);
                }
                break;

            case SDL_EVENT_TEXT_INPUT:
                $text = $event['text'] ?? '';

                // Propagate text input to root component
                if ($this->rootComponent && !empty($text)) {
                    $this->rootComponent->handleTextInput($text);
                }
                break;

            case SDL_EVENT_WINDOW_RESIZED:
                // Update window dimensions (from event data)
                $newWidth = $event['data1'] ?? $this->width;
                $newHeight = $event['data2'] ?? $this->height;
                $this->width = $newWidth;
                $this->height = $newHeight;

                // Update text renderer framebuffer / DPI
                if ($this->textRenderer && $this->textRenderer->isInitialized()) {
                    $this->textRenderer->updateFramebuffer($newWidth, $newHeight);
                }
                $this->viewport->x = 0;
                $this->viewport->y = 0;
                $this->viewport->windowWidth = $newWidth * $this->viewport->uiScale;
                $this->viewport->width = $newWidth * $this->viewport->uiScale;
                $this->viewport->height = $newHeight * $this->viewport->uiScale;
                $this->viewport->windowHeight = $newHeight * $this->viewport->uiScale;
                $this->shouldBeReLayouted = true;
                if ($this->onResize) {
                    ($this->onResize)($this);
                }
                break;

            case SDL_EVENT_MOUSE_MOTION:
                // Convert physical pixels to logical coordinates using uiScale
                $newMouseX = ((float) ($event['x'] ?? 0)) * $this->uiScale;
                $newMouseY = ((float) ($event['y'] ?? 0)) * $this->uiScale;

                $this->mouseX = $newMouseX;
                $this->mouseY = $newMouseY;

                // Check overlays first (in reverse z-index order - highest first)
                if ($this->rootComponent) {
                    $overlays = $this->rootComponent->collectOverlays();
                    usort($overlays, fn($a, $b) => $b->getZIndex() <=> $a->getZIndex());

                    $handled = false;
                    foreach ($overlays as $overlay) {
                        if ($overlay->isVisible()) {
                            $overlay->handleMouseMove($this->mouseX, $this->mouseY);
                            $handled = true;
                            break;
                        }
                    }

                    // If overlay is visible, send fake event to background to clear hover states
                    if ($handled) {
                        $this->rootComponent->handleMouseMove(-1000, -1000);
                    } else {
                        // If no overlay handled it, propagate to normal components
                        $this->rootComponent->handleMouseMove($this->mouseX, $this->mouseY);
                    }
                }
                break;

            case SDL_EVENT_MOUSE_BUTTON_DOWN:
                $button = $event['button'] ?? 0;

                if ($button === 1) {
                    $this->leftButtonDown = true;
                }

                // Check overlays first (in reverse z-index order - highest first)
                if ($this->rootComponent) {
                    $overlays = $this->rootComponent->collectOverlays();
                    usort($overlays, fn($a, $b) => $b->getZIndex() <=> $a->getZIndex());

                    $handled = false;
                    foreach ($overlays as $overlay) {
                        if (
                            $overlay->isVisible() &&
                                $overlay->handleMouseClick($this->mouseX, $this->mouseY, $button)
                        ) {
                            $handled = true;
                            break;
                        }
                    }

                    // If no overlay handled it, propagate to normal components
                    if (!$handled) {
                        $this->rootComponent->handleMouseClick($this->mouseX, $this->mouseY, $button);
                    }
                }
                break;

            case SDL_EVENT_MOUSE_BUTTON_UP:
                $button = $event['button'] ?? 0;

                if ($button === 1) {
                    $this->leftButtonDown = false;
                }

                // Propagate release to root component
                if ($this->rootComponent) {
                    $this->rootComponent->handleMouseRelease($this->mouseX, $this->mouseY, $button);
                }
                break;

            case SDL_EVENT_MOUSE_WHEEL:
                $deltaY = $event['y'] ?? 0;

                // Check overlays first (in reverse z-index order - highest first)
                if ($this->rootComponent) {
                    $overlays = $this->rootComponent->collectOverlays();
                    usort($overlays, fn($a, $b) => $b->getZIndex() <=> $a->getZIndex());

                    $handled = false;
                    foreach ($overlays as $overlay) {
                        if (
                            $overlay->isVisible() &&
                                $overlay->handleMouseWheel($this->mouseX, $this->mouseY, $deltaY)
                        ) {
                            $handled = true;
                            break;
                        }
                    }

                    // If no overlay handled it, propagate to normal components
                    if (!$handled) {
                        $this->rootComponent->handleMouseWheel($this->mouseX, $this->mouseY, $deltaY);
                    }
                }
                break;
        }
    }

    /**
     * Update mouse position and hover states
     * This is called every frame to ensure hover works even without focus
     */
    public function updateMousePosition(): void
    {
        // With SDL3, mouse position is tracked through SDL_EVENT_MOUSE_MOTION events
        // This method is kept for compatibility but does nothing
        // Hover states are updated in handleEvents() via SDL_EVENT_MOUSE_MOTION
    }

    /**
     * Update window state
     */
    public function update(): void
    {
        if ($this->rootComponent) {
            $this->rootComponent->update();
        }
    }

    /**
     * Layout components
     */
    public function layout(): bool
    {
        if ($this->rootComponent && $this->shouldBeReLayouted) {
            // Always layout for now - we can optimize this later by tracking what changed
            // The shouldBeReLayouted flag was preventing proper layout updates
            $this->rootComponent->setViewport($this->viewport);
            $this->rootComponent->layout($this->textRenderer);
            $this->shouldBeReLayouted = false;

            return true;
        }
        return false;
    }

    /**
     * Render the window
     *
     * Note: With SDL3, we must clear and redraw everything each frame.
     * The optimization comes from dirty tracking which prevents unnecessary
     * state updates and layout recalculations, not from skipping rendering.
     */
    public function render(): void
    {
        if ($this->rootComponent === null) {
            return;
        }

        Profiler::start('window_render');

        if ($this->shouldBeReLayouted) {
            Profiler::start('layout');
            $this->layout();
            Profiler::end('layout');
        }

        sdl_set_render_draw_color($this->renderer, 255, 255, 255, 255);
        sdl_render_clear($this->renderer);

        // Build texture cache for components that need it (after layout)
        Profiler::start('buildTextureCacheRecursive');
        $this->rootComponent->buildTextureCacheRecursive($this->renderer, $this->textRenderer);
        Profiler::end('buildTextureCacheRecursive');

        Profiler::start('render_tree');
        $this->rootComponent->render($this->renderer, $this->textRenderer);
        $this->rootComponent->renderContent($this->renderer, $this->textRenderer);
        Profiler::end('render_tree');

        $overlays = $this->rootComponent->collectOverlays();

        if (!empty($overlays)) {
            usort($overlays, static fn($a, $b) => $a->getZIndex() <=> $b->getZIndex());

            foreach ($overlays as $overlay) {
                if (!$overlay->isVisible()) {
                    continue;
                }

                $overlay->render($this->renderer, $this->textRenderer);
                $overlay->renderContent($this->renderer, $this->textRenderer);
            }
        }

        $this->rootComponent->markClean();

        $this->updateFps();

        sdl_render_present($this->renderer);

        Profiler::end('window_render');

        // Report profiling data periodically
        Profiler::report();
    }

    /**
     * Clean up resources
     */
    public function cleanup(): void
    {
        if ($this->textRenderer) {
            $this->textRenderer->free();
            $this->textRenderer = null; // Prevent destructor from running again
        }
    }

    /**
     * Destroy the window and all resources
     * This explicitly closes the SDL window
     */
    public function destroy(): void
    {
        // First clean up text renderer
        $this->cleanup();

        // Explicitly destroy SDL renderer and window
        if ($this->renderer) {
            sdl_destroy_renderer($this->renderer);
            $this->renderer = null;
        }

        if ($this->window) {
            sdl_destroy_window($this->window);
            $this->window = null;
        }
    }

    public function setShouldBeReLayouted(bool $shouldBeReLayouted): void
    {
        $this->shouldBeReLayouted = $shouldBeReLayouted;
    }

    public function getViewport(): Viewport
    {
        return $this->viewport;
    }

    public function setOnResize(callable $callback): void
    {
        $this->onResize = $callback;
    }

    public function setOnFpsChange(null|callable $callback): void
    {
        $this->onFpsChange = $callback;
    }

    public function getCurrentFps(): float
    {
        return $this->currentFps;
    }

    private function updateFps(): void
    {
        $this->frameCounter++;
        $now = microtime(true);
        $elapsed = $now - $this->lastFpsUpdate;

        if ($elapsed >= 1.0) {
            $this->currentFps = $this->frameCounter / $elapsed;
            $this->frameCounter = 0;
            $this->lastFpsUpdate = $now;

            if ($this->onFpsChange !== null) {
                ($this->onFpsChange)($this->currentFps);
            }
        }
    }
}
