<?php

namespace ZenoChatbot\Services\Indexing\Engine;

use ZenoChatbot\Helper\Resources;
use ZenoChatbot\Helper\Settings;
use ZenoChatbot\Support\Hooks;

if (!defined('ABSPATH')) {
    exit;
}

class SitemapContentIndexEngine
{
    private const DEFAULT_CHUNK_SIZE = 50;

    /**
     * @return string
     */
    private static function getFilePath()
    {
        $pluginDir = Resources::getBaseDirPath();
        return $pluginDir . '/sitemap-content-index.json';
    }

    /**
     * @return array<int,array<string,mixed>>
     */
    public static function getAll()
    {
        $filePath = static::getFilePath();
        if (!file_exists($filePath)) {
            return [];
        }

        $content = file_get_contents($filePath);
        if ($content === false) {
            return [];
        }

        $decoded = json_decode($content, true);
        return is_array($decoded) ? $decoded : [];
    }

    /**
     * @param int|null $chunkSize
     * @return array<string,mixed>
     */
    public static function buildIndex($chunkSize = null)
    {
        $job = static::startBuildJob('auto', '', $chunkSize);
        if (!($job['success'] ?? false)) {
            return $job;
        }

        $jobId = (string) ($job['data']['job_id'] ?? '');
        if ($jobId === '') {
            return [
                'success' => false,
                'message' => 'Failed to initialize sitemap build job.'
            ];
        }

        do {
            $result = static::processBuildJob($jobId);
            if (!($result['success'] ?? false)) {
                return $result;
            }
        } while (($result['data']['status'] ?? '') === 'running');

        return [
            'success' => true,
            'message' => (string) ($result['data']['message'] ?? 'Sitemap indexed successfully.'),
            'data' => [
                'indexed_count' => (int) ($result['data']['indexed'] ?? 0),
                'sitemap_url' => (string) ($result['data']['sitemap_url'] ?? ''),
            ]
        ];
    }

    /**
     * @param int|null $chunkSize
     * @return array<string,mixed>
     */
    public static function syncIfChanged($chunkSize = null)
    {
        $resolved = static::resolveSitemapUrl('auto', '');
        if (!($resolved['success'] ?? false)) {
            return [
                'success' => false,
                'updated' => false,
                'message' => (string) ($resolved['message'] ?? 'No sitemap URL could be auto-detected for this site.')
            ];
        }

        $sitemapUrl = (string) ($resolved['url'] ?? '');
        if ($sitemapUrl === '') {
            return [
                'success' => false,
                'updated' => false,
                'message' => 'No sitemap URL could be auto-detected for this site.'
            ];
        }

        $syncJobId = 'sync-' . wp_generate_uuid4();
        $entries = static::collectSitemapEntries($sitemapUrl, $syncJobId);
        static::clearJobSitemapCache($syncJobId);
        if (empty($entries)) {
            return [
                'success' => false,
                'updated' => false,
                'message' => 'No sitemap URLs found to index.'
            ];
        }

        $fingerprint = static::fingerprintEntries($entries);
        $existingFingerprint = (string) Settings::get('sitemap_index.fingerprint', '');

        Settings::update('sitemap_index.last_checked_at', gmdate('Y-m-d H:i:s'));
        Settings::update('sitemap_index.sitemap_url', $sitemapUrl);

        if (static::count() > 0 && $existingFingerprint !== '' && hash_equals($existingFingerprint, $fingerprint)) {
            return [
                'success' => true,
                'updated' => false,
                'message' => 'Sitemap index is up to date.',
                'data' => static::getStats()
            ];
        }

        $result = static::buildIndex($chunkSize);
        $result['updated'] = (bool) ($result['success'] ?? false);
        return $result;
    }

    /**
     * @param string $mode
     * @param string $manualPath
     * @param int|null $chunkSize
     * @return array<string,mixed>
     */
    public static function startBuildJob($mode = 'auto', $manualPath = '', $chunkSize = null)
    {
        $chunkSize = (int) ($chunkSize ?? static::DEFAULT_CHUNK_SIZE);
        $chunkSize = max(1, $chunkSize);

        $resolved = static::resolveSitemapUrl((string) $mode, (string) $manualPath);
        if (!($resolved['success'] ?? false)) {
            return [
                'success' => false,
                'message' => (string) ($resolved['message'] ?? 'Unable to resolve sitemap URL.')
            ];
        }

        $sitemapUrl = (string) ($resolved['url'] ?? '');
        if ($sitemapUrl === '') {
            return [
                'success' => false,
                'message' => 'Unable to resolve sitemap URL.'
            ];
        }

        $jobId = wp_generate_uuid4();
        $itemsFile = static::getJobItemsFilePath($jobId);

        if (file_exists($itemsFile)) {
            @wp_delete_file($itemsFile);
        }

        $entries = static::collectSitemapEntries($sitemapUrl, $jobId);
        if (empty($entries)) {
            static::clearJobSitemapCache($jobId);
            return [
                'success' => false,
                'message' => 'No sitemap URLs found to index.'
            ];
        }

        $job = [
            'job_id' => $jobId,
            'status' => 'running',
            'mode' => sanitize_text_field((string) $mode),
            'sitemap_url' => esc_url_raw($sitemapUrl),
            'entries' => $entries,
            'chunk_size' => $chunkSize,
            'offset' => 0,
            'total' => count($entries),
            'indexed' => 0,
            'skipped' => 0,
            'failed' => 0,
            'created_at' => gmdate('c'),
            'updated_at' => gmdate('c'),
            'completed_at' => null,
            'summary' => null,
            'message' => 'Build initialized.',
        ];

        static::writeJob($jobId, $job);

        return [
            'success' => true,
            'data' => static::formatJobData($job, true)
        ];
    }

    /**
     * @param string $jobId
     * @return array<string,mixed>
     */
    public static function processBuildJob($jobId)
    {
        $jobId = sanitize_text_field((string) $jobId);
        if ($jobId === '') {
            return [
                'success' => false,
                'message' => 'Invalid job id.'
            ];
        }

        $job = static::readJob($jobId);
        if (!is_array($job)) {
            return [
                'success' => false,
                'message' => 'Sitemap build job not found.'
            ];
        }

        $status = (string) ($job['status'] ?? '');
        if ($status === 'completed' || $status === 'failed') {
            return [
                'success' => true,
                'data' => static::formatJobData($job, true)
            ];
        }

        $entries = is_array($job['entries'] ?? null) ? $job['entries'] : [];
        $offset = max(0, (int) ($job['offset'] ?? 0));
        $chunkSize = max(1, (int) ($job['chunk_size'] ?? static::DEFAULT_CHUNK_SIZE));

        $chunk = array_slice($entries, $offset, $chunkSize);
        foreach ($chunk as $entry) {
            $item = static::buildItemFromEntry(is_array($entry) ? $entry : []);
            if ($item === null) {
                $job['skipped'] = ((int) ($job['skipped'] ?? 0)) + 1;
                continue;
            }

            if (!static::appendJobItem($jobId, $item)) {
                $job['failed'] = ((int) ($job['failed'] ?? 0)) + 1;
                continue;
            }

            $job['indexed'] = ((int) ($job['indexed'] ?? 0)) + 1;
        }

        $job['offset'] = $offset + count($chunk);
        $job['updated_at'] = gmdate('c');

        $total = max(0, (int) ($job['total'] ?? 0));
        if ((int) $job['offset'] >= $total) {
            $finalize = static::finalizeBuildJob($jobId, $job);
            if (!($finalize['success'] ?? false)) {
                $job['status'] = 'failed';
                $job['message'] = (string) ($finalize['message'] ?? 'Failed to finalize sitemap index.');
                $job['completed_at'] = gmdate('c');
                static::clearJobSitemapCache($jobId);
                static::writeJob($jobId, $job);

                return [
                    'success' => false,
                    'message' => $job['message'],
                    'data' => static::formatJobData($job, true)
                ];
            }

            $job = $finalize['job'];
        }

        static::writeJob($jobId, $job);

        return [
            'success' => true,
            'data' => static::formatJobData($job, true)
        ];
    }

    /**
     * @param string $jobId
     * @return array<string,mixed>
     */
    public static function getBuildJobStatus($jobId)
    {
        $jobId = sanitize_text_field((string) $jobId);
        if ($jobId === '') {
            return [
                'success' => false,
                'message' => 'Invalid job id.'
            ];
        }

        $job = static::readJob($jobId);
        if (!is_array($job)) {
            return [
                'success' => false,
                'message' => 'Sitemap build job not found.'
            ];
        }

        return [
            'success' => true,
            'data' => static::formatJobData($job, true)
        ];
    }

    /**
     * @return array<string,mixed>
     */
    public static function clearIndex()
    {
        $currentCount = static::count();
        $saved = static::saveAll([]);

        if (!$saved) {
            return [
                'success' => false,
                'message' => 'Failed to clear sitemap index.'
            ];
        }

        Settings::delete('sitemap_index.last_indexed_at');
        Settings::delete('sitemap_index.indexed_count');
        Settings::delete('sitemap_index.sitemap_url');
        Settings::delete('sitemap_index.fingerprint');
        Settings::delete('sitemap_index.last_checked_at');

        static::emitUpdateHooks('clear');

        return [
            'success' => true,
            'message' => "Successfully cleared {$currentCount} sitemap items.",
            'data' => [
                'cleared_count' => $currentCount
            ]
        ];
    }

    /**
     * @return array<string,mixed>
     */
    public static function getStats()
    {
        return [
            'total_count' => static::count(),
            'indexed_count' => (int) Settings::get('sitemap_index.indexed_count', 0),
            'last_indexed_at' => Settings::get('sitemap_index.last_indexed_at'),
            'last_checked_at' => Settings::get('sitemap_index.last_checked_at'),
            'sitemap_url' => Settings::get('sitemap_index.sitemap_url', ''),
        ];
    }

    /**
     * @return int
     */
    public static function count()
    {
        return count(static::getAll());
    }

    /**
     * @param string $mode
     * @param string $manualPath
     * @return array<string,mixed>
     */
    private static function resolveSitemapUrl($mode, $manualPath)
    {
        $mode = sanitize_key((string) $mode);

        if ($mode === 'manual') {
            $manualUrl = static::buildManualSitemapUrl((string) $manualPath);
            if ($manualUrl === '') {
                return [
                    'success' => false,
                    'message' => 'Please provide a valid sitemap path (for example: /sitemap.xml).'
                ];
            }

            if (!static::isReachableSitemapUrl($manualUrl)) {
                return [
                    'success' => false,
                    'message' => 'Manual sitemap path is not reachable or invalid.'
                ];
            }

            return [
                'success' => true,
                'url' => $manualUrl
            ];
        }

        $detected = static::detectSitemapUrl();
        if ($detected === '') {
            return [
                'success' => false,
                'message' => 'No sitemap URL could be auto-detected for this site.'
            ];
        }

        return [
            'success' => true,
            'url' => $detected
        ];
    }

    /**
     * @param string $manualPath
     * @return string
     */
    private static function buildManualSitemapUrl($manualPath)
    {
        $manualPath = trim((string) $manualPath);
        if ($manualPath === '') {
            return '';
        }

        if (preg_match('#^https?://#i', $manualPath)) {
            return '';
        }

        $manualPath = '/' . ltrim($manualPath, '/');
        $manualPath = preg_replace('/\s+/', '', $manualPath) ?? $manualPath;

        return esc_url_raw(home_url($manualPath));
    }

    /**
     * @return string
     */
    private static function detectSitemapUrl()
    {
        $candidates = [
            home_url('/wp-sitemap.xml'),
            home_url('/sitemap.xml'),
        ];

        $robotsUrl = home_url('/robots.txt');
        $robotsResponse = wp_remote_get($robotsUrl, ['timeout' => 10]);
        if (!is_wp_error($robotsResponse)) {
            $robotsBody = wp_remote_retrieve_body($robotsResponse);
            if (is_string($robotsBody) && $robotsBody !== '') {
                if (preg_match_all('/^\s*Sitemap:\s*(.+)\s*$/im', $robotsBody, $matches)) {
                    foreach (($matches[1] ?? []) as $match) {
                        $match = esc_url_raw(trim((string) $match));
                        if ($match !== '') {
                            $candidates[] = $match;
                        }
                    }
                }
            }
        }

        foreach (array_unique($candidates) as $candidate) {
            if (static::isReachableSitemapUrl($candidate)) {
                return (string) $candidate;
            }
        }

        return '';
    }

    /**
     * @param string $candidate
     * @return bool
     */
    private static function isReachableSitemapUrl($candidate)
    {
        $candidate = esc_url_raw((string) $candidate);
        if ($candidate === '') {
            return false;
        }

        if (!static::isSameSiteUrl($candidate)) {
            return false;
        }

        $status = 0;
        $head = wp_remote_head($candidate, ['timeout' => 10]);
        if (!is_wp_error($head)) {
            $status = (int) wp_remote_retrieve_response_code($head);
        }

        if ($status < 200 || $status >= 300) {
            $get = wp_remote_get($candidate, ['timeout' => 10]);
            if (!is_wp_error($get)) {
                $status = (int) wp_remote_retrieve_response_code($get);
            }
        }

        return $status >= 200 && $status < 300;
    }

    /**
     * @param string $candidate
     * @return bool
     */
    private static function isSameSiteUrl($candidate)
    {
        $candidate = esc_url_raw((string) $candidate);
        if ($candidate === '') {
            return false;
        }

        $scheme = strtolower((string) wp_parse_url($candidate, PHP_URL_SCHEME));
        if (!in_array($scheme, ['http', 'https'], true)) {
            return false;
        }

        $homeHost = wp_parse_url(home_url('/'), PHP_URL_HOST);
        $host = wp_parse_url($candidate, PHP_URL_HOST);

        return is_string($homeHost)
            && $homeHost !== ''
            && is_string($host)
            && strtolower($host) === strtolower($homeHost);
    }

    /**
     * @param string $sitemapUrl
     * @return array<int,array{loc:string,lastmod:string}>
     */
    private static function collectSitemapEntries($sitemapUrl, $jobId = '')
    {
        $visited = [];
        $collected = [];
        static::walkSitemap($sitemapUrl, $visited, $collected, static::getJobSitemapCacheDir($jobId));

        $unique = [];
        foreach ($collected as $entry) {
            $loc = (string) ($entry['loc'] ?? '');
            if ($loc === '') {
                continue;
            }

            $unique[$loc] = [
                'loc' => $loc,
                'lastmod' => (string) ($entry['lastmod'] ?? ''),
            ];
        }

        return array_values($unique);
    }

    /**
     * @param string $url
     * @param array<string,bool> $visited
     * @param array<int,array{loc:string,lastmod:string}> $collected
     * @return void
     */
    private static function walkSitemap($url, &$visited, &$collected, $cacheDir)
    {
        $normalizedUrl = esc_url_raw((string) $url);
        if ($normalizedUrl === '' || isset($visited[$normalizedUrl])) {
            return;
        }

        if (!static::isSameSiteUrl($normalizedUrl)) {
            return;
        }

        $visited[$normalizedUrl] = true;

        $localPath = static::downloadSitemapXmlToLocal($normalizedUrl, $cacheDir);
        if ($localPath === '') {
            return;
        }

        libxml_use_internal_errors(true);
        $xml = simplexml_load_file($localPath);
        if ($xml === false) {
            return;
        }

        $rootName = strtolower((string) $xml->getName());
        if ($rootName === 'sitemapindex') {
            foreach ($xml->sitemap as $sitemapNode) {
                $childUrl = esc_url_raw((string) ($sitemapNode->loc ?? ''));
                if ($childUrl !== '' && static::isSameSiteUrl($childUrl)) {
                    static::walkSitemap($childUrl, $visited, $collected, $cacheDir);
                }
            }
            return;
        }

        if ($rootName === 'urlset') {
            $homeHost = wp_parse_url(home_url('/'), PHP_URL_HOST);
            foreach ($xml->url as $urlNode) {
                $loc = esc_url_raw((string) ($urlNode->loc ?? ''));
                if ($loc === '') {
                    continue;
                }

                $entryHost = wp_parse_url($loc, PHP_URL_HOST);
                if (
                    is_string($homeHost)
                    && $homeHost !== ''
                    && is_string($entryHost)
                    && strtolower($entryHost) !== strtolower($homeHost)
                ) {
                    continue;
                }

                $collected[] = [
                    'loc' => $loc,
                    'lastmod' => sanitize_text_field((string) ($urlNode->lastmod ?? '')),
                ];
            }
        }
    }

    /**
     * @param array<string,mixed> $entry
     * @return array<string,mixed>|null
     */
    private static function buildItemFromEntry($entry)
    {
        $loc = esc_url_raw((string) ($entry['loc'] ?? ''));
        if ($loc === '') {
            return null;
        }

        $postId = url_to_postid($loc);
        $title = '';
        $context = '';

        if ($postId > 0) {
            $post = get_post($postId);
            if ($post instanceof \WP_Post) {
                $title = $post->post_title;
                $context = static::getPostExcerpt($post);
            }
        }

        if ($title === '') {
            $title = static::buildFallbackTitle($loc);
        }

        return [
            'title' => sanitize_text_field($title),
            'link' => $loc,
            'context' => sanitize_textarea_field($context),
            'lastmod' => sanitize_text_field((string) ($entry['lastmod'] ?? '')),
        ];
    }

    /**
     * @param string $url
     * @param string $cacheDir
     * @return string
     */
    private static function downloadSitemapXmlToLocal($url, $cacheDir)
    {
        $response = wp_remote_get($url, ['timeout' => 20]);
        if (is_wp_error($response)) {
            return '';
        }

        $status = (int) wp_remote_retrieve_response_code($response);
        if ($status < 200 || $status >= 300) {
            return '';
        }

        $body = wp_remote_retrieve_body($response);
        if (!is_string($body) || trim($body) === '') {
            return '';
        }

        if (!file_exists($cacheDir)) {
            wp_mkdir_p($cacheDir);
        }

        $path = trailingslashit($cacheDir) . md5((string) $url) . '.xml';
        $saved = file_put_contents($path, $body, LOCK_EX);
        return $saved === false ? '' : $path;
    }

    /**
     * @param string $jobId
     * @param array<string,mixed> $job
     * @return array<string,mixed>
     */
    private static function finalizeBuildJob($jobId, $job)
    {
        $items = static::readJobItems($jobId);
        if (!static::saveAll($items)) {
            return [
                'success' => false,
                'message' => 'Failed to save sitemap content index.'
            ];
        }

        $entries = is_array($job['entries'] ?? null) ? $job['entries'] : [];

        Settings::update('sitemap_index.last_indexed_at', gmdate('Y-m-d H:i:s'));
        Settings::update('sitemap_index.last_checked_at', gmdate('Y-m-d H:i:s'));
        Settings::update('sitemap_index.sitemap_url', esc_url_raw((string) ($job['sitemap_url'] ?? '')));
        Settings::update('sitemap_index.indexed_count', (int) ($job['indexed'] ?? 0));
        Settings::update('sitemap_index.fingerprint', static::fingerprintEntries($entries));

        static::emitUpdateHooks('build');

        $job['status'] = 'completed';
        $job['completed_at'] = gmdate('c');
        $job['message'] = 'Sitemap indexing completed successfully.';
        $job['summary'] = [
            'total' => (int) ($job['total'] ?? 0),
            'indexed' => (int) ($job['indexed'] ?? 0),
            'skipped' => (int) ($job['skipped'] ?? 0),
            'failed' => (int) ($job['failed'] ?? 0),
            'sitemap_url' => (string) ($job['sitemap_url'] ?? ''),
        ];
        static::clearJobSitemapCache($jobId);

        return [
            'success' => true,
            'job' => $job
        ];
    }

    /**
     * @param string $jobId
     * @param array<string,mixed> $item
     * @return bool
     */
    private static function appendJobItem($jobId, $item)
    {
        $line = json_encode($item, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if ($line === false) {
            return false;
        }

        $path = static::getJobItemsFilePath($jobId);
        return file_put_contents($path, $line . "\n", FILE_APPEND | LOCK_EX) !== false;
    }

    /**
     * @param string $jobId
     * @return array<int,array<string,mixed>>
     */
    private static function readJobItems($jobId)
    {
        $path = static::getJobItemsFilePath($jobId);
        if (!file_exists($path)) {
            return [];
        }

        $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if (!is_array($lines)) {
            return [];
        }

        $items = [];
        foreach ($lines as $line) {
            $decoded = json_decode((string) $line, true);
            if (is_array($decoded)) {
                $items[] = $decoded;
            }
        }

        return $items;
    }

    /**
     * @param array<int,array{loc:string,lastmod:string}> $entries
     * @return string
     */
    private static function fingerprintEntries($entries)
    {
        $lines = [];
        foreach ($entries as $entry) {
            $lines[] = (string) ($entry['loc'] ?? '') . '|' . (string) ($entry['lastmod'] ?? '');
        }

        sort($lines);
        return hash('sha256', implode("\n", $lines));
    }

    /**
     * @param string $url
     * @return string
     */
    private static function buildFallbackTitle($url)
    {
        $path = (string) wp_parse_url($url, PHP_URL_PATH);
        if ($path === '' || $path === '/') {
            return get_bloginfo('name');
        }

        $trimmed = trim($path, '/');
        if ($trimmed === '') {
            return get_bloginfo('name');
        }

        $segments = explode('/', $trimmed);
        $lastSegment = (string) end($segments);
        $lastSegment = str_replace(['-', '_'], ' ', $lastSegment);

        return ucwords($lastSegment);
    }

    /**
     * @param \WP_Post $post
     * @return string
     */
    private static function getPostExcerpt($post)
    {
        if (!empty($post->post_excerpt)) {
            return wp_strip_all_tags($post->post_excerpt);
        }

        $content = wp_strip_all_tags($post->post_content);
        return wp_trim_words($content, 55);
    }

    /**
     * @param array<int,array<string,mixed>> $content
     * @return bool
     */
    private static function saveAll($content)
    {
        $filePath = static::getFilePath();
        $jsonContent = json_encode($content, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if ($jsonContent === false) {
            return false;
        }

        return file_put_contents($filePath, $jsonContent, LOCK_EX) !== false;
    }

    /**
     * @param string $jobId
     * @param array<string,mixed> $job
     * @return bool
     */
    private static function writeJob($jobId, $job)
    {
        $path = static::getJobFilePath($jobId);
        $encoded = json_encode($job, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if ($encoded === false) {
            return false;
        }

        return file_put_contents($path, $encoded, LOCK_EX) !== false;
    }

    /**
     * @param string $jobId
     * @return array<string,mixed>|null
     */
    private static function readJob($jobId)
    {
        $path = static::getJobFilePath($jobId);
        if (!file_exists($path)) {
            return null;
        }

        $content = file_get_contents($path);
        if ($content === false) {
            return null;
        }

        $decoded = json_decode($content, true);
        return is_array($decoded) ? $decoded : null;
    }

    /**
     * @param string $jobId
     * @return string
     */
    private static function getJobFilePath($jobId)
    {
        return Resources::getCacheDirPath() . '/sitemap-build-' . sanitize_file_name((string) $jobId) . '.json';
    }

    /**
     * @param string $jobId
     * @return string
     */
    private static function getJobItemsFilePath($jobId)
    {
        return Resources::getCacheDirPath() . '/sitemap-build-' . sanitize_file_name((string) $jobId) . '.items.jsonl';
    }

    /**
     * @param string $jobId
     * @return string
     */
    private static function getJobSitemapCacheDir($jobId)
    {
        return Resources::getCacheDirPath() . '/sitemap-build-' . sanitize_file_name((string) $jobId) . '.xml-cache';
    }

    /**
     * @param string $jobId
     * @return void
     */
    private static function clearJobSitemapCache($jobId)
    {
        $dir = static::getJobSitemapCacheDir((string) $jobId);
        if (!is_dir($dir)) {
            return;
        }

        $files = glob($dir . '/*');
        if (is_array($files)) {
            foreach ($files as $file) {
                if (is_file($file)) {
                    @wp_delete_file($file);
                }
            }
        }
    }

    /**
     * @param array<string,mixed> $job
     * @param bool $includeSummary
     * @return array<string,mixed>
     */
    private static function formatJobData($job, $includeSummary = false)
    {
        $total = max(0, (int) ($job['total'] ?? 0));
        $offset = max(0, (int) ($job['offset'] ?? 0));
        $progress = $total > 0 ? min(100, (int) floor(($offset / $total) * 100)) : 0;

        $data = [
            'job_id' => (string) ($job['job_id'] ?? ''),
            'status' => (string) ($job['status'] ?? 'running'),
            'message' => (string) ($job['message'] ?? ''),
            'mode' => (string) ($job['mode'] ?? 'auto'),
            'sitemap_url' => (string) ($job['sitemap_url'] ?? ''),
            'total' => $total,
            'processed' => $offset,
            'indexed' => (int) ($job['indexed'] ?? 0),
            'skipped' => (int) ($job['skipped'] ?? 0),
            'failed' => (int) ($job['failed'] ?? 0),
            'progress_percent' => $progress,
            'created_at' => (string) ($job['created_at'] ?? ''),
            'updated_at' => (string) ($job['updated_at'] ?? ''),
            'completed_at' => (string) ($job['completed_at'] ?? ''),
        ];

        if ($includeSummary) {
            $data['summary'] = is_array($job['summary'] ?? null) ? $job['summary'] : null;
        }

        return $data;
    }

    /**
     * @param string $operation
     * @return void
     */
    private static function emitUpdateHooks($operation)
    {
        $context = [
            'source' => 'sitemap',
            'operation' => sanitize_text_field((string) $operation),
            'timestamp' => gmdate('c'),
        ];

        Hooks::doAction(Hooks::INDEX_SITEMAP_UPDATED, $context);
    }
}
