<?php
declare(strict_types=1);
/**
 * EnglishSpeak — Word Library · Image Import (single page)
 *
 * Teachers prepare images. EnglishSpeak prepares everything else.
 *   Select Folder → Scan → Preview → Import Selected → Done
 *
 * This one file contains: UI + scan check endpoint + batch import endpoint
 * (word creation, Vietnamese meaning, IPA, part of speech, primary-school
 * example + translation, real US pronunciation audio, image attach).
 */

require __DIR__ . '/../partials/auth.php';
require_once __DIR__ . '/../../config/database.php';

$pdo = getDbConnection();
$currentUserId = (int)($_SESSION['user_id'] ?? 0);
$currentUserRole = strtolower(trim((string)($_SESSION['user_role'] ?? $_SESSION['role'] ?? 'teacher')));
$isAdmin = ($currentUserRole === 'admin');

// ════════════════════════════════════════════════════════════
//  HELPERS
// ════════════════════════════════════════════════════════════

/** Detect word from a filename: remove extension, "_"/"-" → space, trim, lowercase. */
function wliWordFromFilename(string $filename): string
{
    $name = pathinfo($filename, PATHINFO_FILENAME);
    $name = str_replace(['_', '-', '+'], ' ', $name);
    $name = preg_replace('/\s+/u', ' ', $name) ?? $name;
    return mb_strtolower(trim($name));
}

function wliSlug(string $word): string
{
    $slug = strtolower(trim((string)preg_replace('/[^a-zA-Z0-9]+/', '-', $word), '-'));
    return $slug !== '' ? $slug : 'word-' . time();
}

/** Simple HTTP GET (cURL, fallback to streams). Returns body or null. */
function wliHttp(string $url, int $timeout = 12): ?string
{
    $ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122 Safari/537.36';

    if (function_exists('curl_init')) {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => $timeout,
            CURLOPT_CONNECTTIMEOUT => 6,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => 0,
            CURLOPT_USERAGENT      => $ua,
        ]);
        $body = curl_exec($ch);
        $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        return ($body !== false && $code >= 200 && $code < 300) ? (string)$body : null;
    }

    $ctx = stream_context_create(['http' => [
        'timeout' => $timeout,
        'header'  => "User-Agent: {$ua}\r\n",
    ], 'ssl' => ['verify_peer' => false, 'verify_peer_name' => false]]);
    $body = @file_get_contents($url, false, $ctx);
    return $body === false ? null : $body;
}

/** English → Vietnamese translation (free endpoint). Returns '' on failure. */
function wliTranslateVi(string $text): string
{
    $text = trim($text);
    if ($text === '') return '';
    $url = 'https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=vi&dt=t&q=' . rawurlencode($text);
    $raw = wliHttp($url, 10);
    if ($raw === null) return '';
    $json = json_decode($raw, true);
    if (!is_array($json) || !isset($json[0]) || !is_array($json[0])) return '';
    $out = '';
    foreach ($json[0] as $chunk) {
        if (isset($chunk[0])) $out .= $chunk[0];
    }
    return trim($out);
}

/** Dictionary lookup → ['phonetic' => '/…/', 'pos' => 'noun', 'definition' => '…'] */
function wliDictionary(string $word): array
{
    $out = ['phonetic' => '', 'pos' => '', 'definition' => ''];
    $lookup = trim($word);
    if ($lookup === '') return $out;

    $raw = wliHttp('https://api.dictionaryapi.dev/api/v2/entries/en/' . rawurlencode($lookup), 10);
    if ($raw === null) return $out;
    $json = json_decode($raw, true);
    if (!is_array($json) || !isset($json[0])) return $out;

    $entry = $json[0];

    // Prefer the US phonetic
    if (!empty($entry['phonetics']) && is_array($entry['phonetics'])) {
        foreach ($entry['phonetics'] as $p) {
            if (!empty($p['text']) && !empty($p['audio']) && str_contains((string)$p['audio'], '-us.')) {
                $out['phonetic'] = (string)$p['text'];
                break;
            }
        }
        if ($out['phonetic'] === '') {
            foreach ($entry['phonetics'] as $p) {
                if (!empty($p['text'])) { $out['phonetic'] = (string)$p['text']; break; }
            }
        }
    }
    if ($out['phonetic'] === '' && !empty($entry['phonetic'])) {
        $out['phonetic'] = (string)$entry['phonetic'];
    }

    $allowed = ['noun','verb','adjective','adverb','pronoun','preposition','conjunction','interjection','determiner','article'];
    if (!empty($entry['meanings'][0]['partOfSpeech'])) {
        $pos = strtolower((string)$entry['meanings'][0]['partOfSpeech']);
        $out['pos'] = in_array($pos, $allowed, true) ? $pos : '';
    }
    if (!empty($entry['meanings'][0]['definitions'][0]['definition'])) {
        $def = (string)$entry['meanings'][0]['definitions'][0]['definition'];
        $out['definition'] = mb_substr($def, 0, 160);
    }

    return $out;
}

function wliArticle(string $word): string
{
    return preg_match('/^[aeiou]/i', $word) ? 'an' : 'a';
}

function wliPlural(string $word): string
{
    if (preg_match('/(s|x|z|ch|sh)$/i', $word)) return $word . 'es';
    if (preg_match('/[^aeiou]y$/i', $word))     return mb_substr($word, 0, -1) . 'ies';
    return $word . 's';
}

/**
 * Primary-school example sentences (Everybody Up / Global Success / i-Learn style).
 * Maximum 3–8 words, textbook patterns only.
 */
function wliExamples(string $word, string $pos): array
{
    $w = trim($word);
    if ($w === '') return [];

    $fixed = [
        'apple'   => ['This is an apple.', 'I like apples.'],
        'banana'  => ['This is a banana.', 'I like bananas.'],
        'cat'     => ['It is a cat.', 'I like cats.'],
        'dog'     => ['It is a dog.', 'I like dogs.'],
        'bird'    => ['Look! A bird!', 'I like birds.'],
        'teacher' => ['She is my teacher.'],
        'book'    => ['This is my book.', 'I like books.'],
        'pen'     => ['This is a pen.'],
        'school'  => ['This is my school.'],
    ];
    if (isset($fixed[mb_strtolower($w)])) return $fixed[mb_strtolower($w)];

    switch ($pos) {
        case 'verb':
            return ['I can ' . $w . '.', 'Let\'s ' . $w . '!'];
        case 'adjective':
            return ['It is ' . $w . '.', 'I am ' . $w . '.'];
        case 'adverb':
            return ['I walk ' . $w . '.'];
        case 'preposition':
        case 'pronoun':
        case 'conjunction':
        case 'interjection':
        case 'determiner':
        case 'article':
            return ['Look! ' . ucfirst($w) . '!'];
        case 'noun':
        default:
            return ['This is ' . wliArticle($w) . ' ' . $w . '.', 'I like ' . wliPlural($w) . '.'];
    }
}

function wliDifficulty(string $word): int
{
    $len = mb_strlen(str_replace(' ', '', $word));
    if ($len <= 4) return 1;
    if ($len <= 6) return 2;
    if ($len <= 8) return 3;
    return 4;
}

/**
 * Generate REAL US pronunciation audio (mp3) and store it.
 * Returns relative path or '' when the audio could not be produced.
 * Never writes fake / placeholder audio.
 */
function wliGenerateAudio(string $word, string $slug): string
{
    $dir = dirname(__DIR__, 2) . '/uploads/word-library/audio';
    if (!is_dir($dir) && !@mkdir($dir, 0775, true) && !is_dir($dir)) return '';

    $url = 'https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&ttsspeed=0.9&tl=en-US&q=' . rawurlencode($word);
    $mp3 = wliHttp($url, 15);

    // Validate a real mp3 payload (ID3 tag or MPEG frame sync)
    if ($mp3 === null || strlen($mp3) < 800) return '';
    $head = substr($mp3, 0, 3);
    if ($head !== 'ID3' && !(ord($mp3[0]) === 0xFF && (ord($mp3[1]) & 0xE0) === 0xE0)) return '';

    $filename = $slug . '-' . substr(md5($word . microtime(true)), 0, 6) . '.mp3';
    $dest = $dir . '/' . $filename;
    if (@file_put_contents($dest, $mp3) === false) return '';
    // Never store a path that does not point at a real file.
    if (!is_file($dest) || filesize($dest) < 800) { @unlink($dest); return ''; }

    return 'uploads/word-library/audio/' . $filename;
}

/** Move an uploaded image into the library. Returns relative web path or ''. */
function wliSaveImage(array $file, string $slug): string
{
    $allowed = ['jpg', 'jpeg', 'png', 'webp'];
    $ext = strtolower(pathinfo((string)$file['name'], PATHINFO_EXTENSION));
    if (!in_array($ext, $allowed, true)) return '';
    if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) return '';

    $tmp = (string)($file['tmp_name'] ?? '');
    if ($tmp === '' || !is_uploaded_file($tmp)) return '';
    if (@getimagesize($tmp) === false) return '';

    $dir = dirname(__DIR__, 2) . '/uploads/word-library/images';
    if (!is_dir($dir) && !@mkdir($dir, 0775, true) && !is_dir($dir)) return '';

    // Safe filename: slug + content hash, no user-supplied characters.
    $filename = $slug . '-' . substr(md5_file($tmp) ?: uniqid(), 0, 8) . '.' . $ext;
    $dest = $dir . '/' . $filename;

    if (!is_file($dest)) {
        if (!move_uploaded_file($tmp, $dest)) return '';
    }
    // Verify the physical file really exists before the path is stored.
    if (!is_file($dest) || filesize($dest) < 1) return '';

    return 'uploads/word-library/images/' . $filename;   // relative web path, "/" separators
}

/** Find Topic by folder name. If missing, create it and preserve ownership. */
function wliFindTopic(PDO $pdo, string $name, int $currentUserId, bool $isAdmin): ?array
{
    $name = trim($name);

    if ($name === '') {
        return null;
    }

    $slug = wliSlug($name);

    // Tìm theo tên
    $stmt = $pdo->prepare(
        'SELECT id, name
         FROM topics
         WHERE LOWER(TRIM(name)) = LOWER(TRIM(?))
         LIMIT 1'
    );
    $stmt->execute([$name]);

    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    if ($row) {
        return [
            'id'   => (int)$row['id'],
            'name' => (string)$row['name'],
        ];
    }

    // Tìm theo slug
    $stmt = $pdo->prepare(
        'SELECT id, name
         FROM topics
         WHERE LOWER(TRIM(slug)) = LOWER(TRIM(?))
         LIMIT 1'
    );
    $stmt->execute([$slug]);

    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    if ($row) {
        return [
            'id'   => (int)$row['id'],
            'name' => (string)$row['name'],
        ];
    }

    // Không có thì tự tạo Topic theo tên thư mục.
    // Admin-created Topics remain shared (owner NULL); Teacher-created Topics keep the Teacher owner.
    $stmt = $pdo->prepare(
        'INSERT INTO topics
            (name, slug, status, created_by_teacher_id, created_at, updated_at)
         VALUES
            (?, ?, "active", ?, NOW(), NOW())'
    );
    $stmt->execute([
        $name,
        $slug,
        $isAdmin ? null : ($currentUserId > 0 ? $currentUserId : null),
    ]);

    return [
        'id'   => (int)$pdo->lastInsertId(),
        'name' => $name,
    ];
}

/** Link a word to a topic once. Returns true only when a new row was inserted. */
function wliLinkTopic(PDO $pdo, int $topicId, int $wordId): bool
{
    if ($topicId <= 0 || $wordId <= 0) return false;

    $chk = $pdo->prepare('SELECT id FROM topic_words WHERE topic_id = ? AND word_id = ? LIMIT 1');
    $chk->execute([$topicId, $wordId]);
    if ($chk->fetchColumn()) return false;      // relationship already exists → keep it

    $pdo->prepare('INSERT INTO topic_words (topic_id, word_id, sort_order) VALUES (?, ?, 0)')
        ->execute([$topicId, $wordId]);
    return true;
}

function wliJson(array $payload): void
{
    while (ob_get_level() > 0) ob_end_clean();
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($payload, JSON_UNESCAPED_UNICODE);
    exit;
}

// ════════════════════════════════════════════════════════════
//  AJAX · SCAN CHECK  (which detected words already exist?)
// ════════════════════════════════════════════════════════════
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'check') {
    $words = json_decode((string)($_POST['words'] ?? '[]'), true);
    $found = [];

    if (is_array($words) && $words) {
        $words = array_values(array_unique(array_filter(array_map('strval', $words))));
        foreach (array_chunk($words, 200) as $chunk) {
            $in = implode(',', array_fill(0, count($chunk), '?'));
            $sql = "SELECT w.id, w.word, w.phonetic, w.meaning_vi, w.cefr_level,
                           w.image_path, w.audio_us_path,
                           (SELECT example_en FROM word_examples we WHERE we.word_id = w.id ORDER BY we.sort_order LIMIT 1) AS example_en
                    FROM words w WHERE LOWER(TRIM(w.word)) IN ({$in})";
            $stmt = $pdo->prepare($sql);
            $stmt->execute(array_map(fn($v) => mb_strtolower(trim($v)), $chunk));
            foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
                $found[mb_strtolower(trim((string)$row['word']))] = [
                    'id'       => (int)$row['id'],
                    'ipa'      => (string)($row['phonetic'] ?? ''),
                    'meaning'  => (string)($row['meaning_vi'] ?? ''),
                    'example'  => (string)($row['example_en'] ?? ''),
                    'cefr'     => (string)($row['cefr_level'] ?? ''),
                    'audio'    => $row['audio_us_path'] ? es_url((string)$row['audio_us_path']) : '',
                    'hasImage' => !empty($row['image_path']),
                ];
            }
        }
    }

    wliJson(['ok' => true, 'existing' => $found]);
}

// ════════════════════════════════════════════════════════════
//  AJAX · IMPORT BATCH
// ════════════════════════════════════════════════════════════
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'import') {
    @set_time_limit(0);
    @ignore_user_abort(true);

    $results = [];
    $names   = $_FILES['files']['name'] ?? [];

    // ── Topic from selected folder name; create automatically if missing ──
    $folderName  = trim((string)($_POST['folder'] ?? ''));
    $topic       = $folderName !== '' ? wliFindTopic($pdo, $folderName, $currentUserId, $isAdmin) : null;
    $topicLinked = 0;

    for ($i = 0, $n = count($names); $i < $n; $i++) {
        $file = [
            'name'     => $_FILES['files']['name'][$i],
            'type'     => $_FILES['files']['type'][$i],
            'tmp_name' => $_FILES['files']['tmp_name'][$i],
            'error'    => $_FILES['files']['error'][$i],
            'size'     => $_FILES['files']['size'][$i],
        ];
        $rowId = (string)($_POST['rows'][$i] ?? (string)$i);

        $detectedWord = wliWordFromFilename((string)$file['name']);
        $word = trim((string)($_POST['edited_word'][$i] ?? $detectedWord));
        $meaningEdited = trim((string)($_POST['edited_meaning'][$i] ?? ''));
        $ipaEdited = trim((string)($_POST['edited_ipa'][$i] ?? ''));
        $cefrEdited = trim((string)($_POST['edited_cefr'][$i] ?? ''));
        $exampleEdited = trim((string)($_POST['edited_example'][$i] ?? ''));
        $updateExisting = (string)($_POST['update_existing'][$i] ?? '0') === '1';

        $allowedCefr = ['Pre-A1', 'A1', 'A2', 'B1', 'B2', 'C1', 'C2'];
        if (!in_array($cefrEdited, $allowedCefr, true)) {
            $cefrEdited = '';
        }

        $res = ['row' => $rowId, 'word' => $word, 'status' => 'error', 'message' => '',
                'meaning' => '', 'ipa' => '', 'cefr' => '', 'example' => '',
                'audio' => '', 'audioOk' => false];

        try {
            if ($word === '') {
                throw new RuntimeException('No word detected in filename.');
            }

            $stmt = $pdo->prepare('SELECT * FROM words WHERE LOWER(TRIM(word)) = ? LIMIT 1');
            $stmt->execute([$word]);
            $existing = $stmt->fetch(PDO::FETCH_ASSOC);

            // ── Existing word → only attach the image when it has none ──
  // ── Existing word ───────────────────────────────────────────
if ($existing) {
    $wordId      = (int)$existing['id'];
    $canManageExisting = $isAdmin || ((int)($existing['created_by_teacher_id'] ?? 0) > 0 && (int)$existing['created_by_teacher_id'] === $currentUserId);
    if (!$canManageExisting) {
        $res['status'] = 'skipped';
        $res['message'] = 'Shared word protected — no changes made.';
        $results[] = $res;
        continue;
    }
    $wasArchived = (($existing['status'] ?? 'active') !== 'active');

    $res['meaning'] = (string)($existing['meaning_vi'] ?? '');
    $res['ipa']     = (string)($existing['phonetic'] ?? '');
    $res['cefr']    = (string)($existing['cefr_level'] ?? '');
    $res['audio']   = !empty($existing['audio_us_path'])
        ? es_url((string)$existing['audio_us_path'])
        : '';
    $res['audioOk'] = $res['audio'] !== '';

    $exStmt = $pdo->prepare(
        'SELECT example_en
         FROM word_examples
         WHERE word_id = ?
         ORDER BY sort_order
         LIMIT 1'
    );
    $exStmt->execute([$wordId]);
    $res['example'] = (string)($exStmt->fetchColumn() ?: '');

    $finalMeaning = $meaningEdited !== '' ? $meaningEdited : (string)($existing['meaning_vi'] ?? '');
    $finalIpa = $ipaEdited !== '' ? $ipaEdited : (string)($existing['phonetic'] ?? '');
    $finalCefr = $cefrEdited !== '' ? $cefrEdited : (string)($existing['cefr_level'] ?? '');
    $finalExample = $exampleEdited !== '' ? $exampleEdited : $res['example'];

    $metadataChanged =
        trim($word) !== trim((string)$existing['word'])
        || trim($finalMeaning) !== trim((string)($existing['meaning_vi'] ?? ''))
        || trim($finalIpa) !== trim((string)($existing['phonetic'] ?? ''))
        || trim($finalCefr) !== trim((string)($existing['cefr_level'] ?? ''))
        || trim($finalExample) !== trim((string)$res['example']);

    if ($updateExisting && $metadataChanged) {
        $pdo->prepare(
            'UPDATE words
             SET word = ?,
                 slug = ?,
                 meaning_vi = ?,
                 phonetic = ?,
                 cefr_level = ?,
                 updated_at = NOW()
             WHERE id = ?'
        )->execute([
            $word,
            wliSlug($word),
            $finalMeaning,
            $finalIpa,
            $finalCefr,
            $wordId,
        ]);

        if ($exampleEdited !== '') {
            $pdo->prepare('DELETE FROM word_examples WHERE word_id = ?')->execute([$wordId]);
            $pdo->prepare(
                'INSERT INTO word_examples
                    (word_id, example_en, example_vi, sort_order, status)
                 VALUES (?, ?, "", 1, "active")'
            )->execute([$wordId, $exampleEdited]);
        }

        $res['meaning'] = $finalMeaning;
        $res['ipa'] = $finalIpa;
        $res['cefr'] = $finalCefr;
        $res['example'] = $finalExample;
    }

    /*
     * Nếu từ đã bị Archive:
     * - khôi phục lại Active
     * - nhận lại ảnh đang tải lên
     * - không báo Skipped
     */
    if ($wasArchived) {
        $imagePath = wliSaveImage($file, wliSlug($word));

        if ($imagePath === '') {
            throw new RuntimeException('Unsupported or unreadable image.');
        }

        $pdo->prepare(
            'UPDATE words
             SET status = "active",
                 image_path = ?,
                 updated_at = NOW()
             WHERE id = ?'
        )->execute([$imagePath, $wordId]);

        $res['status']  = 'updated';
        $res['message'] = 'Archived word restored and image attached.';
    }
    /*
     * Từ đang Active nhưng chưa có ảnh:
     * - gắn ảnh
     */
    elseif (empty($existing['image_path'])) {
        $imagePath = wliSaveImage($file, wliSlug($word));

        if ($imagePath === '') {
            throw new RuntimeException('Unsupported or unreadable image.');
        }

        $pdo->prepare(
            'UPDATE words
             SET image_path = ?,
                 updated_at = NOW()
             WHERE id = ?'
        )->execute([$imagePath, $wordId]);

        $res['status']  = 'updated';
        $res['message'] = 'Image attached.';
    }
    /*
     * Từ đang Active và đã có ảnh:
     * - vẫn bỏ qua để tránh ghi đè nhầm
     */
    else {
        if ($updateExisting && $metadataChanged) {
            $res['status']  = 'updated';
            $res['message'] = 'Metadata updated • Image kept.';
        } elseif ($updateExisting && !$metadataChanged) {
            $res['status']  = 'skipped';
            $res['message'] = 'Already up to date.';
        } else {
            $res['status']  = 'skipped';
            $res['message'] = 'Image already exists • No overwrite selected.';
        }
    }

    // Gắn lại Topic theo tên thư mục
    if ($topic && wliLinkTopic($pdo, $topic['id'], $wordId)) {
        $topicLinked++;
    }

    $results[] = $res;
    continue;
}
            // ── New word → generate everything ──
            $imagePath = wliSaveImage($file, wliSlug($word));
            if ($imagePath === '') {
                throw new RuntimeException('Unsupported or unreadable image.');
            }

            $dict       = wliDictionary($word);
            $pos        = $dict['pos'];
            $meaningVi  = $meaningEdited !== '' ? $meaningEdited : wliTranslateVi($word);
            $shortMean  = $dict['definition'];
            // meaning_vi is NOT NULL without a default → always send a safe value.
            if ($meaningVi === '') $meaningVi = $word;
            $examples   = wliExamples($word, $pos);
            $exampleEn  = $exampleEdited !== '' ? $exampleEdited : ($examples[0] ?? '');
            $exampleEn2 = $examples[1] ?? '';
            $exampleVi  = $exampleEn !== '' ? wliTranslateVi($exampleEn) : '';
            $exampleVi2 = $exampleEn2 !== '' ? wliTranslateVi($exampleEn2) : '';

            $slug = wliSlug($word);
            $slugCheck = $pdo->prepare('SELECT COUNT(*) FROM words WHERE slug = ?');
            $slugCheck->execute([$slug]);
            if ((int)$slugCheck->fetchColumn() > 0) {
                $slug .= '-' . substr(md5($word . microtime(true)), 0, 5);
            }

            // TTS must never block the image / word import.
            $audioPath = '';
            try {
                $audioPath = wliGenerateAudio($word, $slug);
            } catch (Throwable $audioError) {
                $audioPath = '';
            }

            $finalIpa = $ipaEdited !== '' ? $ipaEdited : $dict['phonetic'];

            $pdo->prepare('
                INSERT INTO words (
                    word, slug, phonetic, part_of_speech, meaning_vi,
                    image_path, audio_us_path, difficulty_level,
                    cefr_level, status, notes, created_by_teacher_id, created_at, updated_at
                )
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, "active", ?, ?, NOW(), NOW())
            ')->execute([
                $word, $slug, $finalIpa, $pos, $meaningVi,
                $imagePath, $audioPath, wliDifficulty($word),
                $cefrEdited, $shortMean, ($isAdmin ? null : $currentUserId),
            ]);
            $newId = (int)$pdo->lastInsertId();

            $insEx = $pdo->prepare('INSERT INTO word_examples (word_id, example_en, example_vi, sort_order, status) VALUES (?, ?, ?, ?, "active")');
            if ($exampleEn !== '')  $insEx->execute([$newId, $exampleEn, $exampleVi, 1]);
            if ($exampleEn2 !== '') $insEx->execute([$newId, $exampleEn2, $exampleVi2, 2]);

            if ($topic && wliLinkTopic($pdo, $topic['id'], $newId)) $topicLinked++;

            $res['status']  = 'created';
            $res['meaning'] = $meaningVi;
            $res['ipa']     = $finalIpa;
            $res['cefr']    = $cefrEdited;
            $res['example'] = $exampleEn;
            $res['audio']   = $audioPath !== '' ? es_url($audioPath) : '';
            $res['audioOk'] = $audioPath !== '';
            $res['message'] = $audioPath !== '' ? 'Created.' : 'Created (browser pronunciation).';
        } catch (Throwable $e) {
            $res['status']  = 'error';
            $res['message'] = $e->getMessage();
        }

        $results[] = $res;
    }

    wliJson([
        'ok'          => true,
        'results'     => $results,
        'topicName'   => $topic['name'] ?? $folderName,
        'topicFound'  => $topic !== null,
        'topicLinked' => $topicLinked,
    ]);
}

// ════════════════════════════════════════════════════════════
//  PAGE
// ════════════════════════════════════════════════════════════
$pageTitle       = 'Import Images';
$pageSubtitle    = 'Prepare images — EnglishSpeak prepares everything else';
$activeTeacherNav = 'word-library';

ob_start();
?>

<style>
.wli-overview{
    display:grid;
    grid-template-columns:repeat(4,minmax(0,1fr));
    gap:12px;
    margin:14px 0;
}
.wli-overview-card{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:12px;
    min-height:72px;
    padding:14px 16px;
    border:1px solid #e5e7eb;
    border-radius:15px;
    background:#fff;
}
.wli-overview-card span{
    color:#64748b;
    font-size:12px;
    font-weight:750;
}
.wli-overview-card strong{
    color:#0f172a;
    font-size:24px;
}
.wli-overview-new{background:#ecfdf5;border-color:#bbf7d0}
.wli-overview-new strong{color:#047857}
.wli-overview-duplicate{background:#fff7ed;border-color:#fed7aa}
.wli-overview-duplicate strong{color:#c2410c}
.wli-edit-input,
.wli-edit-select{
    width:100%;
    min-width:110px;
    min-height:34px;
    padding:6px 8px;
    border:1px solid #dbe2ea;
    border-radius:8px;
    background:#fff;
    font:inherit;
    font-size:12px;
}
.wli-edit-input:focus,
.wli-edit-select:focus{
    outline:none;
    border-color:#60a5fa;
    box-shadow:0 0 0 3px rgba(59,130,246,.12);
}
.wli-update-check{
    display:flex;
    align-items:center;
    gap:7px;
    color:#475569;
    font-size:11px;
    font-weight:700;
}
.wli-update-check input{
    width:16px;
    height:16px;
}
.wli-action-cell{
    display:grid;
    gap:6px;
}
.wli-action-label{
    display:inline-flex;
    width:max-content;
    padding:4px 8px;
    border-radius:999px;
    background:#f1f5f9;
    color:#475569;
    font-size:10px;
    font-weight:850;
}
.wli-action-create{background:#ecfdf5;color:#047857}
.wli-action-update{background:#eff6ff;color:#1d4ed8}
.wli-action-skip{background:#fff7ed;color:#c2410c}
.wli-cefr-prea1{background:#ecfdf5;color:#047857}
.wli-cefr-a1{background:#eff6ff;color:#1d4ed8}
.wli-cefr-a2{background:#eef2ff;color:#4338ca}
.wli-cefr-b1{background:#faf5ff;color:#7e22ce}
.wli-cefr-b2{background:#fdf2f8;color:#be185d}
.wli-cefr-c1{background:#fff7ed;color:#c2410c}
.wli-cefr-c2{background:#fef2f2;color:#b91c1c}
.wli-cefr-badge{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    min-width:52px;
    padding:5px 9px;
    border-radius:999px;
    background:#eef2ff;
    color:#4338ca;
    font-size:11px;
    font-weight:850;
}
.wli-result-summary{
    margin-bottom:12px;
    padding:12px 14px;
    border:1px solid #dbeafe;
    border-radius:12px;
    background:#eff6ff;
    color:#1d4ed8;
    font-size:12px;
    line-height:1.55;
}
.wli-result-section{
    margin-top:12px;
    padding-top:10px;
    border-top:1px solid #eef2f7;
}
.wli-result-section-title{
    display:block;
    margin-bottom:6px;
    color:#0f172a;
    font-weight:850;
}
.wli-result-item{
    display:flex;
    align-items:flex-start;
    gap:7px;
    padding:4px 0;
    color:#475569;
    font-size:12px;
    line-height:1.4;
}
.wli-result-item small{
    display:block;
    margin-top:2px;
    color:#94a3b8;
}
.wli-result-item-created{color:#047857}
.wli-result-item-updated{color:#1d4ed8}
.wli-result-item-skipped{color:#64748b}
.wli-result-item-error{color:#b91c1c}
.wli-result-details{
    margin-top:10px;
    border-top:1px solid #eef2f7;
}
.wli-result-details summary{
    cursor:pointer;
    padding:10px 0 6px;
    color:#0f172a;
    font-size:12px;
    font-weight:850;
    list-style:none;
}
.wli-result-details summary::-webkit-details-marker{display:none}
.wli-result-details summary::after{
    content:'▾';
    float:right;
    color:#94a3b8;
}
.wli-result-details[open] summary::after{content:'▴'}
.wli-result-inline{
    margin-top:10px;
    padding-top:10px;
    border-top:1px solid #eef2f7;
}
.wli-result-inline-title{
    display:block;
    margin-bottom:6px;
    color:#0f172a;
    font-size:12px;
    font-weight:850;
}
@media(max-width:900px){
    .wli-overview{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media(max-width:520px){
    .wli-overview{grid-template-columns:1fr}
}
</style>

<div class="wl-page-header">
    <div>
        <span class="section-kicker">Word Library</span>
        <h2>Import Images</h2>
    </div>
    <a href="index.php" class="button button-sm button-secondary">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M19 12H5m7-7-7 7 7 7"/></svg>
        Back to Library
    </a>
</div>

<!-- Drop / Browse folder -->
<div class="wli-drop" id="wliDrop">
    <input type="file" id="wliFolderInput" webkitdirectory directory multiple accept=".png,.jpg,.jpeg,.webp">
    <div class="wli-drop-icon">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/><circle cx="9" cy="13" r="1.5"/><path d="m21 18-5-5-4 4-2-2-4 4"/></svg>
    </div>
    <div class="wli-drop-title">Drag a folder here or <strong>browse folder</strong></div>
    <div class="wli-drop-hint">PNG · JPG · JPEG · WEBP — other files are ignored automatically</div>
</div>

<!-- Scan overview -->
<div class="wli-overview" id="wliOverview" hidden>
    <div class="wli-overview-card">
        <span>Total images</span>
        <strong id="wliTotalCount">0</strong>
    </div>
    <div class="wli-overview-card">
        <span>Selected</span>
        <strong id="wliSelectedCount">0</strong>
    </div>
    <div class="wli-overview-card wli-overview-new">
        <span>New words</span>
        <strong id="wliNewCount">0</strong>
    </div>
    <div class="wli-overview-card wli-overview-duplicate">
        <span>Existing words</span>
        <strong id="wliDuplicateCount">0</strong>
    </div>
</div>

<!-- Toolbar -->
<div class="wli-toolbar" id="wliToolbar" hidden>
    <div>
        <div class="wli-summary" id="wliSummary">No images scanned yet.</div>
        <div style="margin-top:4px;color:#64748b;font-size:11px;">
            You can edit Word, Meaning, IPA, CEFR and Example before importing.
            Existing words are protected. Editing a field automatically checks “Update existing”.
        </div>
    </div>
    <div class="wli-toolbar-actions">
        <button type="button" class="button button-sm button-secondary" id="wliSelectAll">Select All</button>
        <button type="button" class="button button-sm button-secondary" id="wliClearSelection">Clear Selection</button>
        <button type="button" class="button button-sm button-primary" id="wliImport">Import Selected</button>
    </div>
</div>

<!-- Top (synchronized) scrollbar -->
<div class="wli-scroll-top" id="wliScrollTop" hidden><div id="wliScrollTopInner"></div></div>

<!-- Image table -->
<div class="wli-table-wrap" id="wliTableWrap" hidden>
    <table class="data-table wli-table" id="wliTable">
        <thead>
            <tr>
                <th style="width:38px;"><input type="checkbox" id="wliCheckAll" checked></th>
                <th style="width:74px;">Preview</th>
                <th style="min-width:180px;">Filename</th>
                <th style="min-width:140px;">Detected Word</th>
                <th style="min-width:180px;">Meaning</th>
                <th style="min-width:130px;">IPA</th>
                <th style="min-width:100px;">CEFR</th>
                <th style="min-width:220px;">Example</th>
                <th style="width:90px;">Audio</th>
                <th style="width:150px;">Action</th>
                <th style="width:130px;">Status</th>
            </tr>
        </thead>
        <tbody id="wliBody"></tbody>
    </table>
</div>

<!-- Result popup -->
<div class="wl-modal-overlay" id="wliResultOverlay">
    <div class="wl-modal" style="max-width:410px;">
        <div class="wl-modal-header">
            <h3>Import Finished</h3>
            <button class="wl-modal-close" type="button" id="wliResultClose">&times;</button>
        </div>
        <div class="wli-result-list" id="wliResultList"></div>
        <div class="button-group" style="justify-content:flex-end;margin-top:18px;">
            <a href="index.php" class="button button-primary button-sm">Return to Word Library</a>
            <button type="button" class="button button-secondary button-sm" id="wliStay">Import More Images</button>
        </div>
    </div>
</div>

<script>
(function () {
    'use strict';

    var ALLOWED   = ['png', 'jpg', 'jpeg', 'webp'];
    var BATCH     = 4;                     // files per import request
    var rows      = [];                    // {id, file, word, tr, checked}
    var seenFiles = Object.create(null);   // duplicate filename / image guard
    var seenWords = Object.create(null);   // duplicate word guard
    var folderMap = new WeakMap();         // reliable folder name for drag-and-drop Files
    var counter   = 0;
    var importing = false;

    var drop        = document.getElementById('wliDrop');
    var input       = document.getElementById('wliFolderInput');
    var overview    = document.getElementById('wliOverview');
    var totalCount  = document.getElementById('wliTotalCount');
    var selectedCount = document.getElementById('wliSelectedCount');
    var newCount    = document.getElementById('wliNewCount');
    var duplicateCount = document.getElementById('wliDuplicateCount');
    var toolbar     = document.getElementById('wliToolbar');
    var summary     = document.getElementById('wliSummary');
    var tableWrap   = document.getElementById('wliTableWrap');
    var table       = document.getElementById('wliTable');
    var tbody       = document.getElementById('wliBody');
    var scrollTop   = document.getElementById('wliScrollTop');
    var scrollInner = document.getElementById('wliScrollTopInner');
    var checkAll    = document.getElementById('wliCheckAll');
    var overlay     = document.getElementById('wliResultOverlay');

    function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) {
        return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c];
    }); }

    function ext(name) { var i = name.lastIndexOf('.'); return i < 0 ? '' : name.slice(i + 1).toLowerCase(); }

    function detectWord(filename) {
        var base = filename.replace(/\.[^.]+$/, '');
        return base.replace(/[_\-+]+/g, ' ').replace(/\s+/g, ' ').trim().toLowerCase();
    }

    function badge(status, text) {
        return '<span class="wli-badge wli-badge-' + status + '">' + esc(text) + '</span>';
    }

    function applyCefrStyle(select) {
        if (!select) return;
        select.className = 'wli-edit-select wli-cefr';
        var value = String(select.value || '').toLowerCase().replace('-', '');
        if (value) select.classList.add('wli-cefr-' + value);
    }

    // Selected folder name → Topic name (first segment of the relative path).
    // Uses WeakMap for drag-and-drop because custom properties on File objects
    // are not reliable in every Chromium build.
	function resetImportList() {
    rows.forEach(function (r) {
        if (r.tr) {
            var img = r.tr.querySelector('.wli-thumb');
            if (img && img.src && img.src.indexOf('blob:') === 0) {
                URL.revokeObjectURL(img.src);
            }
        }
    });

    rows = [];
    seenFiles = Object.create(null);
    seenWords = Object.create(null);
    counter = 0;

    tbody.innerHTML = '';
    summary.textContent = 'No images scanned yet.';
    checkAll.checked = true;

    overview.hidden = true;
    toolbar.hidden = true;
    tableWrap.hidden = true;
    scrollTop.hidden = true;

    totalCount.textContent = '0';
    selectedCount.textContent = '0';
    newCount.textContent = '0';
    duplicateCount.textContent = '0';
}
    function folderOf(file, hint) {
        var mapped = folderMap.get(file);
        if (mapped) return String(mapped).trim();

        var rel = file.webkitRelativePath || file.relativePath || '';
        if (rel && rel.indexOf('/') !== -1) return rel.split('/')[0].trim();

        return String(hint || '').trim();
    }

    // ── Scan ────────────────────────────────────────────────
    function scan(files, folderHint) {
        var added = [], ignored = 0, dupes = 0;

        for (var i = 0; i < files.length; i++) {
            var f = files[i];
            if (ALLOWED.indexOf(ext(f.name)) === -1) { ignored++; continue; }

            var key = f.name.toLowerCase() + ':' + f.size;
            if (seenFiles[key]) { dupes++; continue; }
            seenFiles[key] = true;

            var word = detectWord(f.name);
            if (!word) { ignored++; continue; }
            if (seenWords[word]) { dupes++; continue; }   // duplicate word → keep the first file
            seenWords[word] = true;

            added.push({ id: 'r' + (++counter), file: f, word: word,
                         folder: folderOf(f, folderHint), checked: true, done: false,
                         exists: false, checkedOnServer: false, cefr: '',
                         original: { word: word, meaning: '', ipa: '', cefr: '', example: '' } });
        }

        if (!added.length) {
            render([], ignored, dupes);
            return;
        }
        render(added, ignored, dupes);
        checkExisting(added);
    }

    function render(added, ignored, dupes) {
        var frag = document.createDocumentFragment();

        added.forEach(function (r) {
            var tr = document.createElement('tr');
            tr.dataset.id = r.id;
            tr.innerHTML =
                '<td><input type="checkbox" class="wli-row-check" checked></td>' +
                '<td><img class="wli-thumb" alt="" loading="lazy"></td>' +
                '<td class="wli-file">' + esc(r.file.name) + '</td>' +
                '<td><input class="wli-edit-input wli-word-input" value="' + esc(r.word) + '"></td>' +
                '<td><input class="wli-edit-input wli-meaning" value=""></td>' +
                '<td><input class="wli-edit-input wli-ipa" value=""></td>' +
                '<td><select class="wli-edit-select wli-cefr">' +
                    '<option value="">Not assigned</option>' +
                    '<option value="Pre-A1">Pre-A1</option>' +
                    '<option value="A1">A1</option>' +
                    '<option value="A2">A2</option>' +
                    '<option value="B1">B1</option>' +
                    '<option value="B2">B2</option>' +
                    '<option value="C1">C1</option>' +
                    '<option value="C2">C2</option>' +
                '</select></td>' +
                '<td><input class="wli-edit-input wli-example" value=""></td>' +
                '<td class="wli-audio">—</td>' +
                '<td><div class="wli-action-cell">' +
                    '<span class="wli-action-label">Checking…</span>' +
                    '<label class="wli-update-check"><input type="checkbox" class="wli-update-existing">Update existing</label>' +
                '</div></td>' +
                '<td class="wli-status">' + badge('pending', 'Pending') + '</td>';
            tr.querySelector('.wli-thumb').src = URL.createObjectURL(r.file);
            tr.querySelector('.wli-row-check').addEventListener('change', function () {
                r.checked = this.checked;
                updateSummary();
            });

            tr.querySelector('.wli-word-input').addEventListener('input', function () {
                r.word = this.value.trim().toLowerCase();
                var updateBox = tr.querySelector('.wli-update-existing');
                if (r.exists && updateBox) updateBox.checked = true;
            });

            [
                '.wli-meaning',
                '.wli-ipa',
                '.wli-cefr',
                '.wli-example'
            ].forEach(function (selector) {
                var field = tr.querySelector(selector);
                if (!field) return;

                field.addEventListener(
                    field.tagName === 'SELECT' ? 'change' : 'input',
                    function () {
                        if (field.classList.contains('wli-cefr')) {
                            applyCefrStyle(field);
                        }

                        var updateBox = tr.querySelector('.wli-update-existing');
                        if (r.exists && updateBox) updateBox.checked = true;
                    }
                );
            });
            r.tr = tr;
            frag.appendChild(tr);
            rows.push(r);
        });

        tbody.appendChild(frag);

        overview.hidden = false;
        toolbar.hidden = false;
        tableWrap.hidden = false;
        scrollTop.hidden = false;
        syncScrollWidth();
        updateSummary(ignored, dupes);
    }

    function updateSummary(ignored, dupes) {
        var selected = rows.filter(function (r) { return r.checked && !r.done; }).length;
        var existing = rows.filter(function (r) { return r.exists; }).length;
        var checked = rows.filter(function (r) { return r.checkedOnServer; }).length;
        var fresh = Math.max(0, checked - existing);

        var text = rows.length + ' image' + (rows.length === 1 ? '' : 's') + ' · ' + selected + ' selected';
        if (ignored) text += ' · ' + ignored + ' ignored';
        if (dupes)   text += ' · ' + dupes + ' duplicate file skipped';
        summary.textContent = text;

        totalCount.textContent = String(rows.length);
        selectedCount.textContent = String(selected);
        newCount.textContent = String(fresh);
        duplicateCount.textContent = String(existing);
    }

    // ── Ask server which words already exist ────────────────
    function checkExisting(batchRows) {
        var words = batchRows.map(function (r) { return r.word; });
        var chunks = [];
        for (var i = 0; i < words.length; i += 400) chunks.push(words.slice(i, i + 400));

        chunks.forEach(function (chunk) {
            var fd = new FormData();
            fd.append('action', 'check');
            fd.append('words', JSON.stringify(chunk));

            fetch('import.php', { method: 'POST', body: fd, credentials: 'same-origin' })
                .then(function (r) { return r.json(); })
                .then(function (data) {
                    if (!data || !data.ok) return;
                    batchRows.forEach(function (r) {
                        r.checkedOnServer = true;
                        var hit = data.existing[r.word];

                        if (!hit) {
                            r.exists = false;
                            r.cefr = '';

                            var updateBox = r.tr.querySelector('.wli-update-existing');
                            if (updateBox) {
                                updateBox.checked = false;
                                updateBox.disabled = true;
                                updateBox.title = 'This is a new word.';
                            }

                            var actionLabel = r.tr.querySelector('.wli-action-label');
                            if (actionLabel) {
                                actionLabel.textContent = 'Create';
                                actionLabel.className = 'wli-action-label wli-action-create';
                            }

                            var cefrSelect = r.tr.querySelector('.wli-cefr');
                            cefrSelect.value = '';
                            applyCefrStyle(cefrSelect);
                            return;
                        }

                        r.exists = true;
                        r.cefr = hit.cefr || '';
                        r.original = {
                            word: r.word,
                            meaning: hit.meaning || '',
                            ipa: hit.ipa || '',
                            cefr: hit.cefr || '',
                            example: hit.example || ''
                        };

                        var updateBox = r.tr.querySelector('.wli-update-existing');
                        if (updateBox) {
                            updateBox.disabled = false;
                            updateBox.title = 'Tick this to overwrite Meaning, IPA, CEFR and Example.';
                        }

                        fillRow(r, {
                            meaning: hit.meaning,
                            ipa: hit.ipa,
                            cefr: hit.cefr,
                            example: hit.example,
                            audio: hit.audio
                        });

                        var actionLabel = r.tr.querySelector('.wli-action-label');
                        if (actionLabel) {
                            actionLabel.textContent = hit.hasImage ? 'Skip' : 'Add image';
                            actionLabel.className = hit.hasImage
                                ? 'wli-action-label wli-action-skip'
                                : 'wli-action-label wli-action-update';
                        }

                        r.tr.querySelector('.wli-status').innerHTML = hit.hasImage
                            ? badge('skipped', 'Exists')
                            : badge('update', 'Add image');
                    });

                    updateSummary();
                })
                .catch(function () { /* offline check is non-blocking */ });
        });
    }

    var canSpeak = ('speechSynthesis' in window) && ('SpeechSynthesisUtterance' in window);

    function playIcon() {
        return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="6 3 20 12 6 21 6 3"/></svg>Play';
    }

    function fillRow(r, data) {
        var meaningInput = r.tr.querySelector('.wli-meaning');
        var ipaInput = r.tr.querySelector('.wli-ipa');
        var exampleInput = r.tr.querySelector('.wli-example');
        var cefrSelect = r.tr.querySelector('.wli-cefr');

        if (meaningInput && data.meaning) meaningInput.value = data.meaning;
        if (ipaInput && data.ipa) ipaInput.value = data.ipa;
        if (exampleInput && data.example) exampleInput.value = data.example;
        if (cefrSelect) {
            cefrSelect.value = data.cefr || '';
            applyCefrStyle(cefrSelect);
        }

        var cell = r.tr.querySelector('.wli-audio');
        if (data.audio) {
            // Real audio file stored on the server.
            cell.innerHTML = '<button type="button" class="wl-audio-btn wli-play" data-src="' +
                esc(data.audio) + '">' + playIcon() + '</button>';
        } else if (canSpeak) {
            // No server audio → real browser en-US pronunciation (no fake path).
            cell.innerHTML = '<button type="button" class="wl-audio-btn wli-play" data-word="' +
                esc(r.word) + '">' + playIcon() + '</button>';
        }
    }

    // ── Play audio (file if it exists, otherwise browser en-US speech) ──
    tbody.addEventListener('click', function (e) {
        var btn = e.target.closest('.wli-play');
        if (!btn) return;

        var src = btn.getAttribute('data-src');
        if (src) { new Audio(src).play().catch(function () {}); return; }

        var word = btn.getAttribute('data-word');
        if (!word || !canSpeak) return;
        try {
            window.speechSynthesis.cancel();
            var u = new SpeechSynthesisUtterance(word);
            u.lang = 'en-US';
            u.rate = 1;
            var voices = window.speechSynthesis.getVoices() || [];
            for (var i = 0; i < voices.length; i++) {
                if (voices[i].lang === 'en-US') { u.voice = voices[i]; break; }
            }
            window.speechSynthesis.speak(u);
        } catch (err) { /* pronunciation is never fatal */ }
    });

    // ── Folder input & drag-drop ────────────────────────────
    input.addEventListener('change', function () {
    resetImportList();
    scan(this.files, '');
    this.value = '';
});

    ['dragenter', 'dragover'].forEach(function (ev) {
        drop.addEventListener(ev, function (e) { e.preventDefault(); drop.classList.add('is-over'); });
    });
    ['dragleave', 'drop'].forEach(function (ev) {
        drop.addEventListener(ev, function (e) { e.preventDefault(); drop.classList.remove('is-over'); });
    });

    drop.addEventListener('drop', function (e) {
        e.preventDefault();
		    resetImportList();
        var items = e.dataTransfer.items;
        if (items && items.length && items[0].webkitGetAsEntry) {
            var entries = [], collected = [], pending = 0, finished = false;
            for (var i = 0; i < items.length; i++) {
                var entry = items[i].webkitGetAsEntry();
                if (entry) entries.push(entry);
            }
            var done = function () {
                if (finished || pending > 0) return;
                finished = true;
                scan(collected);
            };
            var walk = function (entry, folder) {
                if (entry.isFile) {
                    pending++;
                    entry.file(function (f) {
                        // Store the top-level folder separately; do not rely only
                        // on assigning a custom property to the File object.
                        if (folder) folderMap.set(f, folder.split('/')[0]);
                        try {
                            f.relativePath = (folder ? folder + '/' : '') + f.name;
                        } catch (ignore) {}
                        collected.push(f);
                        pending--; done();
                    }, function () { pending--; done(); });
                } else if (entry.isDirectory) {
                    pending++;
                    var reader = entry.createReader();
                    var childFolder = folder || entry.name;   // top-level folder = Topic name
                    var readMore = function () {
                        reader.readEntries(function (list) {
                            if (!list.length) { pending--; done(); return; }
                            list.forEach(function (child) { walk(child, childFolder); });
                            readMore();
                        }, function () { pending--; done(); });
                    };
                    readMore();
                }
            };
            entries.forEach(function (entry) { walk(entry, ''); });
            setTimeout(done, 50);
        } else if (e.dataTransfer.files) {
            scan(e.dataTransfer.files, '');
        }
    });

    // ── Selection buttons ───────────────────────────────────
    function setAll(state) {
        rows.forEach(function (r) {
            if (r.done) return;
            r.checked = state;
            var cb = r.tr.querySelector('.wli-row-check');
            if (cb) cb.checked = state;
        });
        checkAll.checked = state;
        updateSummary();
    }
    document.getElementById('wliSelectAll').addEventListener('click', function () { setAll(true); });
    document.getElementById('wliClearSelection').addEventListener('click', function () { setAll(false); });
    checkAll.addEventListener('change', function () { setAll(this.checked); });

    // ── Synchronized scrollbars ─────────────────────────────
    function syncScrollWidth() {
        scrollInner.style.width = table.scrollWidth + 'px';
        scrollTop.style.display = table.scrollWidth > tableWrap.clientWidth ? '' : 'none';
    }
    var lock = false;
    scrollTop.addEventListener('scroll', function () {
        if (lock) return; lock = true; tableWrap.scrollLeft = scrollTop.scrollLeft; lock = false;
    });
    tableWrap.addEventListener('scroll', function () {
        if (lock) return; lock = true; scrollTop.scrollLeft = tableWrap.scrollLeft; lock = false;
    });
    window.addEventListener('resize', syncScrollWidth);

    // ── Import ──────────────────────────────────────────────
    var importBtn = document.getElementById('wliImport');

    importBtn.addEventListener('click', function () {
        if (importing) return;
        var queue = rows.filter(function (r) { return r.checked && !r.done; });
        if (!queue.length) { alert('Please select at least one image.'); return; }

        importing = true;
        importBtn.disabled = true;
        var stats = {
            processed: 0,
            imported: 0,
            created: 0,
            updated: 0,
            skipped: 0,
            errors: 0,
            topics: 0,
            audioOk: 0,
            audioFail: 0,
            missingTopics: [],
            createdWords: [],
            updatedWords: [],
            skippedWords: [],
            errorWords: []
        };
        var index = 0;

        var step = function () {
            if (index >= queue.length) { finish(stats); return; }

            // One request = one folder, so the Topic is unambiguous.
            var folder = queue[index].folder || '';
            var slice = [];
            while (index < queue.length && slice.length < BATCH && (queue[index].folder || '') === folder) {
                slice.push(queue[index]);
                index++;
            }

            var fd = new FormData();
            fd.append('action', 'import');
            fd.append('folder', folder);
            slice.forEach(function (r) {
                fd.append('files[]', r.file, r.file.name);
                fd.append('rows[]', r.id);
                fd.append('edited_word[]', r.tr.querySelector('.wli-word-input').value.trim());
                fd.append('edited_meaning[]', r.tr.querySelector('.wli-meaning').value.trim());
                fd.append('edited_ipa[]', r.tr.querySelector('.wli-ipa').value.trim());
                fd.append('edited_cefr[]', r.tr.querySelector('.wli-cefr').value);
                fd.append('edited_example[]', r.tr.querySelector('.wli-example').value.trim());
                fd.append(
                    'update_existing[]',
                    r.tr.querySelector('.wli-update-existing').checked ? '1' : '0'
                );
                r.tr.querySelector('.wli-status').innerHTML = badge('working', 'Importing…');
            });

            summary.textContent = 'Importing ' + Math.min(index, queue.length) + ' / ' + queue.length + '…';

            fetch('import.php', { method: 'POST', body: fd, credentials: 'same-origin' })
                .then(function (res) { return res.json(); })
                .then(function (data) {
                    var map = {};
                    (data.results || []).forEach(function (x) { map[x.row] = x; });

                    stats.topics += (data.topicLinked || 0);
                    if (folder && data.topicFound === false &&
                        stats.missingTopics.indexOf(data.topicName || folder) === -1) {
                        stats.missingTopics.push(data.topicName || folder);
                    }

                    slice.forEach(function (r) {
                        var out = map[r.id];
                        r.done = true;
                        stats.processed++;

                        if (!out) {
                            stats.errors++;
                            stats.errorWords.push({
                                word: r.word,
                                reason: 'No response returned by the server.'
                            });
                            r.tr.querySelector('.wli-status').innerHTML = badge('error', 'Error');
                            return;
                        }
                        fillRow(r, out);
                        if (out.status === 'created') {
                            stats.created++; stats.imported++;
                            stats.createdWords.push({
                                word: out.word || r.word,
                                reason: out.message || 'New word created.'
                            });
                            if (out.audioOk) { stats.audioOk++; } else { stats.audioFail++; }
                            r.tr.querySelector('.wli-status').innerHTML = badge('created', 'Created');
                        } else if (out.status === 'updated') {
                            stats.updated++; stats.imported++;
                            stats.updatedWords.push({
                                word: out.word || r.word,
                                reason: out.message || 'Existing word updated.'
                            });
                            r.tr.querySelector('.wli-status').innerHTML = badge('update', 'Updated');
                        } else if (out.status === 'skipped') {
                            stats.skipped++;
                            stats.skippedWords.push({
                                word: out.word || r.word,
                                reason: out.message || 'No update was needed.'
                            });
                            r.tr.querySelector('.wli-status').innerHTML = badge('skipped', 'Skipped');
                        } else {
                            stats.errors++;
                            stats.errorWords.push({
                                word: out.word || r.word,
                                reason: out.message || 'Import failed.'
                            });
                            r.tr.querySelector('.wli-status').innerHTML = badge('error', 'Error');
                            r.tr.querySelector('.wli-status').title = out.message || '';
                        }
                        var cb = r.tr.querySelector('.wli-row-check');
                        if (cb) { cb.checked = false; cb.disabled = true; }
                        r.checked = false;
                    });

                    setTimeout(step, 0);   // yield → browser stays responsive
                })
                .catch(function () {
                    slice.forEach(function (r) {
                        r.done = true;
                        stats.processed++;
                        stats.errors++;
                        stats.errorWords.push({
                            word: r.word,
                            reason: 'Network or server error.'
                        });
                        r.tr.querySelector('.wli-status').innerHTML = badge('error', 'Error');
                    });
                    setTimeout(step, 0);
                });
        };

        step();
    });

    function finish(stats) {
        importing = false;
        importBtn.disabled = false;
        updateSummary();

        var successCount = stats.created + stats.updated;
        var summaryLines = [];

        if (stats.updated > 0) {
            summaryLines.push('✓ Updated ' + stats.updated + ' word' + (stats.updated === 1 ? '' : 's'));
        }
        if (stats.created > 0) {
            summaryLines.push('✓ Created ' + stats.created + ' word' + (stats.created === 1 ? '' : 's'));
        }
        if (stats.skipped > 0) {
            summaryLines.push('Skipped ' + stats.skipped + ' word' + (stats.skipped === 1 ? '' : 's'));
        }
        if (stats.errors > 0) {
            summaryLines.push('Errors ' + stats.errors);
        }
        if (!summaryLines.length) {
            summaryLines.push('No changes were made.');
        }

        var html =
            '<div class="wli-result-summary">' +
                summaryLines.map(function (line, index) {
                    return (index === 0 ? '<strong>' + esc(line) + '</strong>' : esc(line));
                }).join('<br>') +
            '</div>' +
            '<div class="wli-result-row"><span>Processed</span><strong>' + stats.processed + '</strong></div>';

        if (stats.created > 0) {
            html += '<div class="wli-result-row"><span>Created</span><strong>' + stats.created + '</strong></div>';
        }
        if (stats.updated > 0) {
            html += '<div class="wli-result-row"><span>Updated</span><strong>' + stats.updated + '</strong></div>';
        }
        if (stats.skipped > 0) {
            html += '<div class="wli-result-row"><span>Skipped</span><strong>' + stats.skipped + '</strong></div>';
        }
        if (stats.errors > 0) {
            html += '<div class="wli-result-row"><span>Errors</span><strong>' + stats.errors + '</strong></div>';
        }

        function renderItems(items, kind, symbol) {
            return items.map(function (item) {
                var word = typeof item === 'string' ? item : item.word;
                var reason = typeof item === 'string' ? '' : item.reason;

                return '<div class="wli-result-item wli-result-item-' + kind + '">' +
                    '<span>' + symbol + '</span>' +
                    '<div><strong>' + esc(word) + '</strong>' +
                        (reason ? '<small>' + esc(reason) + '</small>' : '') +
                    '</div>' +
                '</div>';
            }).join('');
        }

        function resultGroup(title, items, kind, symbol) {
            if (!items.length) return '';

            if (items.length <= 3) {
                return '<div class="wli-result-inline">' +
                    '<span class="wli-result-inline-title">' + esc(title) + ' (' + items.length + ')</span>' +
                    renderItems(items, kind, symbol) +
                '</div>';
            }

            return '<details class="wli-result-details">' +
                '<summary>' + esc(title) + ' (' + items.length + ')</summary>' +
                renderItems(items, kind, symbol) +
            '</details>';
        }

        html += resultGroup('Created words', stats.createdWords, 'created', '✓');
        html += resultGroup('Updated words', stats.updatedWords, 'updated', '✓');
        html += resultGroup('Skipped words', stats.skippedWords, 'skipped', '•');
        html += resultGroup('Errors', stats.errorWords, 'error', '⚠');

        if (stats.topics > 0) {
            html += '<div class="wli-result-row"><span>Topic links added</span><strong>' +
                stats.topics + '</strong></div>';
        }

        stats.missingTopics.forEach(function (name) {
            html += '<div class="wli-result-row"><span>Topic "' + esc(name) +
                    '" was not found. Words were imported without Topic assignment.</span></div>';
        });

        document.getElementById('wliResultList').innerHTML = html;
        overlay.classList.add('is-open');
    }

    document.getElementById('wliResultClose').addEventListener('click', function () { overlay.classList.remove('is-open'); });
    document.getElementById('wliStay').addEventListener('click', function () {
        overlay.classList.remove('is-open');
        resetImportList();
        input.click();
    });

    window.addEventListener('beforeunload', function (e) {
        if (importing) { e.preventDefault(); e.returnValue = ''; }
    });
})();
</script>

<?php
$content = ob_get_clean();
require __DIR__ . '/../layouts/app.php';
