<?php

/**
 * TextRenderer - Wrapper for RFont text rendering
 */

namespace PHPNative\Framework;

class TextRenderer
{
    private const MAX_TEXTURE_SIZE = 16000;

    private $renderer;
    private bool $initialized = false;
    private string $fontPath = '';
    private int $defaultFontSize = 16;
    /** @var array<int, resource> */
    private array $fonts = [];
    private float $colorR = 1.0;
    private float $colorG = 1.0;
    private float $colorB = 1.0;
    private float $colorA = 1.0;
    private float $pixelRatio = 1.0;

    /**
     * Constructor
     *
     * @param resource $renderer SDL Renderer resource
     */
    public function __construct($renderer)
    {
        $this->renderer = $renderer;
    }

    /**
     * Initialize TTF with a font file
     *
     * @param string|null $fontPath Path to TTF font file
     * @param int $fontSize Default font size
     * @param int $atlasWidth Font atlas texture width (ignored, for compatibility)
     * @param int $atlasHeight Font atlas texture height (ignored, for compatibility)
     * @return bool Success
     */
    public function init(
        null|string $fontPath = null,
        int $fontSize = 16,
        int $atlasWidth = 512,
        int $atlasHeight = 512,
    ): bool {
        // Try to find a suitable font if none provided
        if ($fontPath === null) {
            $fontPath = $this->findSystemFont();
        }

        if (!file_exists($fontPath)) {
            error_log("Font file not found: {$fontPath}");
            return false;
        }

        $this->fontPath = $fontPath;
        $this->defaultFontSize = $fontSize;

        $font = $this->loadFont($fontSize);
        if ($font === null) {
            error_log("Failed to open font: {$fontPath}");
            $this->initialized = false;
            return false;
        }

        $this->initialized = true;

        // Set default color to white
        $this->setColor(1.0, 1.0, 1.0, 1.0);

        return true;
    }

    private function loadFont(int $size): mixed
    {
        $size = max(1, (int) round($size)) * $this->pixelRatio;

        if (isset($this->fonts[$size])) {
            return $this->fonts[$size];
        }

        if ($this->fontPath === '') {
            return null;
        }

        $font = ttf_open_font($this->fontPath, $size);
        if (!$font) {
            return null;
        }

        $this->fonts[$size] = $font;
        return $font;
    }

    private function getFont(int $size): mixed
    {
        if (!$this->initialized) {
            return null;
        }

        return $this->loadFont($size);
    }

    /**
     * Ensure that rendered text does not exceed the maximum supported
     * texture size by truncating very long strings.
     */
    private function truncateToMaxTextureSize(string $text, $font): string
    {
        if ($text === '') {
            return $text;
        }

        $dimensions = ttf_size_text($font, $text);
        $width = (int) ($dimensions['w'] ?? 0);
        $height = (int) ($dimensions['h'] ?? 0);

        if ($width <= self::MAX_TEXTURE_SIZE && $height <= self::MAX_TEXTURE_SIZE) {
            return $text;
        }

        $length = \function_exists('mb_strlen') ? mb_strlen($text) : strlen($text);
        if ($length <= 1) {
            return $text;
        }

        // Estimate how much we can keep based on width ratio
        $scale = self::MAX_TEXTURE_SIZE / max($width, 1);
        $targetLength = max(1, (int) floor($length * $scale));

        $substr = \function_exists('mb_substr') ? 'mb_substr' : 'substr';
        $text = $substr($text, 0, $targetLength);

        // Safety: if still too large, iteratively shrink
        for ($i = 0; $i < 3; $i++) {
            $dimensions = ttf_size_text($font, $text);
            $width = (int) ($dimensions['w'] ?? 0);
            $height = (int) ($dimensions['h'] ?? 0);

            if ($width <= self::MAX_TEXTURE_SIZE && $height <= self::MAX_TEXTURE_SIZE) {
                break;
            }

            $length = \function_exists('mb_strlen') ? mb_strlen($text) : strlen($text);
            if ($length <= 1) {
                break;
            }

            $targetLength = max(1, (int) floor($length * 0.7));
            $text = $substr($text, 0, $targetLength);
        }

        return $text;
    }

    private function getScaledFontSize(int $size): int
    {
        if ($this->pixelRatio <= 1.0) {
            return $size;
        }

        return max(1, (int) round($size * $this->pixelRatio));
    }

    public function setPixelRatio(float $ratio): void
    {
        $this->pixelRatio = max(1.0, $ratio);
    }

    /**
     * Find a system font
     *
     * @return string|null Font path or null if not found
     */
    private function findSystemFont(): null|string
    {
        $fontPaths = [
            // Linux
            '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
            '/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf',
            '/usr/share/fonts/TTF/DejaVuSans.ttf',
            // macOS
            '/System/Library/Fonts/Helvetica.ttc',
            '/Library/Fonts/Arial.ttf',
            // Windows
            'C:/Windows/Fonts/arial.ttf',
            'C:/Windows/Fonts/calibri.ttf',
        ];

        foreach ($fontPaths as $path) {
            if (file_exists($path)) {
                return $path;
            }
        }

        return null;
    }

    /**
     * Draw text at position
     *
     * @param string $text Text to draw
     * @param int $x X position
     * @param int $y Y position
     * @param int|null $size Font size
     */
    public function drawText(string $text, int $x, int $y, null|int $size = null): void
    {
        if (!$this->initialized) {
            return;
        }

        $fontSize = $size ?? $this->defaultFontSize;
        $baseFont = $this->getFont($fontSize);
        if ($baseFont === null) {
            return;
        }

        $renderFontSize = $this->getScaledFontSize($fontSize);
        $renderFont = $this->getFont($renderFontSize) ?? $baseFont;

        // Convert float color (0.0-1.0) to int (0-255)
        $r = (int) ($this->colorR * 255);
        $g = (int) ($this->colorG * 255);
        $b = (int) ($this->colorB * 255);

        // Render text to surface with anti-aliasing (blended mode for smooth text)
        if (strlen($text) < 1) {
            return;
        }
        // Truncate extremely long text so that the resulting texture
        // stays within the GPU's max texture size.
        $safeText = $this->truncateToMaxTextureSize($text, $renderFont);
        if ($safeText === '') {
            return;
        }

        $surface = ttf_render_text_blended($renderFont, $safeText, $r, $g, $b);
        if (!$surface) {
            return;
        }

        // Create texture from surface
        $texture = sdl_create_texture_from_surface($this->renderer, $surface);
        if (!$texture) {
            return;
        }

        // Get text size
        $textSize = ttf_size_text($baseFont, $safeText);

        // Render texture
        sdl_render_texture($this->renderer, $texture, [
            'x' => $x,
            'y' => $y,
            'w' => $textSize['w'],
            'h' => $textSize['h'],
        ]);

        // Note: Texture and surface are automatically cleaned up by PHP resource destructors
    }

    /**
     * Create an SDL texture for the given text using the current color settings.
     *
     * @param string $text Text to render into a texture
     * @return array|null Returns ['texture' => resource, 'width' => int, 'height' => int] or null on failure
     */
    public function createTextTexture(string $text, null|int $size = null): null|array
    {
        if (!$this->initialized) {
            return null;
        }

        $fontSize = $size ?? $this->defaultFontSize;
        $baseFont = $this->getFont($fontSize);
        if ($baseFont === null) {
            return null;
        }

        $renderFontSize = $this->getScaledFontSize($fontSize);
        $renderFont = $this->getFont($renderFontSize) ?? $baseFont;

        $r = (int) ($this->colorR * 255);
        $g = (int) ($this->colorG * 255);
        $b = (int) ($this->colorB * 255);
        if (strlen($text) < 1) {
            return null;
        }
        // Truncate extremely long text so that the resulting texture
        // stays within the GPU's max texture size.
        $safeText = $this->truncateToMaxTextureSize($text, $renderFont);
        if ($safeText === '') {
            return null;
        }

        $surface = ttf_render_text_blended($renderFont, $safeText, $r, $g, $b);
        if (!$surface) {
            return null;
        }

        $texture = sdl_create_texture_from_surface($this->renderer, $surface);
        if (!$texture) {
            return null;
        }

        $dimensions = ttf_size_text($baseFont, $safeText);

        sdl_set_texture_blend_mode($texture, SDL_BLENDMODE_BLEND);
        if (\function_exists('sdl_set_texture_alpha_mod')) {
            sdl_set_texture_alpha_mod($texture, (int) ($this->colorA * 255));
        }

        return [
            'texture' => $texture,
            'width' => (int) $dimensions['w'],
            'height' => (int) $dimensions['h'],
        ];
    }

    /**
     * Set text color
     *
     * @param float $r Red (0.0 - 1.0)
     * @param float $g Green (0.0 - 1.0)
     * @param float $b Blue (0.0 - 1.0)
     * @param float $a Alpha (0.0 - 1.0)
     */
    public function setColor(float $r, float $g, float $b, float $a = 1.0): void
    {
        $this->colorR = $r;
        $this->colorG = $g;
        $this->colorB = $b;
        $this->colorA = $a;
    }

    /**
     * Measure text dimensions
     *
     * @param string $text Text to measure
     * @param int|null $size Font size
     * @return array [width, height]
     */
    public function measureText(string $text, null|int $size = null): array
    {
        if (!$this->initialized) {
            return [0, 0];
        }

        $fontSize = $size ?? $this->defaultFontSize;
        $font = $this->getFont($fontSize);
        if ($font === null) {
            return [0, 0];
        }

        $dimensions = ttf_size_text($font, $text);
        return [(int) $dimensions['w'], (int) $dimensions['h']];
    }

    /**
     * Update framebuffer size (call on window resize)
     *
     * @param int $width Window width
     * @param int $height Window height
     */
    public function updateFramebuffer(int $width, int $height): void
    {
        // SDL3 handles this automatically through the renderer
        // This method is kept for compatibility but does nothing
    }

    /**
     * Free TTF resources
     */
    public function free(): void
    {
        if (!$this->initialized) {
            return;
        }

        foreach ($this->fonts as $font) {
            ttf_close_font($font);
        }

        $this->fonts = [];
        $this->initialized = false;
    }

    /**
     * Check if initialized
     *
     * @return bool
     */
    public function isInitialized(): bool
    {
        return $this->initialized;
    }

    /**
     * Destructor
     */
    public function __destruct()
    {
        $this->free();
    }
}
