<?php

namespace Miruni\RestApi;

use Miruni\BlockWidget\Miruni_Block_Widget_Manager;
use WP_Error;
use WP_REST_Response;
use Miruni\Elementor\Miruni_Elementor_Service;
use Miruni\UpdateUtils\MiruniFileType;


// Initialize the global variable
global $miruni_query_parameters;
if (!isset($miruni_query_parameters)) {
    $miruni_query_parameters = [];
}

/**
 * Handles fetching content by a composite ID for the Miruni service.
 *
 * @phpstan-type ContentEntry array{type: MiruniFileType, content: string, content_type: string, file_name: string, id: string|int, internal_id: string}
 * @phpstan-type HandlerInfo array{method: string, id_type: 'numeric'|'string'|'special_page_id'|'content_id', error_code?: string, error_message?: string}
 */
class Miruni_Content_By_Id_API extends Miruni_BaseAPI
{
    // Add a static flag to track when we're in capture mode

    public static string $namespace = 'miruni/v1';
    public static string $route = '/content/';

    public static function get_rest_url(int $page_id, string|null $snippet_secret = null): string
    {
        $params = ['page_id' => $page_id];
        if ($snippet_secret !== null) {
            $params['snippet_secret'] = $snippet_secret;
        }
        return self::_get_rest_url($params);
    }

    public static function setup(): void
    {

        error_log("Miruni: Setting up content by ID API");

        // Register REST API endpoint
        add_action('rest_api_init', function () {
            error_log("registering rest api endpoint");
            // Original endpoint
            register_rest_route(self::$namespace, self::$route, [
                'methods' => 'GET',
                'callback' => [__CLASS__, 'get_content_by_id'],
                'permission_callback' => [__CLASS__, 'snippet_secret_required_permission_callback'],
                'args' => [
                    'content_id' => [
                        'required' => false,
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                    'page_id' => [
                        'required' => false,
                        'sanitize_callback' => 'absint',
                    ],
                ],
            ]);
        });
    }

    /**
     * Extracts type and ID from a composite content ID string.
     * @param string $content_id The composite content ID (e.g., "other_post_title_123").
     * @return array{string, string}|null An array containing the type and ID, or null if not matched.
     */
    private static function _get_details_from_id(string $content_id): ?array
    {
        // Get all enum case values
        $types = array_map(fn($case) => $case->value, MiruniFileType::cases());

        // Sort types by length, descending, to match longer prefixes first
        usort($types, function ($a, $b) {
            return strlen($b) <=> strlen($a);
        });

        // widgets don't follow the same pattern as other content types
        if (str_starts_with($content_id, 'wordpress_widget')) {
            // id is the last part after the last underscore
            $parts = explode('_', $content_id);
            $id = array_pop($parts); // Get the last part as ID
            $type = MiruniFileType::BLOCK_WIDGET->value; // Use the enum value directly
            // @phpstan-ignore notIdentical.alwaysTrue
            if ($id !== '' && $id !== null) { // Ensure an ID part exists
                return [$type, $id];
            }
        }

        // Iterate through sorted types
        foreach ($types as $type) {
            if (str_starts_with($content_id, $type . '_')) { // Check for prefix followed by underscore
                // Extract the ID from the content_id
                $id = substr($content_id, strlen($type . '_'));
                // @phpstan-ignore notIdentical.alwaysTrue
                if ($id !== '' && $id !== false) { // Ensure an ID part exists and substr didn't fail
                    return [$type, $id];
                }
            }
        }

        return null;
    }

    /**
     * Map content types to their handler methods and expected ID types.
     * @return array<string, HandlerInfo>
     */
    private static function get_handler_map()
    {
        $handler_map = [
                // Types requiring numeric ID from content_id (Post ID or Menu Item ID)
            MiruniFileType::OTHER_POST_CONTENT->value => ['method' => 'get_other_post_content_by_id', 'id_type' => 'numeric', 'error_code' => 'post_not_found', 'error_message' => 'Post not found for the provided ID'],
            MiruniFileType::OTHER_POST_EXCERPT->value => ['method' => 'get_other_post_content_by_id', 'id_type' => 'numeric', 'error_code' => 'post_not_found', 'error_message' => 'Post not found for the provided ID'],
            MiruniFileType::OTHER_POST_TITLE->value => ['method' => 'get_other_post_content_by_id', 'id_type' => 'numeric', 'error_code' => 'post_not_found', 'error_message' => 'Post not found for the provided ID'],
            MiruniFileType::POST->value => ['method' => 'get_post_content_by_id', 'id_type' => 'numeric', 'error_code' => 'post_not_found', 'error_message' => 'Post not found for the provided ID'],
            MiruniFileType::POST_CONTENT->value => ['method' => 'get_post_content_by_id', 'id_type' => 'numeric', 'error_code' => 'post_not_found', 'error_message' => 'Post not found for the provided ID'],
            MiruniFileType::POST_TITLE->value => ['method' => 'get_post_content_by_id', 'id_type' => 'numeric', 'error_code' => 'post_not_found', 'error_message' => 'Post not found for the provided ID'],
            MiruniFileType::MENU_ITEM_NAME->value => ['method' => 'get_menu_item_content_by_id', 'id_type' => 'numeric', 'error_code' => 'menu_item_not_found', 'error_message' => 'Menu item post not found for the provided ID'],
            MiruniFileType::MENU_ITEM_PARENT->value => ['method' => 'get_menu_item_content_by_id', 'id_type' => 'numeric', 'error_code' => 'menu_item_not_found', 'error_message' => 'Menu item post not found for the provided ID'],
            MiruniFileType::MENU_ITEM_URL->value => ['method' => 'get_menu_item_content_by_id', 'id_type' => 'numeric', 'error_code' => 'menu_item_not_found', 'error_message' => 'Menu item post not found for the provided ID'],
            MiruniFileType::MENU_ITEM_OBJECT_ID->value => ['method' => 'get_menu_item_content_by_id', 'id_type' => 'numeric', 'error_code' => 'menu_item_not_found', 'error_message' => 'Menu item post not found for the provided ID'],
            MiruniFileType::MENU_ITEM_OBJECT->value => ['method' => 'get_menu_item_content_by_id', 'id_type' => 'numeric', 'error_code' => 'menu_item_not_found', 'error_message' => 'Menu item post not found for the provided ID'],

                // Types requiring string ID (name) from content_id
            MiruniFileType::WP_OPTION->value => ['method' => 'get_wp_option_content', 'id_type' => 'string', 'error_code' => 'wp_option_not_found', 'error_message' => 'WordPress option not found.'], // Custom check needed in handler/caller
            MiruniFileType::THEME_MOD->value => ['method' => 'get_theme_mod_content', 'id_type' => 'string'], // Always returns an entry

                // Special case: Elementor JSON (needs page_id, potentially from request)
            MiruniFileType::ELEMENTOR_JSON->value => ['method' => 'get_elementor_json', 'id_type' => 'special_page_id', 'error_code' => 'elementor_data_not_found_or_empty', 'error_message' => 'Elementor data not found for the provided post ID or it was empty/invalid.'],
            MiruniFileType::BLOCK_WIDGET->value => ['method' => 'get_block_widget_by_id', 'id_type' => 'content_id', 'error_code' => 'block_widget_not_found', 'error_message' => 'Block widget not found for the provided ID'],

        ];
        return $handler_map;

    }

    /**
     * Return content by ID formatted for the AI content service
     * @param \WP_REST_Request<array<mixed>> $request
     * @return WP_Error|WP_REST_Response On success, the response data is array<int, ContentEntry>
     */
    public static function get_content_by_id(\WP_REST_Request $request): WP_Error|WP_REST_Response
    {
        $content_id = $request->get_param('content_id');

        if (empty($content_id)) {
            return new WP_Error('invalid_content_id', 'Content ID must be provided', ['status' => 400]);
        }

        $content_id = sanitize_text_field($content_id);
        $content_details = self::_get_details_from_id($content_id);

        // Handle numeric IDs (fallback for simple post ID requests)
        if (is_numeric($content_id) && !$content_details) {
            $page_id = intval($content_id);
            $content = self::get_other_post_content_by_id($page_id); // Use a default handler
            if (empty($content)) {
                return new WP_Error('post_not_found', 'Post not found for the provided numeric ID', ['status' => 404]);
            }
            return new WP_REST_Response($content, 200);
        }

        if (!$content_details) {
            return new WP_Error('invalid_content_id_format', 'Content ID format not recognized', ['status' => 400]);
        }

        [$type, $id_part] = $content_details; // $id_part is the string part after the type_ prefix

        // Look up the handler based on the type
        if (!isset(self::get_handler_map()[$type])) {
            return new WP_Error('unsupported_content_type', "Content type '{$type}' is not currently supported by this endpoint", ['status' => 400]);
        }

        $handler_info = self::get_handler_map()[$type];
        $handler_method = $handler_info['method'];
        $id_type = $handler_info['id_type'];
        $argument = null;

        // Validate and prepare the argument based on expected ID type
        switch ($id_type) {
            case 'numeric':
                if (!is_numeric($id_part)) {
                    // Determine specific error based on type group
                    $error_msg = str_contains($type, 'menu_item') ? 'Menu Item ID' : 'Post ID';
                    return new WP_Error("invalid_{$error_msg}_id", "{$error_msg} extracted from content ID must be numeric - received {$id_part}", ['status' => 400]);
                }
                $argument = intval($id_part);
                break;

            case 'string':
                $argument = $id_part; // Use the string ID part directly (option name, theme mod name)
                break;

            case 'content_id':
                // This case is for block widgets, which use the full content_id as the ID
                $argument = $content_id; // Use the full content_id as the ID
                break;

            case 'special_page_id':
                // Logic specific to Elementor JSON to find the page_id
                $page_id_param = $request->get_param('page_id');
                $page_id = $page_id_param ?: (is_numeric($id_part) ? intval($id_part) : 0); // Attempt to use id_part if numeric, else check param
                if ($page_id <= 0) {
                    return new WP_Error('invalid_page_id', 'Page ID is required to fetch Elementor data and must be numeric', ['status' => 400]);
                }
                $argument = $page_id;
                break;

            default:
                // Should not happen if map is correct
                return new WP_Error('internal_server_error', 'Invalid handler configuration', ['status' => 500]);
        }

        // Call the handler method
        /** @var array<int, ContentEntry> $content */
        $content = self::{$handler_method}($argument);

        // Handle WP_OPTION not found specifically (as get_option returns false)
        if ($type === MiruniFileType::WP_OPTION->value && get_option($argument, '___NOT_FOUND___') === '___NOT_FOUND___') {
            return new WP_Error($handler_info['error_code'] ?? 'not_found', $handler_info['error_message'] ?? 'Resource not found.', ['status' => 404]);
        }


        // Check if content is empty (usually indicates not found, except for WP_OPTION/THEME_MOD)
        if (empty($content) && $type !== MiruniFileType::WP_OPTION->value && $type !== MiruniFileType::THEME_MOD->value) {
            return new WP_Error($handler_info['error_code'] ?? 'not_found', $handler_info['error_message'] ?? 'Resource not found.', ['status' => 404]);
        }

        return new WP_REST_Response($content, 200);
    }

    /**
     * Fetches and formats post data (title, content, excerpt) by post ID.
     * @param int $page_id The ID of the post.
     * @return array<int, ContentEntry> An array of content entries, or empty array if post not found.
     */
    private static function get_other_post_content_by_id(int $page_id): array
    {
        // Fetch the post by ID
        $post = get_post($page_id);

        // Check if the post exists
        if (!($post instanceof \WP_Post)) {
            return []; // Return empty array if post not found
        }

        /** @var array<int, ContentEntry> $content */
        $content = [];

        // Add title entry if it exists
        if (!empty($post->post_title)) {
            $content[] = [
                'type' => MiruniFileType::OTHER_POST_TITLE, // Use enum instance
                'content' => $post->post_title,
                'content_type' => 'txt',
                'file_name' => $post->post_title, // Use title as filename
                'id' => $post->ID,
                'internal_id' => MiruniFileType::OTHER_POST_TITLE->value . "_{$post->ID}" // Use enum value for string ID
            ];
        }

        // Add content entry if it exists
        if (!empty($post->post_content)) {
            $content[] = [
                'type' => MiruniFileType::OTHER_POST_CONTENT, // Use enum instance
                'content' => $post->post_content,
                'content_type' => 'txt',
                'file_name' => $post->post_title ?: "post_{$post->ID}_content", // Fallback filename
                'id' => $post->ID,
                'internal_id' => MiruniFileType::OTHER_POST_CONTENT->value . "_{$post->ID}" // Use enum value for string ID
            ];
        }

        // Add excerpt entry if it exists
        if (!empty($post->post_excerpt)) {
            $content[] = [
                'type' => MiruniFileType::OTHER_POST_EXCERPT, // Use enum instance
                'content' => $post->post_excerpt,
                'content_type' => 'txt',
                'file_name' => $post->post_title ?: "post_{$post->ID}_excerpt", // Fallback filename
                'id' => $post->ID,
                'internal_id' => MiruniFileType::OTHER_POST_EXCERPT->value . "_{$post->ID}" // Use enum value for string ID
            ];
        }

        return $content;
    }

    /**
     * Fetches and formats post data (title, content, excerpt) by post ID.
     * @param int $page_id The ID of the post.
     * @return array<int, ContentEntry> An array of content entries, or empty array if post not found.
     */
    private static function get_post_content_by_id(int $page_id): array
    {
        // Fetch the post by ID
        $post = get_post($page_id);

        // Check if the post exists
        if (!($post instanceof \WP_Post)) {
            return []; // Return empty array if post not found
        }

        /** @var array<int, ContentEntry> $content */
        $content = [];

        // Add title entry if it exists
        if (!empty($post->post_title)) {
            $content[] = [
                'type' => MiruniFileType::POST_TITLE, // Use enum instance
                'content' => $post->post_title,
                'content_type' => 'txt',
                'file_name' => $post->post_title, // Use title as filename
                'id' => $post->ID,
                'internal_id' => MiruniFileType::POST_TITLE->value . "_{$post->ID}" // Use enum value for string ID
            ];
        }

        // Add content entry if it exists
        if (!empty($post->post_content)) {
            $content[] = [
                'type' => MiruniFileType::POST_CONTENT, // Use enum instance
                'content' => $post->post_content,
                'content_type' => 'txt',
                'file_name' => $post->post_title ?: "post_{$post->ID}_content", // Fallback filename
                'id' => $post->ID,
                'internal_id' => MiruniFileType::POST_CONTENT->value . "_{$post->ID}" // Use enum value for string ID
            ];
        }
        // Add excerpt entry if it exists
        if (!empty($post->post_excerpt)) {
            $content[] = [
                'type' => MiruniFileType::POST_CONTENT, // Use enum instance
                'content' => $post->post_excerpt,
                'content_type' => 'txt',
                'file_name' => $post->post_title ?: "post_{$post->ID}_excerpt", // Fallback filename
                'id' => $post->ID,
                'internal_id' => MiruniFileType::POST_CONTENT->value . "_{$post->ID}" // Use enum value for string ID
            ];
        }

        return $content;
    }

    /**
     * Fetches and formats menu item data by its post ID.
     * @param int $page_id The post ID of the menu item (nav_menu_item).
     * @return array<int, ContentEntry> An array of content entries for the menu item, or empty array if not found.
     */
    private static function get_menu_item_content_by_id(int $page_id): array
    {
        // Fetch the menu item post
        $post = get_post($page_id);

        // Check if the post exists and is a nav_menu_item
        if (!($post instanceof \WP_Post) || $post->post_type !== 'nav_menu_item') {
            error_log("Miruni: Menu item post not found or not a nav_menu_item for ID: " . $page_id);
            return []; // Return empty array if not found or wrong type
        }

        /** @var array<int, ContentEntry> $content */
        $content = [];
        $menu_item_id = $post->ID; // Use the actual post ID

        // 1. Menu Item Name (Title)
        if (!empty($post->post_title)) {
            $content[] = [
                'type' => MiruniFileType::MENU_ITEM_NAME,
                'content' => $post->post_title,
                'content_type' => 'txt',
                'file_name' => (string) $menu_item_id, // Use ID as filename for consistency
                'id' => $menu_item_id,
                'internal_id' => MiruniFileType::MENU_ITEM_NAME->value . "_{$menu_item_id}"
            ];
        }

        // 2. Menu Item Parent ID
        $parent_id = get_post_meta($menu_item_id, '_menu_item_menu_item_parent', true);
        $content[] = [ // Always add, even if empty, to represent the field
            'type' => MiruniFileType::MENU_ITEM_PARENT,
            'content' => (string) ($parent_id ?: '0'), // Default to '0' if empty or not set
            'content_type' => 'txt', // Representing an ID as text
            'file_name' => (string) $menu_item_id,
            'id' => $menu_item_id,
            'internal_id' => MiruniFileType::MENU_ITEM_PARENT->value . "_{$menu_item_id}"
        ];

        // 3. Menu Item URL
        $url = get_post_meta($menu_item_id, '_menu_item_url', true);
        $content[] = [ // Always add, even if empty
            'type' => MiruniFileType::MENU_ITEM_URL,
            'content' => (string) ($url ?: ''),
            'content_type' => 'txt', // URL is text
            'file_name' => (string) $menu_item_id,
            'id' => $menu_item_id,
            'internal_id' => MiruniFileType::MENU_ITEM_URL->value . "_{$menu_item_id}"
        ];

        // 4. Menu Item Object ID (Associated Post/Term ID)
        $object_id = get_post_meta($menu_item_id, '_menu_item_object_id', true);
        $content[] = [ // Always add, even if empty
            'type' => MiruniFileType::MENU_ITEM_OBJECT_ID,
            'content' => (string) ($object_id ?: ''),
            'content_type' => 'txt', // Representing an ID as text
            'file_name' => (string) $menu_item_id,
            'id' => $menu_item_id,
            'internal_id' => MiruniFileType::MENU_ITEM_OBJECT_ID->value . "_{$menu_item_id}"
        ];

        // 5. Menu Item Object Type (Associated Post/Term Type)
        $object_type = get_post_meta($menu_item_id, '_menu_item_object', true);
        $content[] = [ // Always add, even if empty
            'type' => MiruniFileType::MENU_ITEM_OBJECT,
            'content' => (string) ($object_type ?: ''),
            'content_type' => 'txt', // Type is text (e.g., 'post', 'page', 'category')
            'file_name' => (string) $menu_item_id,
            'id' => $menu_item_id,
            'internal_id' => MiruniFileType::MENU_ITEM_OBJECT->value . "_{$menu_item_id}"
        ];


        return $content;
    }

    /**
     * Fetches and formats a single WordPress option by its name.
     * @param string $option_name The name of the WordPress option.
     * @return array<int, ContentEntry> An array containing a single ContentEntry, or empty array if option not found/empty.
     */
    private static function get_wp_option_content(string $option_name): array
    {
        $option_value = get_option($option_name);

        // Check if the option exists. get_option returns false if not found and no default provided.
        // It can also return false, 0, or '' for valid options, so check specifically for non-existence if needed.
        // For simplicity, we'll format whatever get_option returns.
        // If you need to distinguish "doesn't exist" from "exists but is false/empty", add more checks here.

        /** @var array<int, ContentEntry> $content */
        $content = [];

        $content[] = [
            'type' => MiruniFileType::WP_OPTION,
            'content' => (string) $option_value, // Cast value to string
            'content_type' => 'txt', // Options are generally treated as text/string
            'file_name' => $option_name, // Filename is the option name
            'id' => $option_name, // ID is the option name
            'internal_id' => MiruniFileType::WP_OPTION->value . "_{$option_name}"
        ];

        return $content;
    }

    /**
     * Fetches and formats a single theme modification by its name.
     * @param string $theme_mod_name The name of the theme modification.
     * @return array<int, ContentEntry> An array containing a single ContentEntry.
     */
    private static function get_theme_mod_content(string $theme_mod_name): array
    {
        $theme_mod_value = get_theme_mod($theme_mod_name);

        // Determine content type - could be string, int, bool, array. Cast to string for simplicity.
        // If it's an array or object, json_encode might be better, but string casting is simpler for now.
        $content_value = is_scalar($theme_mod_value) ? (string) $theme_mod_value : json_encode($theme_mod_value);
        // If get_theme_mod returns false (not set), it will be cast to an empty string "".
        // If it's boolean false, it also becomes "". If boolean true, becomes "1".

        /** @var array<int, ContentEntry> $content */
        $content = [];

        if ($content_value === false) {
            error_log("Miruni: Failed to get theme mod for name: " . $theme_mod_name);
            return []; // Return empty array if not found
        }

        $content[] = [
            'type' => MiruniFileType::THEME_MOD,
            'content' => $content_value,
            'content_type' => gettype($theme_mod_value), // Report original type before casting/encoding
            'file_name' => $theme_mod_name, // Filename is the theme mod name
            'id' => $theme_mod_name, // ID is the theme mod name
            'internal_id' => MiruniFileType::THEME_MOD->value . "_{$theme_mod_name}"
        ];

        return $content;
    }

    /**
     * Fetches and formats Elementor JSON data by post ID, creating an entry for each element.
     * @param int $page_id The ID of the post.
     * @return array<int, ContentEntry> An array of content entries, or empty array if not found or invalid.
     */
    private static function get_elementor_json(int $page_id): array
    {
        // Fetch the Elementor data for the post
        $elementor_data = Miruni_Elementor_Service::get_page_data($page_id);

        if (empty($elementor_data)) {
            error_log("Miruni: No Elementor data found for post ID: " . $page_id);
            return []; // Return empty array if no data found
        }

        // Decode if it's a JSON string
        if (is_string($elementor_data)) {
            $decoded_data = json_decode($elementor_data, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                error_log("Miruni: Failed to decode Elementor JSON for post ID: " . $page_id . " Error: " . json_last_error_msg());
                return []; // Return empty if JSON is invalid
            }
            $elementor_data = $decoded_data;
        }

        // Ensure it's an array of elements (widgets/sections/columns)
        if (!is_array($elementor_data)) {
            error_log("Miruni: Elementor data is not an array for post ID: " . $page_id);
            return [];
        }

        // If it's an associative array (single element at root?), wrap it
        if (!empty($elementor_data) && !isset($elementor_data[0])) {
            $elementor_data = [$elementor_data];
        }


        /** @var array<int, ContentEntry> $content */
        $content = [];

        // Iterate through each element in the Elementor data
        foreach ($elementor_data as $item) {
            if (!is_array($item) || !isset($item['id'])) {
                error_log("Miruni: Skipping invalid Elementor item in post ID: " . $page_id);
                continue; // Skip items that are not arrays or don't have an ID
            }

            $item_id = $item['id']; // Elementor element ID (string)
            $item_json = json_encode($item); // Encode the individual element back to JSON

            if ($item_json === false) {
                error_log("Miruni: Failed to re-encode Elementor item JSON for item ID: " . $item_id . " in post ID: " . $page_id);
                continue; // Skip if encoding fails
            }


            $content[] = [
                'type' => MiruniFileType::ELEMENTOR_JSON, // Use enum instance
                'content' => $item_json, // Content is the JSON string of the element
                'content_type' => 'json',
                'file_name' => (string) $item_id, // Filename is the element ID
                'id' => (string) $item_id, // ID is the element ID (string)
                'internal_id' => MiruniFileType::ELEMENTOR_JSON->value . "_{$item_id}" // Use element ID for internal ID
            ];
        }

        return $content;
    }

    /**
     * Fetches and formats Elementor JSON data by post ID, creating an entry for each element.
     * @param string $block_widget_id The ID of the block_widget.
     * @return array<int, ContentEntry> An array of content entries, or empty array if not found or invalid.
     */
    private static function get_block_widget_by_id(string $block_widget_id): array
    {
        /** @var array<int, ContentEntry> $content */
        $content = [];
        $block_widget = Miruni_Block_Widget_Manager::get_widget_data_by_id($block_widget_id);
        if (!$block_widget) {
            error_log("Miruni: Block widget not found for ID: " . $block_widget_id);
            return [];
        }
        $content[] = [
            'type' => MiruniFileType::BLOCK_WIDGET,
            'content' => $block_widget->content,
            'content_type' => "html",
            'file_name' => $block_widget_id,
            'id' => $block_widget_id,
            'internal_id' => $block_widget_id
        ];
        return $content;
    }


}
