<?php
if (!defined('ABSPATH')) exit;

class AISB_Schema {

    const META_JSON = '_aisb_schema_json';
    const META_TYPE = '_aisb_schema_type';

    public static function init() {
        add_action('wp_head', [__CLASS__, 'output_global'], 4);
        add_action('wp_head', [__CLASS__, 'output_head'], 5);
        add_action('wp_head', [__CLASS__, 'output_breadcrumbs'], 6);
    }

    public static function output_global() {
        if (!is_front_page() && !is_home()) {
            return;
        }
        $settings = aisb_get_settings();
        if (empty($settings['output_mode']) || $settings['output_mode'] !== 'enabled') {
            return;
        }
        if (!AISB_SEO_Plugins::should_output_frontend(0)) {
            return;
        }
        $graph = AISB_Rich_Results::build_global_graph();
        if (!$graph) {
            return;
        }
        self::echo_json_ld($graph);
    }

    public static function output_head() {
        if (!is_singular()) {
            return;
        }
        $settings = aisb_get_settings();
        if (empty($settings['output_mode']) || $settings['output_mode'] !== 'enabled') {
            return;
        }
        $post_id = get_queried_object_id();
        $post = get_post($post_id);
        if (!$post || !AISB_CPT_Registry::is_post_type_enabled($post->post_type)) {
            return;
        }
        if (!AISB_SEO_Plugins::should_output_frontend($post_id)) {
            return;
        }
        $json = self::get_json($post_id);
        if (!$json) {
            return;
        }
        self::echo_json_ld($json);
    }

    public static function output_breadcrumbs() {
        if (!is_singular()) {
            return;
        }
        $settings = aisb_get_settings();
        if (empty($settings['output_mode']) || $settings['output_mode'] !== 'enabled') {
            return;
        }
        $post_id = get_queried_object_id();
        if (!AISB_SEO_Plugins::should_output_frontend($post_id)) {
            return;
        }
        $crumbs = AISB_Rich_Results::build_breadcrumbs($post_id);
        if (!$crumbs) {
            return;
        }
        self::echo_json_ld($crumbs);
    }

    private static function echo_json_ld($data) {
        // JSON_HEX_TAG escapes "<" and ">" to \u003C/\u003E so the payload cannot break out of the <script> tag.
        $json = wp_json_encode($data, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG);
        echo "\n<script type=\"application/ld+json\">\n";
        echo $json; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- HTML-safe JSON-LD (JSON_HEX_TAG escapes angle brackets).
        echo "\n</script>\n";
    }

    public static function get_json($post_id) {
        $raw = get_post_meta($post_id, self::META_JSON, true);
        if (!$raw) {
            return null;
        }
        if (is_array($raw)) {
            return $raw;
        }
        $decoded = json_decode($raw, true);
        return is_array($decoded) ? $decoded : null;
    }

    public static function get_type($post_id) {
        $type = get_post_meta($post_id, self::META_TYPE, true);
        return $type ? sanitize_key($type) : '';
    }

    public static function save($post_id, $schema, $type = '') {
        if (is_string($schema)) {
            $decoded = json_decode($schema, true);
            if (!is_array($decoded)) {
                return new WP_Error('aisb_invalid_json', __('Invalid JSON', 'pewit-ai-schema-builder'));
            }
            $schema = $decoded;
        }
        if (!is_array($schema)) {
            return new WP_Error('aisb_invalid_schema', __('Schema must be an array', 'pewit-ai-schema-builder'));
        }
        $type = $type ?: AISB_Rich_Results::primary_type_public($schema);
        $schema = AISB_Rich_Results::enrich_from_post($schema, $post_id);
        update_post_meta($post_id, self::META_JSON, wp_slash(aisb_safe_json($schema)));
        if ($type) {
            update_post_meta($post_id, self::META_TYPE, sanitize_key($type));
        }
        AISB_SEO_Sync::sync_after_save($post_id, $schema, $type);
        return $schema;
    }

    public static function delete($post_id) {
        delete_post_meta($post_id, self::META_JSON);
        delete_post_meta($post_id, self::META_TYPE);
    }

    public static function detect_type($post) {
        if (!$post instanceof WP_Post) {
            $post = get_post($post);
        }
        if (!$post) {
            return 'WebPage';
        }
        return AISB_CPT_Registry::get_schema_type_for_post_type($post->post_type);
    }

    public static function available_types($post = null) {
        $catalog = AISB_CPT_Registry::schema_type_catalog();
        $out = [];
        foreach ($catalog as $key => $label) {
            $allowed = self::is_type_allowed($key, $post);
            $suffix = $allowed ? '' : ' (Pro)';
            $out[$key] = $label . $suffix;
        }
        return $out;
    }

    public static function free_types() {
        return ['Article', 'WebPage', 'AboutPage', 'ContactPage'];
    }

    public static function is_type_allowed($type, $post = null) {
        $type = sanitize_key($type);
        if (in_array($type, self::free_types(), true)) {
            if (in_array($type, ['AboutPage', 'ContactPage'], true)) {
                return $post && $post->post_type === 'page';
            }
            return true;
        }
        if ($type === 'Product') {
            return $post && $post->post_type === 'product' && class_exists('WooCommerce');
        }
        return isset(AISB_CPT_Registry::schema_type_catalog()[$type]);
    }

    public static function required_fields_hint($type) {
        $rules = AISB_Rich_Results::field_rules();
        if (empty($rules[$type])) {
            return '';
        }
        $req = implode(', ', $rules[$type]['required']);
        $rec = implode(', ', $rules[$type]['recommended']);
        $hint = "\nRequired for Google/Bing rich results: {$req}.";
        if ($rec) {
            $hint .= " Recommended: {$rec}.";
        }
        return $hint;
    }

    public static function validate($schema, $type = '') {
        if (!is_array($schema)) {
            return ['valid' => false, 'errors' => [__('Schema is not valid JSON', 'pewit-ai-schema-builder')], 'warnings' => []];
        }
        $rich = AISB_Rich_Results::validate($schema);
        return [
            'valid'    => empty($rich['errors']),
            'errors'   => $rich['errors'],
            'warnings' => $rich['warnings'],
            'type'     => $type ?: ($rich['type'] ?? ''),
            'google'   => !empty($rich['google']),
            'bing'     => !empty($rich['bing']),
            'score'    => AISB_Rich_Results_Score::compute($schema),
        ];
    }

    public static function normalize($schema, $type, $post) {
        if (empty($schema['@context'])) {
            $schema['@context'] = 'https://schema.org';
        }
        if (empty($schema['@type'])) {
            $schema['@type'] = $type;
        }
        if (in_array($type, ['WebPage', 'AboutPage', 'ContactPage', 'FAQPage'], true)) {
            if (empty($schema['url'])) {
                $schema['url'] = get_permalink($post);
            }
            if (empty($schema['name'])) {
                $schema['name'] = get_the_title($post);
            }
        }
        if (in_array($type, ['Article', 'BlogPosting'], true)) {
            if (empty($schema['headline'])) {
                $schema['headline'] = get_the_title($post);
            }
            if (empty($schema['datePublished'])) {
                $schema['datePublished'] = get_the_date('c', $post);
            }
            if (empty($schema['dateModified'])) {
                $schema['dateModified'] = get_the_modified_date('c', $post);
            }
        }
        return AISB_Rich_Results::enrich_from_post($schema, $post->ID);
    }

    public static function posts_without_schema($args = []) {
        $defaults = [
            'post_type'      => AISB_CPT_Registry::get_enabled_post_types(),
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'fields'         => 'ids',
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Intentional query for posts missing schema JSON meta.
            'meta_query'     => [
                'relation' => 'OR',
                [
                    'key'     => self::META_JSON,
                    'compare' => 'NOT EXISTS',
                ],
                [
                    'key'     => self::META_JSON,
                    'value'   => '',
                    'compare' => '=',
                ],
            ],
        ];
        $query = wp_parse_args($args, $defaults);
        return get_posts($query);
    }

    public static function count_with_schema() {
        global $wpdb;
        $enabled = AISB_CPT_Registry::get_enabled_post_types();
        if (empty($enabled)) {
            return 0;
        }
        $placeholders = implode(',', array_fill(0, count($enabled), '%s'));
        $sql = "SELECT COUNT(DISTINCT pm.post_id) FROM {$wpdb->postmeta} pm
            INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
            WHERE pm.meta_key = %s AND pm.meta_value != '' AND p.post_type IN ($placeholders)";
        $params = array_merge([self::META_JSON], $enabled);
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- IN() placeholders match enabled post type count; values passed to prepare().
        return (int) $wpdb->get_var($wpdb->prepare($sql, $params));
    }

    public static function count_without_schema() {
        return count(self::posts_without_schema(['posts_per_page' => -1]));
    }
}
