<?php

declare(strict_types=1);

class EntityTranslateAdmin
{
    private const EXACT_COUNT_LIMIT = 5000;

    private static function meta(string $entityType): array
    {
        $meta = EntityTranslateProcessor::typeMeta($entityType);
        if ($meta === null) { Router::json(['error' => 'Unknown entity type.'], 400); }
        return $meta;
    }

    private static function input(): array
    {
        $in = json_decode(file_get_contents('php://input'), true);
        return is_array($in) ? $in : [];
    }

    /**
     * Target languages for this run. Absent key = every enabled language. A key that IS present
     * but yields nothing usable returns [] on purpose: silently falling back to "all languages"
     * would bill an operator for 30 languages when they asked for one we do not have.
     */
    private static function chosenLanguages(array $input): array
    {
        $available = EntityTranslateProcessor::targetLanguageCodes();
        if (!array_key_exists('languages', $input) || !is_array($input['languages'])) {
            return $available;
        }
        return array_values(array_intersect($available, array_map('strval', $input['languages'])));
    }

    /** Explicit selections are bounded so a hand-made request cannot ask for an unbounded run. */
    private static function selectedIds(array $input): array
    {
        $ids = array_values(array_unique(array_filter(array_map('intval', $input['ids'] ?? []), fn($id) => $id > 0)));
        return array_slice($ids, 0, self::EXACT_COUNT_LIMIT);
    }

    

    public static function headerButtons(string $entityType): string
    {
        $html = '<button type="button" onclick="AT.translateAll()" class="inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-sky-700 bg-sky-50 hover:bg-sky-100 border border-sky-200 rounded-lg transition-colors"><i class="fa-solid fa-language"></i> AI Translate</button>';
        if (EntityTranslateProcessor::autoEnabled($entityType)) {
            $html .= ' <button type="button" onclick="AT.disableAuto()" title="Auto-translation is ON — click to turn off" class="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-emerald-700 bg-emerald-50 hover:bg-emerald-100 border border-emerald-200 rounded-lg transition-colors"><i class="fa-solid fa-bolt"></i> Auto-translate: ON <i class="fa-solid fa-xmark text-emerald-400"></i></button>';
        }
        return $html;
    }

    public static function auto(string $entityType): void
    {
        Auth::requireAdmin();
        if (!Router::verifyCsrf()) { Router::json(['error' => 'Invalid CSRF token'], 403); }
        self::meta($entityType);

        $input = self::input();
        $enabled = !empty($input['enabled']) ? '1' : '0';
        Setting::set('entity_autotranslate_' . $entityType, $enabled);
        EntityTranslateProcessor::syncAutoBlockedFlag();

        Router::json(['success' => true, 'enabled' => $enabled === '1']);
    }

    

    public static function init(string $entityType): void
    {
        Auth::requireAdmin();
        if (!Router::verifyCsrf()) { Router::json(['error' => 'CSRF'], 403); }
        session_write_close();

        $meta = self::meta($entityType);
        $input = self::input();

        $all = !empty($input['all']);
        $typeFilter = self::sanitizeTypeFilter($meta, (string) ($input['type_filter'] ?? ''));
        $selectedIds = self::selectedIds($input);

        $languages = EntityTranslateProcessor::targetLanguages();
        if (empty($languages)) {
            Router::json(['error' => 'No additional languages are enabled. Go to Admin > Languages to enable the languages you want to translate into.'], 400);
        }
        $chosen = self::chosenLanguages($input);
        $fields = TranslateTypes::fieldKeys($meta, $input['fields'] ?? null);

        if (!$all && empty($selectedIds)) {
            Router::json(['error' => 'Nothing selected.'], 400);
        }

        $total = $all ? self::countScope($meta, $typeFilter) : count($selectedIds);
        if ($total === 0) {
            Router::json(['error' => 'There is nothing to translate here yet.'], 400);
        }

        $exact = $total <= self::EXACT_COUNT_LIMIT;
        $counts = $exact
            ? self::exactCounts($meta, $fields, $chosen, $all, $typeFilter, $selectedIds)
            : self::upperBoundCounts($meta, $fields, $total);

        $pendingItems = $exact ? (int) ($counts['_items'] ?? 0) : $total;
        unset($counts['_items']);

        $fieldInfo = [];
        foreach (TranslateTypes::fieldKeys($meta) as $fk) {
            $f = $meta['fields'][$fk];
            $fieldInfo[] = ['key' => $fk, 'label' => $f['label'], 'kind' => $f['kind'], 'hint' => (string) ($f['hint'] ?? ''), 'selected' => in_array($fk, $fields, true)];
        }
        $langInfo = array_map(fn($l) => [
            'code'     => $l['code'],
            'name'     => $l['name'],
            'flag'     => Translator::flagCode($l['code']),
            'selected' => in_array($l['code'], $chosen, true),
        ], $languages);

        $cachedRate = Setting::get('catalogue_credit_rate', '');
        $payload = [
            'entity_type'  => $entityType,
            'label_many'   => $meta['label_many'],
            'title_many'   => $meta['title_many'],
            'icon'         => $meta['icon'],
            'scope'        => $all ? 'all' : 'ids',
            'type_filter'  => $typeFilter,
            'ids'          => $all ? [] : $selectedIds,
            'total'        => $total,
            'count'        => $pendingItems,
            'exact'        => $exact,
            'fields'       => $fieldInfo,
            'languages'    => $langInfo,
            'lang_count'   => count($chosen),
            'credit_rate'  => $cachedRate !== '' ? (float) $cachedRate : 0.01,
            'total_cost'   => 0.0,
            'unit_prices'  => [],
            'balance'      => 0.0,
            'sufficient'   => false,
            'missing'      => 0.0,
            'warnings'     => [],
        ];

        if (empty($chosen)) {
            $payload['count'] = 0;
            $payload['sufficient'] = true;
            $payload['warnings'][] = 'Pick at least one target language to see the price.';
            Router::json($payload);
        }

        if (empty($counts)) {
            $payload['count'] = 0;
            $payload['sufficient'] = true;
            $payload['warnings'][] = 'Everything here is already translated into the languages you picked.';
            Router::json($payload);
        }

        if (!CatalogueClient::isConfigured()) {
            $payload['not_connected'] = true;
            Router::json($payload);
        }

        $quote = CatalogueClient::translateQuote($counts, $chosen);
        if ($quote === null) {
            $payload['unreachable'] = true;
            $payload['warnings'][] = 'Could not reach TubePress to price this run. Check the connection and try again.';
            Router::json($payload);
        }

        $payload['unit_prices'] = $quote['unit_prices'] ?? [];
        $payload['total_cost']  = (float) ($quote['total'] ?? 0);
        $payload['balance']     = (float) ($quote['balance'] ?? 0);
        $payload['sufficient']  = !empty($quote['sufficient']);
        $payload['missing']     = (float) ($quote['missing'] ?? 0);
        $payload['max_langs']   = (int) ($quote['max_langs'] ?? 30);

        if (!$exact) {
            $payload['warnings'][] = 'This is the maximum for ' . number_format($total) . ' ' . $meta['label_many']
                . '. Items already translated into your selected languages are skipped and cost nothing, so the final total is usually lower.';
        }
        if (!$payload['sufficient']) {
            $covered = $payload['total_cost'] > 0 ? (int) floor($payload['balance'] / ($payload['total_cost'] / max(1, $payload['count']))) : 0;
            $payload['covered_items'] = max(0, min($payload['count'], $covered));
        }

        Router::json($payload);
    }

    private static function sanitizeTypeFilter(array $meta, string $raw): string
    {
        if ($raw === '' || empty($meta['type_col'])) return '';
        return in_array($raw, Video::VALID_TYPES, true) ? $raw : '';
    }

    private static function countScope(array $meta, string $typeFilter): int
    {
        $sql = 'SELECT COUNT(*) AS c FROM ' . $meta['main'];
        $params = [];
        if ($typeFilter !== '' && !empty($meta['type_col'])) {
            $sql .= ' WHERE ' . $meta['type_col'] . ' = ?';
            $params[] = $typeFilter;
        }
        return (int) (Database::fetchOne($sql, $params)['c'] ?? 0);
    }

    /**
     * Exactly how many (item, field) units still need work, so the quote matches what the
     * job will actually charge. Only used below EXACT_COUNT_LIMIT rows.
     */
    private static function exactCounts(array $meta, array $fields, array $langs, bool $all, string $typeFilter, array $ids): array
    {
        if (empty($langs) || empty($fields)) {
            return [];
        }
        $cols = ['id'];
        foreach ($fields as $fk) { $cols[] = $meta['fields'][$fk]['src']; }
        $cols = array_values(array_unique($cols));
        $select = implode(', ', $cols);

        if ($all) {
            $sql = "SELECT {$select} FROM {$meta['main']}";
            $params = [];
            if ($typeFilter !== '' && !empty($meta['type_col'])) {
                $sql .= ' WHERE ' . $meta['type_col'] . ' = ?';
                $params[] = $typeFilter;
            }
            $sql .= ' ORDER BY id ASC LIMIT ' . self::EXACT_COUNT_LIMIT;
            $rows = Database::fetchAll($sql, $params);
        } else {
            $ph = implode(',', array_fill(0, count($ids), '?'));
            $rows = Database::fetchAll("SELECT {$select} FROM {$meta['main']} WHERE id IN ({$ph})", $ids);
        }
        if (empty($rows)) return [];

        $rowIds = array_map(fn($r) => (int) $r['id'], $rows);
        $existing = [];
        $iph = implode(',', array_fill(0, count($rowIds), '?'));
        $lph = implode(',', array_fill(0, count($langs), '?'));
        $dst = [];
        foreach ($fields as $fk) { $dst[] = $meta['fields'][$fk]['dst']; }
        $dst = array_values(array_unique($dst));
        $trRows = Database::fetchAll(
            'SELECT ' . $meta['fk'] . ', lang_code, ' . implode(', ', $dst) . " FROM {$meta['translations']}
              WHERE {$meta['fk']} IN ({$iph}) AND lang_code IN ({$lph})",
            array_merge($rowIds, $langs)
        );
        foreach ($trRows as $r) { $existing[(int) $r[$meta['fk']]][$r['lang_code']] = $r; }

        $counts = [];
        $items = [];
        foreach ($rows as $row) {
            $id = (int) $row['id'];
            foreach ($fields as $fk) {
                $f = $meta['fields'][$fk];
                $src = trim((string) ($row[$f['src']] ?? ''));
                if ($src === '' || mb_strlen($src) > (int) $f['max']) continue;
                foreach ($langs as $lang) {
                    if (trim((string) ($existing[$id][$lang][$f['dst']] ?? '')) === '') {
                        $counts[$f['kind']] = ($counts[$f['kind']] ?? 0) + 1;
                        $items[$id] = true;
                        break;
                    }
                }
            }
        }
        $counts['_items'] = count($items);
        return $counts;
    }

    /** Worst case for a table too big to scan: every row needs every selected field. */
    private static function upperBoundCounts(array $meta, array $fields, int $total): array
    {
        if (empty($fields)) {
            return [];
        }
        $counts = [];
        foreach ($fields as $fk) {
            $kind = $meta['fields'][$fk]['kind'];
            $counts[$kind] = ($counts[$kind] ?? 0) + $total;
        }
        return $counts;
    }

    

    public static function start(string $entityType): void
    {
        Auth::requireAdmin();
        if (!Router::verifyCsrf()) { Router::json(['error' => 'CSRF'], 403); }

        $meta = self::meta($entityType);
        $input = self::input();

        $all = ($input['scope'] ?? '') === 'all';
        $ids = self::selectedIds($input);
        if (!$all && empty($ids)) {
            Router::json(['error' => 'Nothing selected.'], 400);
        }

        $chosen = self::chosenLanguages($input);
        if (empty($chosen)) {
            Router::json(['error' => 'Pick at least one target language that is enabled on your site.'], 400);
        }
        $fields = TranslateTypes::fieldKeys($meta, $input['fields'] ?? null);

        $lockName = 'entity_translate_' . $entityType;
        if (!(Database::fetchOne('SELECT GET_LOCK(?, 0) AS l', [$lockName])['l'] ?? 0)) {
            Router::json(['error' => 'A translation run is already starting for this section.'], 409);
        }

        try {
            if (EntityTranslateProcessor::getActiveJob($entityType) !== null) {
                Router::json(['error' => 'A translation run is already going for this section.'], 409);
            }
            $jobId = EntityTranslateProcessor::createJob([
                'entity_type' => $entityType,
                'scope'       => $all ? 'all' : 'ids',
                'type_filter' => self::sanitizeTypeFilter($meta, (string) ($input['type_filter'] ?? '')),
                'ids'         => $ids,
                'languages'   => $chosen,
                'fields'      => $fields,
                'user_id'     => (int) Auth::id(),
            ]);
        } finally {
            Database::query('SELECT RELEASE_LOCK(?)', [$lockName]);
        }

        Router::json(['success' => true, 'job_id' => $jobId]);
    }

    

    public static function status(string $entityType): void
    {
        Auth::requireAdmin();
        session_write_close();
        self::meta($entityType);

        $jobId = (int) ($_GET['job_id'] ?? 0);
        $job = $jobId > 0 ? EntityTranslateProcessor::getJob($jobId) : EntityTranslateProcessor::getActiveJob($entityType);

        if (!$job || ($jobId > 0 && $job['entity_type'] !== $entityType)) {
            Router::json(['active' => false]);
        }

        $running = in_array($job['status'], ['pending', 'processing', 'paused'], true);
        $reason = $running ? (string) ($job['pause_reason'] ?? '') : '';
        if (str_starts_with($reason, 'cooldown:')) {
            $reason = 'Waiting for the AI service to answer again…';
        }

        Router::json([
            'active'     => $running,
            'paused'     => $job['status'] === 'paused',
            'job_id'     => (int) $job['id'],
            'status'     => $job['status'],
            'total'      => (int) $job['total_items'],
            'processed'  => (int) $job['processed_items'],
            'translated' => (int) $job['translated_count'],
            'failed'     => (int) $job['failed_count'],
            'charged'    => round((float) $job['charged_credits'], 2),
            'refunded'   => round((float) ($job['refund_amount'] ?? 0), 2),
            'reason'     => $reason,
            'error'      => $job['error_message'],
            'created_at' => $job['created_at'],
        ]);
    }

    public static function cancel(string $entityType): void
    {
        Auth::requireAdmin();
        if (!Router::verifyCsrf()) { Router::json(['error' => 'CSRF'], 403); }
        self::meta($entityType);

        $jobId = (int) (self::input()['job_id'] ?? 0);
        if ($jobId <= 0) { Router::json(['success' => true]); }

        $result = EntityTranslateProcessor::cancelJob($jobId, (int) Auth::id());
        if (!empty($result['error'])) { Router::json(['error' => $result['error']], 403); }
        Router::json(['success' => true] + $result);
    }

    public static function pause(string $entityType): void
    {
        Auth::requireAdmin();
        if (!Router::verifyCsrf()) { Router::json(['error' => 'CSRF'], 403); }
        self::meta($entityType);
        $jobId = (int) (self::input()['job_id'] ?? 0);
        Router::json(['success' => $jobId > 0 && EntityTranslateProcessor::pauseByUser($jobId)]);
    }

    public static function resume(string $entityType): void
    {
        Auth::requireAdmin();
        if (!Router::verifyCsrf()) { Router::json(['error' => 'CSRF'], 403); }
        self::meta($entityType);
        $jobId = (int) (self::input()['job_id'] ?? 0);
        Router::json(['success' => $jobId > 0 && EntityTranslateProcessor::resumeJob($jobId)]);
    }

    public static function retry(string $entityType): void
    {
        Auth::requireAdmin();
        if (!Router::verifyCsrf()) { Router::json(['error' => 'CSRF'], 403); }
        self::meta($entityType);
        $jobId = (int) (self::input()['job_id'] ?? 0);
        Router::json(['success' => $jobId > 0 && EntityTranslateProcessor::retryFailed($jobId)]);
    }

    

    public static function routes(string $entityType, string $endpoint, string $class): void
    {
        Router::post('/admin/' . $endpoint . '/ai-translate-init',   [$class, 'aiTranslateInit']);
        Router::post('/admin/' . $endpoint . '/ai-translate-start',  [$class, 'aiTranslateStart']);
        Router::get('/admin/' . $endpoint . '/ai-translate-status',  [$class, 'aiTranslateStatus']);
        Router::post('/admin/' . $endpoint . '/ai-translate-cancel', [$class, 'aiTranslateCancel']);
        Router::post('/admin/' . $endpoint . '/ai-translate-pause',  [$class, 'aiTranslatePause']);
        Router::post('/admin/' . $endpoint . '/ai-translate-resume', [$class, 'aiTranslateResume']);
        Router::post('/admin/' . $endpoint . '/ai-translate-retry',  [$class, 'aiTranslateRetry']);
        Router::post('/admin/' . $endpoint . '/ai-translate-auto',   [$class, 'aiTranslateAuto']);
    }
}
