<?php
/**
 * REST API endpoints for Rootz AI Discovery.
 *
 * @package Rootz_AI_Discovery
 */

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

class Rootz_Rest_Api {

    /**
     * Register all REST routes.
     */
    public function register_routes() {
        $namespace = 'rootz/v1';

        // GET /rootz/v1/ai.json — Organization info.
        register_rest_route( $namespace, '/ai.json', array(
            'methods'             => 'GET',
            'callback'            => array( $this, 'get_ai_json' ),
            'permission_callback' => '__return_true',
        ) );

        // GET /rootz/v1/policies — Machine-readable policies.
        // Supports ?full_text=1 to include complete policy page content.
        register_rest_route( $namespace, '/policies', array(
            'methods'             => 'GET',
            'callback'            => array( $this, 'get_policies' ),
            'permission_callback' => '__return_true',
            'args'                => array(
                'full_text' => array(
                    'type'              => 'string',
                    'default'           => '0',
                    'sanitize_callback' => 'sanitize_text_field',
                ),
            ),
        ) );

        // GET /rootz/v1/knowledge — Knowledge base.
        register_rest_route( $namespace, '/knowledge', array(
            'methods'             => 'GET',
            'callback'            => array( $this, 'get_knowledge' ),
            'permission_callback' => '__return_true',
        ) );

        // GET /rootz/v1/feed — AI-optimized feed.
        register_rest_route( $namespace, '/feed', array(
            'methods'             => 'GET',
            'callback'            => array( $this, 'get_feed' ),
            'permission_callback' => '__return_true',
        ) );

        // GET /rootz/v1/tools — Available tool manifest.
        register_rest_route( $namespace, '/tools', array(
            'methods'             => 'GET',
            'callback'            => array( $this, 'get_tools' ),
            'permission_callback' => '__return_true',
        ) );

        // GET /rootz/v1/search — Search site content.
        register_rest_route( $namespace, '/search', array(
            'methods'             => 'GET',
            'callback'            => array( $this, 'search_content' ),
            'permission_callback' => '__return_true',
            'args'                => array(
                'q' => array(
                    'required'          => true,
                    'type'              => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ),
                'limit' => array(
                    'type'              => 'integer',
                    'default'           => 10,
                    'sanitize_callback' => 'absint',
                ),
                'offset' => array(
                    'type'              => 'integer',
                    'default'           => 0,
                    'sanitize_callback' => 'absint',
                    'description'       => 'Skip this many results (for pagination). Use with limit.',
                ),
                'type' => array(
                    'type'              => 'string',
                    'default'           => '',
                    'sanitize_callback' => 'sanitize_text_field',
                    'description'       => 'Filter by post type: post, page, or empty for all.',
                ),
            ),
        ) );

        // GET /rootz/v1/verify — Verify page content hash against signed manifest.
        register_rest_route( $namespace, '/verify', array(
            'methods'             => 'GET',
            'callback'            => array( $this, 'verify_page_hash' ),
            'permission_callback' => '__return_true',
            'args'                => array(
                'page' => array(
                    'required'          => true,
                    'type'              => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ),
            ),
        ) );

        // GET /rootz/v1/status — Self-scoring site status for management console.
        register_rest_route( $namespace, '/status', array(
            'methods'             => 'GET',
            'callback'            => array( $this, 'get_status' ),
            'permission_callback' => '__return_true',
        ) );

        // GET /rootz/v1/context — AI assistant context file for plugin setup.
        register_rest_route( $namespace, '/context', array(
            'methods'             => 'GET',
            'callback'            => array( $this, 'get_context' ),
            'permission_callback' => '__return_true',
        ) );

        // GET /rootz/v1/page — Read any published page/post as structured markdown.
        register_rest_route( $namespace, '/page', array(
            'methods'             => 'GET',
            'callback'            => array( $this, 'get_page_content' ),
            'permission_callback' => '__return_true',
            'args'                => array(
                'path' => array(
                    'required'          => true,
                    'type'              => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                    'description'       => 'Page URL path (e.g., /about/, /blog/my-post/)',
                ),
            ),
        ) );
    }

    /**
     * GET /rootz/v1/ai.json
     *
     * @return WP_REST_Response
     */
    public function get_ai_json() {
        $generator = new Rootz_Ai_Json();
        $data = $generator->generate();
        $response = rest_ensure_response( $data );
        $response->header( 'Access-Control-Allow-Origin', '*' );
        return $response;
    }

    /**
     * GET /rootz/v1/policies
     *
     * @return WP_REST_Response
     */
    public function get_policies( $request = null ) {
        $license_key = get_option( 'rootz_content_license', 'all-rights-reserved' );
        $full_text   = $request && '1' === $request->get_param( 'full_text' );

        $policies = array(
            'contentLicense' => array(
                'type'          => $this->format_license( $license_key ),
                'permissions'   => array(),
                'restrictions'  => array(),
                'commercialUse' => ! in_array( $license_key, array( 'cc-by-nc-4.0', 'cc-by-nc-sa-4.0' ), true ),
            ),
            'termsOfService' => array(
                'summary'   => esc_html__( 'Standard usage terms apply. AI agents must respect rate limits and attribution requirements.', 'rootz-ai-discovery' ),
                'rateLimit' => '60/hour',
            ),
        );

        if ( '1' === get_option( 'rootz_allow_quoting', '1' ) ) {
            $policies['contentLicense']['permissions'][] = 'quote';
            $policies['contentLicense']['permissions'][] = 'summarize';
            $policies['contentLicense']['permissions'][] = 'cache_24h';
        }
        if ( '0' === get_option( 'rootz_allow_training', '0' ) ) {
            $policies['contentLicense']['restrictions'][] = 'no_training';
        }
        $policies['contentLicense']['restrictions'][] = 'no_full_reproduction';

        // Auto-discover policy pages from WordPress content.
        $discovered = self::discover_policy_pages();
        foreach ( $discovered as $key => $info ) {
            $entry = $info;

            // If ?full_text=1, include the complete plain-text page content.
            if ( $full_text && ! empty( $info['url'] ) ) {
                $page_id = url_to_postid( $info['url'] );
                if ( $page_id ) {
                    $page = get_post( $page_id );
                    if ( $page ) {
                        $entry['fullText'] = wp_strip_all_tags( $page->post_content );
                    }
                }
            }

            $policies[ $key ] = $entry;
        }

        $policies = $this->sign_response( $policies );

        $response = rest_ensure_response( $policies );
        $response->header( 'Access-Control-Allow-Origin', '*' );
        return $response;
    }

    /**
     * Return the total number of tools in the manifest.
     * Keep in sync with get_tools() categories.
     */
    public static function tool_count() {
        // discovery: getOrganizationInfo, getPolicies, getKnowledge, getFeed
        // actions:   searchContent, verifyPageHash, getPage
        // meta:      getStatus, getContext
        return 9;
    }

    public static function discover_policy_pages() {
        $cached = get_transient( 'rootz_discovered_policies' );
        if ( false !== $cached ) {
            return $cached;
        }

        $discovered = array();

        // Policy page patterns: key => [ slugs to try, type label ]
        $patterns = array(
            'privacy'       => array(
                'slugs' => array( 'privacy-policy', 'privacy', 'datenschutz', 'confidentialite' ),
                'type'  => 'privacy',
            ),
            'terms'         => array(
                'slugs' => array( 'terms-of-use', 'terms-of-service', 'terms', 'tos', 'terms-and-conditions' ),
                'type'  => 'terms',
            ),
            'dataPolicy'    => array(
                'slugs' => array( 'data-pii-policy', 'data-policy', 'pii-policy', 'data-protection', 'data-handling', 'gdpr' ),
                'type'  => 'data-protection',
            ),
            'cookiePolicy'  => array(
                'slugs' => array( 'cookie-policy', 'cookies', 'cookie-notice' ),
                'type'  => 'cookies',
            ),
            'aiPolicy'      => array(
                'slugs' => array( 'ai-usage-policy', 'ai-policy', 'ai-terms', 'ai-usage', 'bot-policy', 'automation-policy' ),
                'type'  => 'ai-usage',
            ),
            'accessibility' => array(
                'slugs' => array( 'accessibility', 'accessibility-statement', 'a11y' ),
                'type'  => 'accessibility',
            ),
            'dmca'          => array(
                'slugs' => array( 'dmca', 'copyright', 'copyright-policy', 'dmca-policy' ),
                'type'  => 'copyright',
            ),
        );

        // 1. WordPress built-in privacy page first.
        $wp_privacy_id = (int) get_option( 'wp_page_for_privacy_policy' );
        if ( $wp_privacy_id && 'publish' === get_post_status( $wp_privacy_id ) ) {
            $page = get_post( $wp_privacy_id );
            $discovered['privacy'] = array(
                'url'     => get_permalink( $wp_privacy_id ),
                'title'   => $page->post_title,
                'type'    => 'privacy',
                'summary' => wp_trim_words( wp_strip_all_tags( $page->post_content ), 100 ),
                'source'  => 'wordpress-setting',
            );
        }

        // 2. Scan for pages matching slug patterns.
        foreach ( $patterns as $key => $pattern ) {
            if ( isset( $discovered[ $key ] ) ) {
                continue;
            }
            foreach ( $pattern['slugs'] as $slug ) {
                $page = get_page_by_path( $slug );
                if ( $page && 'publish' === $page->post_status ) {
                    $discovered[ $key ] = array(
                        'url'     => get_permalink( $page ),
                        'title'   => $page->post_title,
                        'type'    => $pattern['type'],
                        'summary' => wp_trim_words( wp_strip_all_tags( $page->post_content ), 100 ),
                        'source'  => 'auto-discovered',
                    );
                    break;
                }
            }
        }

        // Cache for 1 hour.
        set_transient( 'rootz_discovered_policies', $discovered, HOUR_IN_SECONDS );
        return $discovered;
    }

    /**
     * Generate knowledge data (usable outside REST context).
     *
     * @return array Knowledge data.
     */
    public function generate_knowledge() {
        $cached = get_transient( 'rootz_knowledge_cache' );
        if ( false !== $cached ) {
            return $cached;
        }

        $knowledge = $this->build_knowledge();
        set_transient( 'rootz_knowledge_cache', $knowledge, HOUR_IN_SECONDS );
        return $knowledge;
    }

    /**
     * Generate feed data (usable outside REST context).
     *
     * @return array Feed data.
     */
    public function generate_feed() {
        $cached = get_transient( 'rootz_feed_cache' );
        if ( false !== $cached ) {
            return $cached;
        }

        $feed = $this->build_feed();
        set_transient( 'rootz_feed_cache', $feed, HOUR_IN_SECONDS );
        return $feed;
    }

    /**
     * GET /rootz/v1/knowledge
     *
     * @return WP_REST_Response
     */
    public function get_knowledge() {
        $knowledge = $this->generate_knowledge();
        $knowledge = $this->sign_response( $knowledge );
        $response  = rest_ensure_response( $knowledge );
        $response->header( 'Access-Control-Allow-Origin', '*' );
        return $response;
    }

    /**
     * Build knowledge data array.
     */
    private function build_knowledge() {
        $knowledge = array(
            'specVersion'  => ROOTZ_AI_DISCOVERY_SPEC,
            'generated'    => gmdate( 'c' ),
            'organization' => array(
                'name'    => get_option( 'rootz_organization_name', get_bloginfo( 'name' ) ),
                'tagline' => get_option( 'rootz_organization_tagline', get_bloginfo( 'description' ) ),
                'url'     => home_url(),
            ),
        );

        // Auto-detect "About" page.
        $about_page = $this->find_page_by_slug( array( 'about', 'about-us', 'who-we-are' ) );
        if ( $about_page ) {
            $knowledge['about'] = array(
                'title'   => $about_page->post_title,
                'url'     => get_permalink( $about_page ),
                'summary' => wp_trim_words( wp_strip_all_tags( $about_page->post_content ), 100 ),
            );
        }

        // Products/services from pages.
        $product_pages = $this->find_pages_by_slug( array( 'products', 'services', 'solutions', 'features' ) );
        if ( ! empty( $product_pages ) ) {
            $knowledge['products'] = array();
            foreach ( $product_pages as $page ) {
                $knowledge['products'][] = array(
                    'name'        => $page->post_title,
                    'url'         => get_permalink( $page ),
                    'description' => wp_trim_words( wp_strip_all_tags( $page->post_content ), 50 ),
                );
            }
        }

        // Categories as glossary.
        $categories = get_categories( array(
            'orderby'    => 'count',
            'order'      => 'DESC',
            'number'     => 20,
            'hide_empty' => true,
            'exclude'    => array( 1 ),
        ) );
        if ( ! empty( $categories ) ) {
            $knowledge['glossary'] = array();
            foreach ( $categories as $cat ) {
                $entry = array(
                    'term' => $cat->name,
                );
                if ( ! empty( $cat->description ) ) {
                    $entry['definition'] = $cat->description;
                }
                $knowledge['glossary'][] = $entry;
            }
        }

        return $knowledge;
    }

    /**
     * GET /rootz/v1/feed
     *
     * @return WP_REST_Response
     */
    public function get_feed() {
        $feed     = $this->generate_feed();
        $feed     = $this->sign_response( $feed );
        $response = rest_ensure_response( $feed );
        $response->header( 'Access-Control-Allow-Origin', '*' );
        return $response;
    }

    /**
     * Build feed data array.
     */
    private function build_feed() {
        $license_key = get_option( 'rootz_content_license', 'all-rights-reserved' );

        $feed = array(
            'specVersion' => ROOTZ_AI_DISCOVERY_SPEC,
            'generated'   => gmdate( 'c' ),
            'source'      => array(
                'name' => get_option( 'rootz_organization_name', get_bloginfo( 'name' ) ),
                'url'  => home_url(),
            ),
            'feedUrl'     => rest_url( 'rootz/v1/feed' ),
            'htmlUrl'     => home_url( '/blog' ),
            'items'       => array(),
        );

        $posts = get_posts( array(
            'post_status'    => 'publish',
            'posts_per_page' => 20,
            'orderby'        => 'date',
            'order'          => 'DESC',
        ) );

        foreach ( $posts as $post ) {
            $item = array(
                'id'        => $post->post_name . '-' . $post->ID,
                'title'     => $post->post_title,
                'url'       => get_permalink( $post ),
                'published' => gmdate( 'c', strtotime( $post->post_date_gmt ) ),
                'summary'   => wp_trim_words( wp_strip_all_tags( $post->post_content ), 60 ),
                'license'   => $this->format_license( $license_key ),
            );

            // Categories.
            $cats = wp_get_post_categories( $post->ID, array( 'fields' => 'names' ) );
            if ( ! empty( $cats ) ) {
                $item['categories'] = $cats;
            }

            // Tags.
            $tags = wp_get_post_tags( $post->ID, array( 'fields' => 'names' ) );
            if ( ! empty( $tags ) ) {
                $item['tags'] = $tags;
            }

            $feed['items'][] = $item;
        }

        return $feed;
    }

    /**
     * GET /rootz/v1/tools
     *
     * @return WP_REST_Response
     */
    public function get_tools() {
        $tools = array(
            'specVersion' => ROOTZ_AI_DISCOVERY_SPEC,
            'site'        => array(
                'name' => get_option( 'rootz_organization_name', get_bloginfo( 'name' ) ),
                'url'  => home_url(),
            ),
            'protocols'   => array( 'rest', 'webmcp' ),
            'categories'  => array(
                'discovery' => array(
                    'description' => 'Read-only information about the site.',
                    'tools'       => array(
                        array(
                            'name'        => 'getOrganizationInfo',
                            'description' => 'Get structured organization identity and capabilities.',
                            'method'      => 'GET',
                            'endpoint'    => rest_url( 'rootz/v1/ai.json' ),
                            'auth'        => 'none',
                        ),
                        array(
                            'name'        => 'getPolicies',
                            'description' => 'Get machine-readable content policies, licensing, and terms.',
                            'method'      => 'GET',
                            'endpoint'    => rest_url( 'rootz/v1/policies' ),
                            'auth'        => 'none',
                        ),
                        array(
                            'name'        => 'getKnowledge',
                            'description' => 'Get structured knowledge base about the organization.',
                            'method'      => 'GET',
                            'endpoint'    => rest_url( 'rootz/v1/knowledge' ),
                            'auth'        => 'none',
                        ),
                        array(
                            'name'        => 'getFeed',
                            'description' => 'Get AI-optimized feed of recent content.',
                            'method'      => 'GET',
                            'endpoint'    => rest_url( 'rootz/v1/feed' ),
                            'auth'        => 'none',
                        ),
                    ),
                ),
                'actions' => array(
                    'description' => 'Interactive tools for searching, reading, and verifying site content in real time from the owner\'s database.',
                    'tools'       => array(
                        array(
                            'name'        => 'searchContent',
                            'description' => 'Search site content by keyword. Returns matching pages and posts with excerpts. Supports pagination — use offset to get more results.',
                            'method'      => 'GET',
                            'endpoint'    => rest_url( 'rootz/v1/search' ),
                            'auth'        => 'none',
                            'parameters'  => array(
                                'q'      => array( 'type' => 'string', 'required' => true, 'description' => 'Search query' ),
                                'limit'  => array( 'type' => 'integer', 'default' => 10, 'description' => 'Max results (up to 50)' ),
                                'offset' => array( 'type' => 'integer', 'default' => 0, 'description' => 'Skip results for pagination. Use nextOffset from response.' ),
                                'type'   => array( 'type' => 'string', 'default' => '', 'description' => 'Filter: post, page, or empty for all' ),
                            ),
                        ),
                        array(
                            'name'        => 'getPage',
                            'description' => 'Read any published page or post as structured markdown with origin provenance, content hash, freshness metadata, and policy permissions. Use searchContent to find pages, then getPage to read them.',
                            'method'      => 'GET',
                            'endpoint'    => rest_url( 'rootz/v1/page' ),
                            'auth'        => 'none',
                            'parameters'  => array(
                                'path' => array( 'type' => 'string', 'required' => true, 'description' => 'Page URL path (e.g., /about/, /blog/my-post/)' ),
                            ),
                        ),
                        array(
                            'name'        => 'verifyPageHash',
                            'description' => 'Verify a page content hash against the signed manifest. Proves content integrity without trusting the transport layer.',
                            'method'      => 'GET',
                            'endpoint'    => rest_url( 'rootz/v1/verify' ),
                            'auth'        => 'none',
                            'parameters'  => array(
                                'page' => array( 'type' => 'string', 'required' => true, 'description' => 'Page path (e.g., /about/)' ),
                            ),
                        ),
                    ),
                ),
                'meta' => array(
                    'description' => 'Site health and configuration status.',
                    'tools'       => array(
                        array(
                            'name'        => 'getStatus',
                            'description' => 'Get site AI Discovery score, endpoint status, and metrics summary.',
                            'method'      => 'GET',
                            'endpoint'    => rest_url( 'rootz/v1/status' ),
                            'auth'        => 'none',
                        ),
                        array(
                            'name'        => 'getContext',
                            'description' => 'Get AI assistant context file with plugin setup instructions, current site status, and score breakdown. Read this first when helping a site owner configure AI Discovery.',
                            'method'      => 'GET',
                            'endpoint'    => rest_url( 'rootz/v1/context' ),
                            'auth'        => 'none',
                            'contentType' => 'text/markdown',
                        ),
                    ),
                ),
            ),
        );

        $tools = $this->sign_response( $tools );

        $response = rest_ensure_response( $tools );
        $response->header( 'Access-Control-Allow-Origin', '*' );
        return $response;
    }

    /**
     * GET /rootz/v1/search — Search site content.
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response
     */
    public function search_content( $request ) {
        $query  = $request->get_param( 'q' );
        $limit  = min( $request->get_param( 'limit' ), 50 );
        $offset = $request->get_param( 'offset' );
        $type   = $request->get_param( 'type' );

        $post_types = array( 'post', 'page' );
        if ( ! empty( $type ) && in_array( $type, array( 'post', 'page' ), true ) ) {
            $post_types = array( $type );
        }

        $wp_query = new WP_Query( array(
            's'              => $query,
            'post_type'      => $post_types,
            'post_status'    => 'publish',
            'posts_per_page' => $limit,
            'offset'         => $offset,
            'orderby'        => 'relevance',
        ) );

        $results = array();
        foreach ( $wp_query->posts as $post ) {
            $plain   = wp_strip_all_tags( $post->post_content );
            $results[] = array(
                'title'     => $post->post_title,
                'url'       => get_permalink( $post ),
                'path'      => wp_parse_url( get_permalink( $post ), PHP_URL_PATH ) ?: '/',
                'type'      => $post->post_type,
                'excerpt'   => wp_trim_words( $plain, 40 ),
                'published' => get_the_date( 'c', $post ),
                'modified'  => get_post_modified_time( 'c', true, $post ),
            );
        }

        $found_total = $wp_query->found_posts;
        $has_more    = ( $offset + $limit ) < $found_total;

        $data = array(
            'query'      => $query,
            'results'    => $results,
            'returned'   => count( $results ),
            'totalFound' => $found_total,
            'offset'     => $offset,
            'hasMore'    => $has_more,
            '_provenance' => $this->build_provenance(),
        );

        if ( $has_more ) {
            $data['nextOffset'] = $offset + $limit;
        }

        $data     = $this->sign_response( $data );
        $response = rest_ensure_response( $data );
        $response->header( 'Access-Control-Allow-Origin', '*' );
        return $response;
    }

    /**
     * GET /rootz/v1/verify — Verify page content hash against signed manifest.
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response
     */
    public function verify_page_hash( $request ) {
        $page_path = $request->get_param( 'page' );

        // Normalize: ensure leading slash, trim trailing slash for matching.
        $page_path = '/' . ltrim( $page_path, '/' );

        // Use the cached manifest (don't bust cache on every verify call).
        $generator = new Rootz_Ai_Json();
        $manifest = $generator->generate();

        // Find the page in the manifest.
        $manifest_entry = null;
        if ( ! empty( $manifest['pages'] ) ) {
            foreach ( $manifest['pages'] as $entry ) {
                // Match with and without trailing slash.
                $entry_path = rtrim( $entry['path'], '/' );
                $check_path = rtrim( $page_path, '/' );
                if ( $entry_path === $check_path || $entry['path'] === $page_path ) {
                    $manifest_entry = $entry;
                    break;
                }
            }
        }

        if ( ! $manifest_entry ) {
            $data = array(
                'page'     => $page_path,
                'verified' => false,
                'error'    => 'Page not found in manifest. Available pages: ' . implode( ', ', array_column( $manifest['pages'] ?? array(), 'path' ) ),
            );
            $data     = $this->sign_response( $data );
            $response = rest_ensure_response( $data );
            $response->header( 'Access-Control-Allow-Origin', '*' );
            return $response;
        }

        // Resolve the page from WordPress to compute current hash.
        $page_id = $this->resolve_page_id( $page_path );

        if ( ! $page_id ) {
            $data = array(
                'page'         => $page_path,
                'manifestHash' => $manifest_entry['contentHash'],
                'verified'     => false,
                'error'        => 'Could not resolve page path to a WordPress post.',
            );
            $data     = $this->sign_response( $data );
            $response = rest_ensure_response( $data );
            $response->header( 'Access-Control-Allow-Origin', '*' );
            return $response;
        }

        // Compute current content hash from the live database content.
        $post  = get_post( $page_id );
        $plain = wp_strip_all_tags( $post->post_content );
        $plain = preg_replace( '/\s+/', ' ', trim( $plain ) );
        $current_hash = 'sha256:' . hash( 'sha256', $plain );

        $match = ( $current_hash === $manifest_entry['contentHash'] );

        $signer_address = '';
        if ( ! empty( $manifest['_signature']['signer'] ) ) {
            $signer_address = $manifest['_signature']['signer'];
        }

        $data = array(
            'page'              => $page_path,
            'title'             => $manifest_entry['title'],
            'manifestHash'      => $manifest_entry['contentHash'],
            'currentHash'       => $current_hash,
            'match'             => $match,
            'verified'          => $match,
            'signer'            => $signer_address,
            'manifestGenerated' => $manifest['generated'] ?? '',
            'pageModified'      => $manifest_entry['modified'],
        );

        $data     = $this->sign_response( $data );
        $response = rest_ensure_response( $data );
        $response->header( 'Access-Control-Allow-Origin', '*' );
        return $response;
    }

    /**
     * Resolve a URL path to a WordPress post ID.
     *
     * Tries multiple strategies: url_to_postid with and without trailing slash,
     * then falls back to get_page_by_path for slug-based lookup.
     *
     * @param string $path URL path (e.g., /about/).
     * @return int Post ID or 0 if not found.
     */
    private function resolve_page_id( $path ) {
        $page_id = url_to_postid( home_url( $path ) );
        if ( $page_id ) {
            return $page_id;
        }

        $page_id = url_to_postid( home_url( rtrim( $path, '/' ) ) );
        if ( $page_id ) {
            return $page_id;
        }

        $slug = trim( $path, '/' );
        $post = get_page_by_path( $slug );
        if ( $post ) {
            return $post->ID;
        }

        return 0;
    }

    /**
     * GET /rootz/v1/status — Self-scoring site status.
     *
     * @return WP_REST_Response
     */
    public function get_status() {
        $data     = $this->build_status_data();
        $data     = $this->sign_response( $data );
        $response = rest_ensure_response( $data );
        $response->header( 'Access-Control-Allow-Origin', '*' );
        return $response;
    }

    /**
     * Build the status data array (shared by get_status and get_context).
     *
     * @return array Status data without signature.
     */
    private function build_status_data() {
        $breakdown = array();
        $total     = 0;

        // 1. Identity (max 15).
        $identity_score = 0;
        $identity_missing = array();
        if ( get_option( 'rootz_organization_name', '' ) ) { $identity_score += 3; } else { $identity_missing[] = 'name'; }
        $identity_score += 3; // domain is always present.
        if ( get_option( 'rootz_legal_name', '' ) ) { $identity_score += 3; } else { $identity_missing[] = 'legalName'; }
        if ( get_option( 'rootz_sector', '' ) ) { $identity_score += 3; } else { $identity_missing[] = 'sector'; }
        if ( get_option( 'rootz_organization_tagline', '' ) || get_option( 'rootz_tagline', '' ) ) { $identity_score += 3; } else { $identity_missing[] = 'tagline'; }
        $breakdown['identity'] = array(
            'score'   => $identity_score,
            'max'     => 15,
            'details' => $identity_missing ? 'Missing: ' . implode( ', ', $identity_missing ) : 'Complete',
        );
        $total += $identity_score;

        // 2. Policies (max 15).
        $policy_score = 0;
        if ( 'all-rights-reserved' !== get_option( 'rootz_content_license', 'all-rights-reserved' ) ) { $policy_score += 3; } else { $policy_score += 1; } // Even default gets 1.
        $discovered = self::discover_policy_pages();
        $policy_score += min( count( $discovered ) * 3, 12 );
        $breakdown['policies'] = array(
            'score'   => min( $policy_score, 15 ),
            'max'     => 15,
            'details' => count( $discovered ) . ' policy pages discovered',
        );
        $total += min( $policy_score, 15 );

        // 3. Signing (max 15).
        $signing_score = 0;
        $signing_detail = 'No wallet';
        $signer = new Rootz_Signer();
        if ( $signer->has_key() ) {
            $signing_score += 5;
            $signing_detail = 'Wallet active';
            if ( Rootz_Signer::has_gmp() ) {
                $signing_score += 5;
                $signing_detail = 'Wallet + signing active';
            }
        }
        if ( get_option( 'rootz_authorization_signature', '' ) ) {
            $signing_score += 5;
            $signing_detail .= ', delegated';
        } else {
            $signing_detail .= ', self-signed';
        }
        $breakdown['signing'] = array(
            'score'   => $signing_score,
            'max'     => 15,
            'details' => $signing_detail,
        );
        $total += $signing_score;

        // 4. Content (max 15).
        $pages = get_pages( array( 'post_status' => 'publish', 'number' => 20 ) );
        $posts = wp_count_posts();
        $page_count = count( $pages );
        $post_count = isset( $posts->publish ) ? $posts->publish : 0;
        $content_score = 0;
        $content_score += min( $page_count, 5 ); // Up to 5 points for pages.
        $content_score += min( $post_count, 5 ); // Up to 5 points for posts.
        if ( '1' === get_option( 'rootz_enable_content', '0' ) ) { $content_score += 5; }
        $breakdown['content'] = array(
            'score'   => min( $content_score, 15 ),
            'max'     => 15,
            'details' => $page_count . ' pages with hashes, ' . $post_count . ' posts',
        );
        $total += min( $content_score, 15 );

        // 5. Endpoints (max 15).
        $ep_score = 15; // All 5 core endpoints always respond if plugin is active.
        $breakdown['endpoints'] = array(
            'score'   => $ep_score,
            'max'     => 15,
            'details' => 'All 5 endpoints responding',
        );
        $total += $ep_score;

        // 6. Contacts (max 10).
        $contact_score = 0;
        if ( get_option( 'rootz_contact_email', '' ) || get_option( 'admin_email', '' ) ) { $contact_score += 3; }
        if ( get_option( 'rootz_contact_operator', '' ) ) { $contact_score += 3; }
        if ( get_option( 'rootz_contact_ai_email', '' ) ) { $contact_score += 2; }
        if ( get_option( 'rootz_contact_privacy_email', '' ) ) { $contact_score += 2; }
        $breakdown['contacts'] = array(
            'score'   => $contact_score,
            'max'     => 10,
            'details' => $contact_score . ' of 10 contact points',
        );
        $total += $contact_score;

        // 7. Knowledge (max 10).
        $knowledge_score = 0;
        $about = $this->find_page_by_slug( array( 'about', 'about-us', 'who-we-are' ) );
        if ( $about ) { $knowledge_score += 4; }
        $products = $this->find_pages_by_slug( array( 'products', 'services', 'solutions', 'features' ) );
        if ( ! empty( $products ) ) { $knowledge_score += 3; }
        $concepts = get_option( 'rootz_core_concepts', '' );
        if ( ! empty( $concepts ) ) { $knowledge_score += 3; }
        $breakdown['knowledge'] = array(
            'score'   => $knowledge_score,
            'max'     => 10,
            'details' => ( $about ? 'About page' : 'No about page' ) . ', ' . count( $products ) . ' product pages',
        );
        $total += $knowledge_score;

        // 8. Metrics (max 5).
        $metrics_active = class_exists( 'Rootz_Metrics' );
        $metrics_score  = $metrics_active ? 5 : 0;
        $breakdown['metrics'] = array(
            'score'   => $metrics_score,
            'max'     => 5,
            'details' => $metrics_active ? 'Logging active' : 'Not active',
        );
        $total += $metrics_score;

        // Grade.
        if ( $total >= 90 ) { $grade = 'A'; }
        elseif ( $total >= 80 ) { $grade = 'B'; }
        elseif ( $total >= 70 ) { $grade = 'C'; }
        elseif ( $total >= 60 ) { $grade = 'D'; }
        else { $grade = 'F'; }

        // Metrics summary.
        $metrics_summary = array( 'last24h' => 0, 'last7d' => 0, 'topAgent' => 'none' );
        if ( $metrics_active ) {
            $summary = Rootz_Metrics::get_summary( 7 );
            $metrics_summary['last24h'] = $summary['last24h'];
            $metrics_summary['last7d']  = $summary['last7d'];
            if ( ! empty( $summary['agents'] ) ) {
                $metrics_summary['topAgent'] = $summary['agents'][0]->ai_provider;
            }
        }

        $wallet_address = Rootz_Signer::stored_address();
        $authorization  = get_option( 'rootz_authorization_signature', '' ) ? 'delegated' : 'self-signed';

        return array(
            'domain'        => wp_parse_url( home_url(), PHP_URL_HOST ),
            'pluginVersion' => ROOTZ_AI_DISCOVERY_VERSION,
            'score'         => $total,
            'grade'         => $grade,
            'breakdown'     => $breakdown,
            'wallet'        => $wallet_address ? $wallet_address : 'none',
            'authorization' => $authorization,
            'endpoints'     => array(
                'ai_json'   => true,
                'policies'  => true,
                'knowledge' => '1' === get_option( 'rootz_enable_knowledge', '1' ),
                'feed'      => '1' === get_option( 'rootz_enable_feed', '1' ),
                'tools'     => true,
                'search'    => true,
                'verify'    => true,
                'status'    => true,
            ),
            'metrics'       => $metrics_summary,
        );
    }

    /**
     * GET /rootz/v1/context — AI assistant context file.
     *
     * Serves the ai.context.md shipped with the plugin so any AI assistant
     * can learn how to help configure AI Discovery for this site.
     *
     * @return WP_REST_Response
     */
    public function get_context() {
        $file = ROOTZ_AI_DISCOVERY_DIR . 'ai.context.md';
        if ( ! file_exists( $file ) ) {
            return new WP_REST_Response( array( 'error' => 'Context file not found' ), 404 );
        }

        $content = file_get_contents( $file );

        // Append current site status for the AI assistant.
        $status_data = $this->get_status_data();
        $content .= "\n\n---\n\n## Current Site Status (auto-appended)\n\n";
        $content .= "- **Domain**: " . wp_parse_url( home_url(), PHP_URL_HOST ) . "\n";
        $content .= "- **Plugin version**: " . ROOTZ_AI_DISCOVERY_VERSION . "\n";
        $content .= "- **Score**: " . $status_data['score'] . "/100 (Grade " . $status_data['grade'] . ")\n";
        $content .= "- **Wallet**: " . ( $status_data['wallet'] !== 'none' ? $status_data['wallet'] : 'Not configured' ) . "\n";
        $content .= "- **Authorization**: " . $status_data['authorization'] . "\n";
        $content .= "\n### Score Breakdown\n\n";
        foreach ( $status_data['breakdown'] as $cat => $info ) {
            $content .= "- **" . ucfirst( $cat ) . "**: " . $info['score'] . "/" . $info['max'] . " — " . $info['details'] . "\n";
        }

        // Setup log if one exists.
        $log = get_option( 'rootz_setup_log', '' );
        if ( ! empty( $log ) ) {
            $content .= "\n### Previous Setup Log\n\n```\n" . $log . "\n```\n";
        }

        $response = new WP_REST_Response( $content, 200 );
        $response->header( 'Content-Type', 'text/markdown; charset=utf-8' );
        $response->header( 'Access-Control-Allow-Origin', '*' );
        return $response;
    }

    /**
     * Get status data (used by get_context to append site status).
     */
    private function get_status_data() {
        return $this->build_status_data();
    }

    /**
     * Find a page by slug from a list of candidates.
     *
     * @param array $slugs Slugs to search for.
     * @return WP_Post|null
     */
    private function find_page_by_slug( $slugs ) {
        foreach ( $slugs as $slug ) {
            $page = get_page_by_path( $slug );
            if ( $page && 'publish' === $page->post_status ) {
                return $page;
            }
        }
        return null;
    }

    /**
     * Find pages matching any of the given slugs.
     *
     * @param array $slugs Slugs to search for.
     * @return array WP_Post objects.
     */
    private function find_pages_by_slug( $slugs ) {
        $found = array();
        foreach ( $slugs as $slug ) {
            $page = get_page_by_path( $slug );
            if ( $page && 'publish' === $page->post_status ) {
                $found[] = $page;
            }
        }
        return $found;
    }

    /**
     * Format license key.
     *
     * @param string $key License key.
     * @return string Formatted name.
     */
    private function format_license( $key ) {
        $map = array(
            'cc-by-4.0'           => 'CC-BY-4.0',
            'cc-by-sa-4.0'        => 'CC-BY-SA-4.0',
            'cc-by-nc-4.0'        => 'CC-BY-NC-4.0',
            'cc-by-nc-sa-4.0'     => 'CC-BY-NC-SA-4.0',
            'cc0'                 => 'CC0-1.0',
            'all-rights-reserved' => 'All Rights Reserved',
        );
        return isset( $map[ $key ] ) ? $map[ $key ] : $key;
    }

    /**
     * GET /rootz/v1/page — Read any published page/post as structured markdown.
     *
     * This is the "conversation mode" tool — AI discovers a page via search or
     * manifest, then requests its full content as clean markdown with provenance
     * and freshness metadata.
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response
     */
    public function get_page_content( $request ) {
        $page_path = $request->get_param( 'path' );

        // Normalize: ensure leading slash.
        $page_path = '/' . ltrim( $page_path, '/' );

        // Resolve the page from WordPress.
        $page_id = $this->resolve_page_id( $page_path );

        if ( ! $page_id ) {
            $data = array(
                'path'   => $page_path,
                'error'  => 'Page not found. Try searchContent to find available pages.',
                '_provenance' => $this->build_provenance(),
            );
            $data     = $this->sign_response( $data );
            $response = rest_ensure_response( $data );
            $response->set_status( 404 );
            $response->header( 'Access-Control-Allow-Origin', '*' );
            return $response;
        }

        $post = get_post( $page_id );

        if ( ! $post || 'publish' !== $post->post_status ) {
            $data = array(
                'path'   => $page_path,
                'error'  => 'Page is not published.',
                '_provenance' => $this->build_provenance(),
            );
            $data     = $this->sign_response( $data );
            $response = rest_ensure_response( $data );
            $response->set_status( 404 );
            $response->header( 'Access-Control-Allow-Origin', '*' );
            return $response;
        }

        // Convert HTML to clean markdown using the llms.txt converter.
        $llms = new Rootz_Llms_Txt();
        $markdown = $llms->html_to_markdown( $post->post_content );
        $markdown = html_entity_decode( $markdown, ENT_QUOTES | ENT_HTML5, 'UTF-8' );

        // Compute content hash from the plain text (same method as verify).
        $plain = wp_strip_all_tags( $post->post_content );
        $plain = preg_replace( '/\s+/', ' ', trim( $plain ) );
        $content_hash = 'sha256:' . hash( 'sha256', $plain );

        // Determine assertion type.
        $assertion_type = 'factual';
        if ( 'post' === $post->post_type ) {
            $assertion_type = 'editorial';
        }

        // Word count for AI context window planning.
        $word_count = str_word_count( $plain );

        // Get policies for this content.
        $license  = get_option( 'rootz_content_license', 'all-rights-reserved' );
        $quoting  = '1' === get_option( 'rootz_allow_quoting', '1' );
        $training = '1' === get_option( 'rootz_allow_training', '0' );

        // Categories and tags (for posts).
        $categories = array();
        $tags       = array();
        if ( 'post' === $post->post_type ) {
            $cats = wp_get_post_categories( $post->ID, array( 'fields' => 'names' ) );
            if ( ! empty( $cats ) ) {
                $categories = $cats;
            }
            $tag_list = wp_get_post_tags( $post->ID, array( 'fields' => 'names' ) );
            if ( ! empty( $tag_list ) ) {
                $tags = $tag_list;
            }
        }

        // Author info.
        $author_name = get_the_author_meta( 'display_name', $post->post_author );

        $data = array(
            'path'          => $page_path,
            'title'         => $post->post_title,
            'url'           => get_permalink( $post ),
            'type'          => $post->post_type,
            'assertionType' => $assertion_type,
            'author'        => $author_name,
            'wordCount'     => $word_count,
            'content'       => $markdown,
            'contentHash'   => $content_hash,
            'policies'      => array(
                'license'   => $this->format_license( $license ),
                'quoting'   => $quoting ? 'allowed' : 'not-allowed',
                'training'  => $training ? 'permitted' : 'not-permitted',
                'caching'   => $quoting ? 'cache_24h' : 'no-cache',
            ),
            '_origin'       => array(
                'domain'      => wp_parse_url( home_url(), PHP_URL_HOST ),
                'publishedAt' => get_the_date( 'c', $post ),
                'modifiedAt'  => get_post_modified_time( 'c', true, $post ),
                'servedAt'    => gmdate( 'c' ),
                'signer'      => Rootz_Signer::stored_address() ?: 'none',
            ),
            '_freshness'    => $this->build_freshness( $post ),
            '_provenance'   => $this->build_provenance(),
        );

        if ( ! empty( $categories ) ) {
            $data['categories'] = $categories;
        }
        if ( ! empty( $tags ) ) {
            $data['tags'] = $tags;
        }

        $data     = $this->sign_response( $data );
        $response = rest_ensure_response( $data );
        $response->header( 'Access-Control-Allow-Origin', '*' );
        return $response;
    }

    /**
     * Build freshness metadata for a post.
     *
     * Tells AI agents when this content expires and should be re-fetched.
     * Creates an incentive to return for fresh content rather than caching forever.
     *
     * @param WP_Post $post The post to build freshness for.
     * @return array Freshness metadata.
     */
    private function build_freshness( $post ) {
        $modified = strtotime( $post->post_modified_gmt );
        $age_days = ( time() - $modified ) / DAY_IN_SECONDS;

        // Adaptive freshness: recently modified content expires sooner.
        if ( $age_days < 1 ) {
            $max_age = 3600;         // 1 hour — actively being edited.
            $policy  = 'real-time';
        } elseif ( $age_days < 7 ) {
            $max_age = 86400;        // 1 day — recent content.
            $policy  = 'daily';
        } elseif ( $age_days < 30 ) {
            $max_age = 604800;       // 1 week — settling down.
            $policy  = 'weekly';
        } else {
            $max_age = 2592000;      // 30 days — stable content.
            $policy  = 'monthly';
        }

        return array(
            'maxAge'       => $max_age,
            'freshUntil'   => gmdate( 'c', time() + $max_age ),
            'refreshPolicy' => $policy,
            'lastModified' => get_post_modified_time( 'c', true, $post ),
            'contentAge'   => round( $age_days, 1 ) . ' days',
        );
    }

    /**
     * Build provenance metadata block.
     *
     * This block travels with every response so that even if the content
     * is scraped, cached, or trained on, the origin is embedded.
     *
     * @return array Provenance metadata.
     */
    private function build_provenance() {
        return array(
            'origin'        => wp_parse_url( home_url(), PHP_URL_HOST ),
            'servedAt'      => gmdate( 'c' ),
            'servedBy'      => 'rootz-ai-discovery/' . ROOTZ_AI_DISCOVERY_VERSION,
            'signer'        => Rootz_Signer::stored_address() ?: 'none',
            'specVersion'   => ROOTZ_AI_DISCOVERY_SPEC,
            'standard'      => 'https://rootz.global/ai-discovery',
        );
    }

    /**
     * Sign a data array and append a _signature block.
     *
     * If the plugin wallet + GMP are available, produces a full ECDSA signature.
     * Otherwise, produces a hash-only attestation (content integrity without crypto proof).
     *
     * @param array $data The data to sign (will NOT be modified in place).
     * @return array The data with _signature appended.
     */
    private function sign_response( $data ) {
        $signer = new Rootz_Signer();
        if ( $signer->has_key() && Rootz_Signer::has_gmp() ) {
            $data['_signature'] = $signer->sign_content( $data );
        } else {
            $content_json = wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
            $data['_signature'] = array(
                'contentHash'   => 'sha256:' . hash( 'sha256', $content_json ),
                'signedAt'      => gmdate( 'c' ),
                'method'        => 'hash-only',
                'authorization' => 'none',
            );
        }
        return $data;
    }
}
