<?php
declare(strict_types=1);

/**
 * EnglishSpeak - Shared Sentence Image Resolver
 * File: includes/sentence_image_resolver.php
 *
 * Single source of truth for sentence images.
 *
 * Final rule:
 * 1) If Sentence Library has its own descriptive image for the sentence,
 *    use that image first.
 * 2) Otherwise, try to reuse a suitable image from Word Library.
 * 3) If neither source has an image, return an empty path.
 *
 * Sentence Set and Student Games must NOT implement their own image lookup.
 * They should call this file only.
 */

if (!function_exists('esSirColumns')) {
    function esSirColumns(PDO $pdo, string $table): array
    {
        static $cache = [];

        $key = spl_object_id($pdo) . ':' . $table;

        if (isset($cache[$key])) {
            return $cache[$key];
        }

        try {
            $rows = $pdo
                ->query("SHOW COLUMNS FROM `{$table}`")
                ->fetchAll(PDO::FETCH_ASSOC);
        } catch (Throwable $e) {
            return $cache[$key] = [];
        }

        $columns = [];

        foreach ($rows as $row) {
            $field = trim((string)($row['Field'] ?? ''));

            if ($field !== '') {
                $columns[$field] = true;
            }
        }

        return $cache[$key] = $columns;
    }
}

if (!function_exists('esSirFirstColumn')) {
    function esSirFirstColumn(array $columns, array $candidates): ?string
    {
        foreach ($candidates as $candidate) {
            if (isset($columns[$candidate])) {
                return $candidate;
            }
        }

        return null;
    }
}

if (!function_exists('esSirAssetUrl')) {
    function esSirAssetUrl(string $path): string
    {
        $path = trim($path);

        if ($path === '') {
            return '';
        }

        if (
            str_starts_with($path, 'http://')
            || str_starts_with($path, 'https://')
            || str_starts_with($path, 'data:')
            || str_starts_with($path, '/')
        ) {
            return $path;
        }

        return '/EnglishSpeak/' . ltrim($path, './');
    }
}

if (!function_exists('esSirNormalizeText')) {
    function esSirNormalizeText(string $value): string
    {
        $value = mb_strtolower(trim($value), 'UTF-8');
        $value = preg_replace('/\s+/u', ' ', $value) ?? $value;

        return trim($value);
    }
}

if (!function_exists('esSirContainsWholeWord')) {
    function esSirContainsWholeWord(string $sentence, string $word): bool
    {
        $sentence = esSirNormalizeText($sentence);
        $word = esSirNormalizeText($word);

        if ($sentence === '' || $word === '') {
            return false;
        }

        return preg_match(
            '/(?<![\p{L}\p{N}])'
            . preg_quote($word, '/')
            . '(?![\p{L}\p{N}])/u',
            $sentence
        ) === 1;
    }
}

if (!function_exists('esSirWordImageCandidates')) {
    function esSirWordImageCandidates(PDO $pdo): array
    {
        static $cache = [];

        $key = spl_object_id($pdo);

        if (isset($cache[$key])) {
            return $cache[$key];
        }

        $columns = esSirColumns($pdo, 'words');

        if ($columns === []) {
            return $cache[$key] = [];
        }

        $wordColumn = esSirFirstColumn(
            $columns,
            ['word', 'english_word', 'word_text', 'term', 'title', 'name', 'text']
        );

        $imageColumn = esSirFirstColumn(
            $columns,
            ['image_path', 'image', 'image_url', 'picture_path']
        );

        if ($wordColumn === null || $imageColumn === null) {
            return $cache[$key] = [];
        }

        $conditions = [
            "`{$imageColumn}` IS NOT NULL",
            "TRIM(`{$imageColumn}`) <> ''",
        ];

        if (isset($columns['status'])) {
            $conditions[] = "`status` = 'active'";
        }

        try {
            $rows = $pdo->query(
                "SELECT
                    `{$wordColumn}` AS word_text,
                    `{$imageColumn}` AS image_path
                 FROM words
                 WHERE " . implode(' AND ', $conditions)
            )->fetchAll(PDO::FETCH_ASSOC);
        } catch (Throwable $e) {
            return $cache[$key] = [];
        }

        $candidates = [];

        foreach ($rows as $row) {
            $word = trim((string)($row['word_text'] ?? ''));
            $image = trim((string)($row['image_path'] ?? ''));

            if ($word === '' || $image === '') {
                continue;
            }

            $candidates[] = [
                'word' => $word,
                'image_path' => $image,
                'length' => mb_strlen($word, 'UTF-8'),
            ];
        }

        /*
         * Longer words/phrases first.
         * This only decides which reusable Word Library image is the best fallback.
         * A sentence-specific descriptive image always has higher priority.
         */
        usort(
            $candidates,
            static fn(array $left, array $right): int =>
                $right['length'] <=> $left['length']
        );

        return $cache[$key] = $candidates;
    }
}

if (!function_exists('esResolveSentenceImage')) {
    function esResolveSentenceImage(
        PDO $pdo,
        string $sentenceText,
        ?string $sentenceOwnImage = null
    ): array {
        /*
         * Priority 1:
         * Sentence Library's own descriptive image.
         *
         * Example:
         * "The boy is riding a bike."
         * should use the descriptive sentence image rather than a plain "boy"
         * or "bike" image when that sentence image exists.
         */
        $ownImage = trim((string)$sentenceOwnImage);

        if ($ownImage !== '') {
            return [
                'path' => $ownImage,
                'url' => esSirAssetUrl($ownImage),
                'source' => 'sentence_library',
                'matched_word' => null,
            ];
        }

        /*
         * Priority 2:
         * Reuse an existing Word Library image when it is sufficient
         * to represent the sentence.
         *
         * Examples:
         * "This is a bike." -> bike
         * "This is a fish." -> fish
         */
        foreach (esSirWordImageCandidates($pdo) as $candidate) {
            $word = (string)$candidate['word'];

            if (!esSirContainsWholeWord($sentenceText, $word)) {
                continue;
            }

            $path = (string)$candidate['image_path'];

            return [
                'path' => $path,
                'url' => esSirAssetUrl($path),
                'source' => 'word_library',
                'matched_word' => $word,
            ];
        }

        return [
            'path' => '',
            'url' => '',
            'source' => 'none',
            'matched_word' => null,
        ];
    }
}

if (!function_exists('esResolveSentenceImagePath')) {
    function esResolveSentenceImagePath(
        PDO $pdo,
        string $sentenceText,
        ?string $sentenceOwnImage = null
    ): string {
        $resolved = esResolveSentenceImage(
            $pdo,
            $sentenceText,
            $sentenceOwnImage
        );

        return (string)$resolved['path'];
    }
}

if (!function_exists('esResolveSentenceImageUrl')) {
    function esResolveSentenceImageUrl(
        PDO $pdo,
        string $sentenceText,
        ?string $sentenceOwnImage = null
    ): string {
        $resolved = esResolveSentenceImage(
            $pdo,
            $sentenceText,
            $sentenceOwnImage
        );

        return (string)$resolved['url'];
    }
}
