<?php

namespace ZenoChatbot\Services\Indexing\Engine;

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

/**
 * Internal Content Index JSON File Manager
 */
class InternalContentIndexEngine
{
    /**
     * Posts fetched per query while building the index. Keeps memory bounded
     * on large sites instead of hydrating every post at once.
     */
    private const QUERY_BATCH_SIZE = 500;

    /**
     * Get the JSON file path
     *
     * @return string
     */
    private static function getFilePath()
    {
        $upload_dir = wp_upload_dir();
        $plugin_dir = $upload_dir['basedir'] . '/zeno-chatbot-ai';

        // Create directory if it doesn't exist
        if (!file_exists($plugin_dir)) {
            wp_mkdir_p($plugin_dir);
        }

        return $plugin_dir . '/internal-content-index.json';
    }

    /**
     * Get all indexed content
     *
     * @return array
     */
    public static function getAll()
    {
        $file_path = static::getFilePath();

        if (!file_exists($file_path)) {
            return [];
        }

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

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

    /**
     * Build index from selected post-types
     *
     * @param array $selected_post_types
     * @return array Result with success status and stats
     */
    public static function buildIndex($selected_post_types)
    {
        if (!is_array($selected_post_types) || empty($selected_post_types)) {
            return [
                'success' => false,
                'message' => 'No post types selected for indexing'
            ];
        }

        // Validate post types
        $available_post_types = get_post_types(['public' => true]);
        $valid_post_types = [];

        foreach ($selected_post_types as $post_type) {
            if (isset($available_post_types[$post_type]) && $post_type !== 'attachment') {
                $valid_post_types[] = sanitize_text_field($post_type);
            }
        }

        if (empty($valid_post_types)) {
            return [
                'success' => false,
                'message' => 'No valid post types found for indexing'
            ];
        }

        $indexed_content = [];
        $total_indexed = 0;

        // Index each post type
        foreach ($valid_post_types as $post_type) {
            $items = static::collectPostTypeItems($post_type);
            $indexed_content = array_merge($indexed_content, $items);
            $total_indexed += count($items);
        }

        // Save to JSON file
        $result = static::saveAll($indexed_content);

        if (!$result) {
            return [
                'success' => false,
                'message' => 'Failed to save content index'
            ];
        }

        // Update metadata
        $settings = [
            'last_indexed_at'    => gmdate('Y-m-d H:i:s'),
            'indexed_post_types' => $valid_post_types,
            'indexed_count'      => $total_indexed
        ];

        Settings::update('internal_index', $settings);
//        Settings::update('internal_index.last_indexed_at', gmdate('Y-m-d H:i:s'));
//        Settings::update('internal_index.indexed_post_types', $valid_post_types);
//        Settings::update('internal_index.indexed_count', $total_indexed);
        static::emitUpdateHooks('build');

        return [
            'success' => true,
            'message' => "Successfully indexed {$total_indexed} items from " . count($valid_post_types) . " post types",
            'data'    => [
                'indexed_count' => $total_indexed,
                'post_types'    => $valid_post_types
            ]
        ];
    }

    /**
     * Collect index items for one post type, paging in fixed-size batches.
     *
     * Previously a single `numberposts => -1` query hydrated every post (full
     * WP_Post objects + primed meta/term caches) — an OOM risk on large sites.
     * Only title/permalink/excerpt are needed, so cache priming is skipped.
     *
     * @param string $post_type
     * @return array<int, array{title: string, link: string|false, context: string}>
     */
    private static function collectPostTypeItems($post_type)
    {
        $items = [];
        $page = 1;

        do {
            $posts = get_posts([
                'post_type'               => $post_type,
                'post_status'             => 'publish',
                'numberposts'             => static::QUERY_BATCH_SIZE,
                'paged'                   => $page,
                'orderby'                 => 'date',
                'order'                   => 'DESC',
                'cache_results'           => false,
                'update_post_meta_cache'  => false,
                'update_post_term_cache'  => false,
            ]);

            foreach ($posts as $post) {
                $items[] = [
                    'title'   => $post->post_title,
                    'link'    => get_permalink($post->ID),
                    'context' => static::getPostExcerpt($post)
                ];
            }

            $page++;
        } while (count($posts) === static::QUERY_BATCH_SIZE);

        return $items;
    }

    /**
     * Clear all indexed content
     *
     * @return array Result with success status
     */
    public static function clearIndex()
    {
        $current_count = static::count();

        $result = static::saveAll([]);

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

        // Clear metadata
        Settings::delete('internal_index.last_indexed_at');
        Settings::delete('internal_index.indexed_post_types');
        Settings::delete('internal_index.indexed_count');
        static::emitUpdateHooks('clear');

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

    /**
     * Get index statistics
     *
     * @return array
     */
    public static function getStats()
    {
        return [
            'total_count'        => static::count(),
            'last_indexed_at'    => Settings::get('internal_index.last_indexed_at'),
            'indexed_post_types' => Settings::get('internal_index.indexed_post_types', []),
            'indexed_count'      => Settings::get('internal_index.indexed_count', 0)
        ];
    }

    /**
     * Save all content to a JSON file
     *
     * @param array $content
     * @return bool
     */
    private static function saveAll($content)
    {
        $file_path = static::getFilePath();

        $json_content = json_encode($content, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        if ($json_content === false) {
            return false;
        }

        $result = file_put_contents($file_path, $json_content, LOCK_EX);
        return $result !== false;
    }

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

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

        // Generate excerpt from content
        $content = wp_strip_all_tags($post->post_content);

        return wp_trim_words($content, 55);
    }

    /**
     * Check if an index exists and has content
     *
     * @return bool
     */
    public static function exists()
    {
        return static::count() > 0;
    }

    /**
     * Get content by link
     *
     * @param string $link
     * @return array|null
     */
    public static function getByLink($link)
    {
        $content = static::getAll();

        foreach ($content as $item) {

            if ($item['link'] === $link) {
                return $item;
            }

        }

        return null;
    }

    /**
     * Emit index update hooks for cache invalidation.
     *
     * @param string $operation
     * @return void
     */
    private static function emitUpdateHooks($operation)
    {
        $context = [
            'source' => 'internal',
            'operation' => sanitize_text_field((string) $operation),
            'timestamp' => gmdate('c'),
        ];

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