<?php

namespace PHPNative\Ui\Widget;

use PHPNative\Framework\TextRenderer;
use PHPNative\Tailwind\Style\Background;
use PHPNative\Tailwind\Style\Border;
use PHPNative\Tailwind\Style\Padding;
use PHPNative\Tailwind\Style\Text;

class TextArea extends Container
{
    private const SCROLLBAR_WIDTH = 12;
    private const SCROLLBAR_MIN_SIZE = 20;

    private array $lines = [''];
    private int $cursorLine = 0;
    private int $cursorCol = 0;
    private bool $focused = false;
    private int $cursorBlinkTimer = 0;
    private bool $cursorVisible = true;
    private int $scrollOffsetY = 0;
    private int $lineHeight = 20;
    private int $selectionStartLine = -1;
    private int $selectionStartCol = -1;
    private int $selectionEndLine = -1;
    private int $selectionEndCol = -1;

    private bool $isDraggingScrollbar = false;
    private float $dragStartY = 0.0;
    private int $scrollStartOffsetY = 0;

    public function __construct(
        public string $value = '',
        public string $placeholder = '',
        string $style = '',
    ) {
        parent::__construct($style);

        if (!empty($value)) {
            $this->lines = explode("\n", $value);
        }
    }

    public function setValue(string $value): void
    {
        $this->value = $value;
        $this->lines = empty($value) ? [''] : explode("\n", $value);
        $this->cursorLine = 0;
        $this->cursorCol = 0;
        $this->clearSelection();
        $this->markDirty(true);
    }

    public function getValue(): string
    {
        return implode("\n", $this->lines);
    }

    public function setFocused(bool $focused): void
    {
        $this->focused = $focused;
        $this->cursorVisible = true;
        $this->cursorBlinkTimer = 0;
        $this->markDirty(true);
    }

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

    public function handleTextInput(string $text): void
    {
        if (!$this->focused || !$this->visible) {
            return;
        }

        $this->deleteSelection();

        $currentLine = $this->lines[$this->cursorLine];
        $before = mb_substr($currentLine, 0, $this->cursorCol);
        $after = mb_substr($currentLine, $this->cursorCol);

        $this->lines[$this->cursorLine] = $before . $text . $after;
        $this->cursorCol += mb_strlen($text);

        $this->value = $this->getValue();
        $this->resetCursorBlink();
        $this->markDirty(true);
    }

    public function handleKeyDown(int $keycode, int $mod = 0): bool
    {
        if (!$this->focused || !$this->visible) {
            return false;
        }

        $shift = ($mod & KMOD_SHIFT) !== 0;
        $ctrl = ($mod & KMOD_CTRL) !== 0;

        if (!$shift && $this->hasSelection()) {
            $this->clearSelection();
        }

        switch ($keycode) {
            case \SDLK_RETURN:
                //case \SDLK_KP_ENTER:
                $this->handleEnter();
                return true;

            case \SDLK_BACKSPACE:
                $this->handleBackspace();
                return true;

            case \SDLK_DELETE:
                $this->handleDelete();
                return true;

            case \SDLK_LEFT:
                $this->handleLeft($shift);
                return true;

            case \SDLK_RIGHT:
                $this->handleRight($shift);
                return true;

            case \SDLK_UP:
                $this->handleUp($shift);
                return true;

            case \SDLK_DOWN:
                $this->handleDown($shift);
                return true;

            case \SDLK_HOME:
                $this->handleHome($shift);
                return true;

            case \SDLK_END:
                $this->handleEnd($shift);
                return true;

            case \SDLK_A:
                if ($ctrl) {
                    $this->selectAll();
                    return true;
                }
                break;

            case \SDLK_C:
                if ($ctrl && $this->hasSelection()) {
                    $this->copyToClipboard();
                    return true;
                }
                break;

            case \SDLK_X:
                if ($ctrl && $this->hasSelection()) {
                    $this->copyToClipboard();
                    $this->deleteSelection();
                    return true;
                }
                break;

            case \SDLK_V:
                if ($ctrl) {
                    $this->pasteFromClipboard();
                    return true;
                }
                break;
        }

        return false;
    }

    public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
    {
        if (!$this->visible) {
            return false;
        }

        // Handle click on scrollbar (if present)
        $contentHeight = count($this->lines) * $this->lineHeight;
        $viewportHeight = $this->contentViewport->height;

        if ($contentHeight > $viewportHeight) {
            $scrollbarX = ($this->contentViewport->x + $this->contentViewport->width) - self::SCROLLBAR_WIDTH;
            $scrollbarY = $this->contentViewport->y;

            if (
                $mouseX >= $scrollbarX &&
                $mouseX <= ($scrollbarX + self::SCROLLBAR_WIDTH) &&
                $mouseY >= $scrollbarY &&
                $mouseY <= ($scrollbarY + $viewportHeight)
            ) {
                $scrollbarHeight = $viewportHeight;
                $thumbHeight = max(
                    self::SCROLLBAR_MIN_SIZE,
                    ($viewportHeight / $contentHeight) * $scrollbarHeight,
                );
                $maxScroll = $contentHeight - $viewportHeight;

                if ($maxScroll > 0) {
                    $thumbRange = $scrollbarHeight - $thumbHeight;

                    // Position scroll to where user clicked (center thumb on click)
                    $clickPos = $mouseY - $scrollbarY - ($thumbHeight / 2);
                    $clickPos = max(0, min($thumbRange, $clickPos));
                    $scrollRatio = $thumbRange > 0 ? ($clickPos / $thumbRange) : 0;
                    $this->scrollOffsetY = (int) max(0, min($maxScroll, $scrollRatio * $maxScroll));
                    // Nur neu rendern, kein neues Layout nötig
                    $this->markDirty(false, false);

                    // Start drag
                    $this->isDraggingScrollbar = true;
                    $this->dragStartY = $mouseY;
                    $this->scrollStartOffsetY = $this->scrollOffsetY;
                }

                return true;
            }
        }

        if (
            $mouseX >= $this->viewport->x &&
                $mouseX <= ($this->viewport->x + $this->viewport->width) &&
                $mouseY >= $this->viewport->y &&
                $mouseY <= ($this->viewport->y + $this->viewport->height)
        ) {
            $this->setFocused(true);

            // Calculate which line was clicked
            $relativeY = ($mouseY - $this->contentViewport->y) + $this->scrollOffsetY;
            $clickedLine = (int) floor($relativeY / $this->lineHeight);
            $clickedLine = max(0, min($clickedLine, count($this->lines) - 1));

            $this->cursorLine = $clickedLine;

            // For now, just move cursor to end of line
            // TODO: Calculate exact column based on mouseX position
            $this->cursorCol = mb_strlen($this->lines[$this->cursorLine]);
            $this->clearSelection();
            $this->resetCursorBlink();
            $this->markDirty(true);

            return true;
        }

        if ($this->focused) {
            $this->setFocused(false);
        }

        return false;
    }

    public function handleMouseMove(float $mouseX, float $mouseY): void
    {
        parent::handleMouseMove($mouseX, $mouseY);

        if (!$this->visible || !$this->isDraggingScrollbar) {
            return;
        }

        $contentHeight = count($this->lines) * $this->lineHeight;
        $viewportHeight = $this->contentViewport->height;

        if ($contentHeight <= $viewportHeight) {
            return;
        }

        $scrollbarHeight = $viewportHeight;
        $thumbHeight = max(
            self::SCROLLBAR_MIN_SIZE,
            ($viewportHeight / $contentHeight) * $scrollbarHeight,
        );

        $maxScroll = $contentHeight - $viewportHeight;
        $thumbRange = $scrollbarHeight - $thumbHeight;

        if ($thumbRange <= 0 || $maxScroll <= 0) {
            return;
        }

        $deltaY = $mouseY - $this->dragStartY;
        $scrollRatioDelta = $deltaY / $thumbRange;
        $newScroll = $this->scrollStartOffsetY + ($scrollRatioDelta * $maxScroll);

        $newOffset = (int) max(0, min($maxScroll, $newScroll));

        if ($newOffset !== $this->scrollOffsetY) {
            $this->scrollOffsetY = $newOffset;
            // Nur neu rendern, kein neues Layout nötig
            $this->markDirty(false, false);
        }
    }

    public function handleMouseRelease(float $mouseX, float $mouseY, int $button): void
    {
        parent::handleMouseRelease($mouseX, $mouseY, $button);
        $this->isDraggingScrollbar = false;
    }

    public function handleMouseWheel(float $mouseX, float $mouseY, float $deltaY): bool
    {
        if (!$this->visible) {
            return false;
        }

        if (
            $mouseX < $this->contentViewport->x ||
            $mouseX > ($this->contentViewport->x + $this->contentViewport->width) ||
            $mouseY < $this->contentViewport->y ||
            $mouseY > ($this->contentViewport->y + $this->contentViewport->height)
        ) {
            return false;
        }

        $contentHeight = count($this->lines) * $this->lineHeight;
        $maxScroll = max(0, $contentHeight - $this->contentViewport->height);

        if ($maxScroll <= 0) {
            return false;
        }

        // Schneller scrollen: mehrere Zeilen pro Tick
        $scrollStep = max($this->lineHeight * 3, 20);

        $oldOffset = $this->scrollOffsetY;
        $this->scrollOffsetY = (int) max(
            0,
            min($maxScroll, $this->scrollOffsetY + ($deltaY * $scrollStep)),
        );

        if ($this->scrollOffsetY === $oldOffset) {
            // Keine tatsächliche Bewegung – parent darf evtl. weiter scrollen
            return false;
        }

        // Nur neu rendern, kein neues Layout nötig
        $this->markDirty(false, false);

        return true;
    }

    public function layout(null|TextRenderer $textRenderer = null): void
    {
        parent::layout($textRenderer);

        if ($textRenderer !== null && $textRenderer->isInitialized()) {
            $textStyle = $this->computedStyles[Text::class] ?? new Text();
            [, $this->lineHeight] = $textRenderer->measureText('Ay', $textStyle->size);
        }
    }

    public function renderContent(&$window, null|TextRenderer $textRenderer = null): void
    {
        if (!$this->visible || $textRenderer === null || !$textRenderer->isInitialized()) {
            return;
        }

        // Setup text rendering
        $textStyle = $this->computedStyles[Text::class] ?? new Text();
        $textColor = $textStyle->color;

        $textRenderer->setColor(
            $textColor->red / 255,
            $textColor->green / 255,
            $textColor->blue / 255,
            $textColor->alpha / 255,
        );

        // Draw selection if active
        if ($this->hasSelection()) {
            $this->renderSelection($window, $textRenderer);
        }

        // Draw text lines
        $y = ((int) $this->contentViewport->y) - $this->scrollOffsetY;

        foreach ($this->lines as $lineIdx => $line) {
            if (($y + $this->lineHeight) < $this->contentViewport->y) {
                $y += $this->lineHeight;
                continue;
            }

            if ($y > ($this->contentViewport->y + $this->contentViewport->height)) {
                break;
            }

            // Draw line (even empty lines to maintain spacing)
            if ($line !== '') {
                $textRenderer->drawText($line, (int) $this->contentViewport->x, $y, $textStyle->size);
            }

            $y += $this->lineHeight;
        }

        // Draw placeholder if empty
        if (empty($this->value) && !empty($this->placeholder) && !$this->focused) {
            $textRenderer->setColor(0.6, 0.6, 0.6, 1.0);
            $textRenderer->drawText(
                $this->placeholder,
                (int) $this->contentViewport->x,
                (int) $this->contentViewport->y,
                $textStyle->size,
            );
        }

        // Draw cursor if focused
        if ($this->focused && $this->cursorVisible) {
            $this->renderCursor($window, $textRenderer);
        }

        // Draw simple vertical scrollbar if content is higher than viewport
        $contentHeight = count($this->lines) * $this->lineHeight;
        $viewportHeight = $this->contentViewport->height;

        if ($contentHeight > $viewportHeight) {
            $scrollbarWidth = self::SCROLLBAR_WIDTH;
            $scrollbarX = ($this->contentViewport->x + $this->contentViewport->width) - $scrollbarWidth;
            $scrollbarY = $this->contentViewport->y;

            // Track
            sdl_set_render_draw_color($window, 220, 220, 220, 255);
            sdl_render_fill_rect($window, [
                'x' => (int) $scrollbarX,
                'y' => (int) $scrollbarY,
                'w' => (int) $scrollbarWidth,
                'h' => (int) $viewportHeight,
            ]);

            // Thumb
            $thumbHeight = max(
                20,
                ($viewportHeight / $contentHeight) * $viewportHeight,
            );
            $maxScroll = $contentHeight - $viewportHeight;
            $scrollRatio = $maxScroll > 0 ? ($this->scrollOffsetY / $maxScroll) : 0;
            $thumbY = $scrollbarY + ($scrollRatio * ($viewportHeight - $thumbHeight));

            sdl_set_render_draw_color($window, 120, 120, 120, 230);
            sdl_render_fill_rect($window, [
                'x' => (int) ($scrollbarX + 1),
                'y' => (int) $thumbY,
                'w' => (int) ($scrollbarWidth - 2),
                'h' => (int) $thumbHeight,
            ]);
        }

        // Update cursor blink
        $this->cursorBlinkTimer++;
        if ($this->cursorBlinkTimer >= 30) {
            $this->cursorVisible = !$this->cursorVisible;
            $this->cursorBlinkTimer = 0;
        }
    }

    private function handleEnter(): void
    {
        $this->deleteSelection();

        $currentLine = $this->lines[$this->cursorLine];
        $before = mb_substr($currentLine, 0, $this->cursorCol);
        $after = mb_substr($currentLine, $this->cursorCol);

        $this->lines[$this->cursorLine] = $before;
        array_splice($this->lines, $this->cursorLine + 1, 0, [$after]);

        $this->cursorLine++;
        $this->cursorCol = 0;

        $this->value = $this->getValue();
        $this->resetCursorBlink();
        $this->markDirty(true);
    }

    private function handleBackspace(): void
    {
        if ($this->hasSelection()) {
            $this->deleteSelection();
            return;
        }

        if ($this->cursorCol > 0) {
            $currentLine = $this->lines[$this->cursorLine];
            $before = mb_substr($currentLine, 0, $this->cursorCol - 1);
            $after = mb_substr($currentLine, $this->cursorCol);
            $this->lines[$this->cursorLine] = $before . $after;
            $this->cursorCol--;
        } elseif ($this->cursorLine > 0) {
            $currentLine = $this->lines[$this->cursorLine];
            $this->cursorLine--;
            $this->cursorCol = mb_strlen($this->lines[$this->cursorLine]);
            $this->lines[$this->cursorLine] .= $currentLine;
            array_splice($this->lines, $this->cursorLine + 1, 1);
        }

        $this->value = $this->getValue();
        $this->resetCursorBlink();
        $this->markDirty(true);
    }

    private function handleDelete(): void
    {
        if ($this->hasSelection()) {
            $this->deleteSelection();
            return;
        }

        if ($this->cursorCol < mb_strlen($this->lines[$this->cursorLine])) {
            $currentLine = $this->lines[$this->cursorLine];
            $before = mb_substr($currentLine, 0, $this->cursorCol);
            $after = mb_substr($currentLine, $this->cursorCol + 1);
            $this->lines[$this->cursorLine] = $before . $after;
        } elseif ($this->cursorLine < (count($this->lines) - 1)) {
            $nextLine = $this->lines[$this->cursorLine + 1];
            $this->lines[$this->cursorLine] .= $nextLine;
            array_splice($this->lines, $this->cursorLine + 1, 1);
        }

        $this->value = $this->getValue();
        $this->resetCursorBlink();
        $this->markDirty(true);
    }

    private function handleLeft(bool $shift): void
    {
        if ($shift) {
            $this->startSelectionIfNeeded();
        }

        if ($this->cursorCol > 0) {
            $this->cursorCol--;
        } elseif ($this->cursorLine > 0) {
            $this->cursorLine--;
            $this->cursorCol = mb_strlen($this->lines[$this->cursorLine]);
        }

        if ($shift) {
            $this->updateSelectionEnd();
        }

        $this->resetCursorBlink();
        $this->markDirty(true);
    }

    private function handleRight(bool $shift): void
    {
        if ($shift) {
            $this->startSelectionIfNeeded();
        }

        if ($this->cursorCol < mb_strlen($this->lines[$this->cursorLine])) {
            $this->cursorCol++;
        } elseif ($this->cursorLine < (count($this->lines) - 1)) {
            $this->cursorLine++;
            $this->cursorCol = 0;
        }

        if ($shift) {
            $this->updateSelectionEnd();
        }

        $this->resetCursorBlink();
        $this->markDirty(true);
    }

    private function handleUp(bool $shift): void
    {
        if ($shift) {
            $this->startSelectionIfNeeded();
        }

        if ($this->cursorLine > 0) {
            $this->cursorLine--;
            $lineLen = mb_strlen($this->lines[$this->cursorLine]);
            $this->cursorCol = min($this->cursorCol, $lineLen);
        }

        if ($shift) {
            $this->updateSelectionEnd();
        }

        $this->resetCursorBlink();
        $this->markDirty(true);
    }

    private function handleDown(bool $shift): void
    {
        if ($shift) {
            $this->startSelectionIfNeeded();
        }

        if ($this->cursorLine < (count($this->lines) - 1)) {
            $this->cursorLine++;
            $lineLen = mb_strlen($this->lines[$this->cursorLine]);
            $this->cursorCol = min($this->cursorCol, $lineLen);
        }

        if ($shift) {
            $this->updateSelectionEnd();
        }

        $this->resetCursorBlink();
        $this->markDirty(true);
    }

    private function handleHome(bool $shift): void
    {
        if ($shift) {
            $this->startSelectionIfNeeded();
        }

        $this->cursorCol = 0;

        if ($shift) {
            $this->updateSelectionEnd();
        }

        $this->resetCursorBlink();
        $this->markDirty(true);
    }

    private function handleEnd(bool $shift): void
    {
        if ($shift) {
            $this->startSelectionIfNeeded();
        }

        $this->cursorCol = mb_strlen($this->lines[$this->cursorLine]);

        if ($shift) {
            $this->updateSelectionEnd();
        }

        $this->resetCursorBlink();
        $this->markDirty(true);
    }

    private function startSelectionIfNeeded(): void
    {
        if (!$this->hasSelection()) {
            $this->selectionStartLine = $this->cursorLine;
            $this->selectionStartCol = $this->cursorCol;
        }
    }

    private function updateSelectionEnd(): void
    {
        $this->selectionEndLine = $this->cursorLine;
        $this->selectionEndCol = $this->cursorCol;
    }

    private function clearSelection(): void
    {
        $this->selectionStartLine = -1;
        $this->selectionStartCol = -1;
        $this->selectionEndLine = -1;
        $this->selectionEndCol = -1;
        $this->markDirty(true);
    }

    private function hasSelection(): bool
    {
        return (
            $this->selectionStartLine >= 0 &&
            $this->selectionEndLine >= 0 &&
            !(

                    $this->selectionStartLine === $this->selectionEndLine &&
                    $this->selectionStartCol === $this->selectionEndCol

            )
        );
    }

    private function selectAll(): void
    {
        $this->selectionStartLine = 0;
        $this->selectionStartCol = 0;
        $this->selectionEndLine = count($this->lines) - 1;
        $this->selectionEndCol = mb_strlen($this->lines[$this->selectionEndLine]);
        $this->markDirty(true);
    }

    private function deleteSelection(): void
    {
        if (!$this->hasSelection()) {
            return;
        }

        [$startLine, $startCol, $endLine, $endCol] = $this->normalizeSelection();

        if ($startLine === $endLine) {
            $line = $this->lines[$startLine];
            $before = mb_substr($line, 0, $startCol);
            $after = mb_substr($line, $endCol);
            $this->lines[$startLine] = $before . $after;
        } else {
            $firstLine = mb_substr($this->lines[$startLine], 0, $startCol);
            $lastLine = mb_substr($this->lines[$endLine], $endCol);
            $this->lines[$startLine] = $firstLine . $lastLine;
            array_splice($this->lines, $startLine + 1, $endLine - $startLine);
        }

        $this->cursorLine = $startLine;
        $this->cursorCol = $startCol;
        $this->clearSelection();
        $this->value = $this->getValue();
        $this->markDirty(true);
    }

    private function normalizeSelection(): array
    {
        $startLine = $this->selectionStartLine;
        $startCol = $this->selectionStartCol;
        $endLine = $this->selectionEndLine;
        $endCol = $this->selectionEndCol;

        if ($startLine > $endLine || $startLine === $endLine && $startCol > $endCol) {
            return [$endLine, $endCol, $startLine, $startCol];
        }

        return [$startLine, $startCol, $endLine, $endCol];
    }

    private function copyToClipboard(): void
    {
        if (!$this->hasSelection()) {
            return;
        }

        [$startLine, $startCol, $endLine, $endCol] = $this->normalizeSelection();

        if ($startLine === $endLine) {
            $text = mb_substr($this->lines[$startLine], $startCol, $endCol - $startCol);
        } else {
            $selectedLines = [];
            $selectedLines[] = mb_substr($this->lines[$startLine], $startCol);

            for ($i = $startLine + 1; $i < $endLine; $i++) {
                $selectedLines[] = $this->lines[$i];
            }

            $selectedLines[] = mb_substr($this->lines[$endLine], 0, $endCol);
            $text = implode("\n", $selectedLines);
        }

        if (function_exists('sdl_set_clipboard_text')) {
            sdl_set_clipboard_text($text);
        }
    }

    private function pasteFromClipboard(): void
    {
        if (!function_exists('sdl_get_clipboard_text')) {
            return;
        }

        $this->deleteSelection();

        $text = sdl_get_clipboard_text();
        if (empty($text)) {
            return;
        }

        $pasteLines = explode("\n", $text);

        if (count($pasteLines) === 1) {
            $currentLine = $this->lines[$this->cursorLine];
            $before = mb_substr($currentLine, 0, $this->cursorCol);
            $after = mb_substr($currentLine, $this->cursorCol);
            $this->lines[$this->cursorLine] = $before . $pasteLines[0] . $after;
            $this->cursorCol += mb_strlen($pasteLines[0]);
        } else {
            $currentLine = $this->lines[$this->cursorLine];
            $before = mb_substr($currentLine, 0, $this->cursorCol);
            $after = mb_substr($currentLine, $this->cursorCol);

            $this->lines[$this->cursorLine] = $before . $pasteLines[0];

            $insertLines = [];
            for ($i = 1; $i < (count($pasteLines) - 1); $i++) {
                $insertLines[] = $pasteLines[$i];
            }

            $lastPasteLine = $pasteLines[count($pasteLines) - 1];
            $insertLines[] = $lastPasteLine . $after;

            array_splice($this->lines, $this->cursorLine + 1, 0, $insertLines);

            $this->cursorLine += count($pasteLines) - 1;
            $this->cursorCol = mb_strlen($lastPasteLine);
        }

        $this->value = $this->getValue();
        $this->resetCursorBlink();
        $this->markDirty(true);
    }

    private function renderCursor(&$window, TextRenderer $textRenderer): void
    {
        $textStyle = $this->computedStyles[Text::class] ?? new Text();

        $y = (((int) $this->contentViewport->y) + ($this->cursorLine * $this->lineHeight)) - $this->scrollOffsetY;

        if (
            $y < $this->contentViewport->y ||
                ($y + $this->lineHeight) > ($this->contentViewport->y + $this->contentViewport->height)
        ) {
            return;
        }

        $beforeCursor = mb_substr($this->lines[$this->cursorLine], 0, $this->cursorCol);
        [$cursorX] = $textRenderer->measureText($beforeCursor, $textStyle->size);

        sdl_set_render_draw_color($window, 0, 0, 0, 255);
        sdl_render_fill_rect($window, [
            'x' => (int) ($this->contentViewport->x + $cursorX),
            'y' => $y,
            'w' => 2,
            'h' => $this->lineHeight,
        ]);
    }

    private function renderSelection(&$window, TextRenderer $textRenderer): void
    {
        [$startLine, $startCol, $endLine, $endCol] = $this->normalizeSelection();

        $textStyle = $this->computedStyles[Text::class] ?? new Text();

        sdl_set_render_draw_color($window, 100, 150, 255, 128);

        for ($lineIdx = $startLine; $lineIdx <= $endLine; $lineIdx++) {
            $y = (((int) $this->contentViewport->y) + ($lineIdx * $this->lineHeight)) - $this->scrollOffsetY;

            if (
                ($y + $this->lineHeight) < $this->contentViewport->y ||
                    $y > ($this->contentViewport->y + $this->contentViewport->height)
            ) {
                continue;
            }

            $lineStart = $lineIdx === $startLine ? $startCol : 0;
            $lineEnd = $lineIdx === $endLine ? $endCol : mb_strlen($this->lines[$lineIdx]);

            $beforeSelection = mb_substr($this->lines[$lineIdx], 0, $lineStart);
            $selectedText = mb_substr($this->lines[$lineIdx], $lineStart, $lineEnd - $lineStart);

            [$startX] = $textRenderer->measureText($beforeSelection, $textStyle->size);
            [$selectionWidth] = $textRenderer->measureText($selectedText, $textStyle->size);

            sdl_render_fill_rect($window, [
                'x' => (int) ($this->contentViewport->x + $startX),
                'y' => $y,
                'w' => (int) $selectionWidth,
                'h' => $this->lineHeight,
            ]);
        }
    }

    private function resetCursorBlink(): void
    {
        $this->cursorVisible = true;
        $this->cursorBlinkTimer = 0;
    }
}
