<?php

declare(strict_types=1);

namespace ProRank\SEO\CLI;

use ProRank\SEO\Core\Content\H1Auditor;
use ProRank\SEO\Core\Content\H1Repairer;
use WP_CLI;

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

/**
 * Missing / empty H1 scan + source-level repair.
 *
 * Repairs are source-level only (Elementor widget / Gutenberg block / classic
 * content). There is NO global hidden-H1 injector. Every repair subcommand
 * defaults to a DRY RUN — pass --commit to actually write.
 *
 * ## EXAMPLES
 *
 *     wp prorank h1 scan
 *     wp prorank h1 repair --post_id=123
 *     wp prorank h1 repair --post_id=123 --commit
 *     wp prorank h1 repair-all --commit --high-confidence-only --limit=50
 */
class H1Command {

    /**
     * Scan all content for missing / empty H1.
     *
     * ## OPTIONS
     *
     * [--verify]
     * : Run the cache-safe rendered-HTML pass and report confirmed buckets
     *   (stored_missing / rendered_missing_confirmed / theme_renders_h1 /
     *   inconclusive). This is the real customer-facing missing-H1 number.
     *
     * [--limit=<n>]
     * : With --verify, max pages to rendered-check. Default 500, cap 500.
     *
     * [--format=<format>]
     * : table|json. Default table.
     *
     * @when after_wp_load
     */
    public function scan( $args, $assoc_args ): void {
        $auditor = new H1Auditor();

        if ( isset( $assoc_args['verify'] ) ) {
            $limit = max( 1, min( 500, (int) ( $assoc_args['limit'] ?? 500 ) ) );
            $v = $auditor->verify( $limit );
            if ( ( $assoc_args['format'] ?? 'table' ) === 'json' ) {
                WP_CLI::log( (string) wp_json_encode( $v ) );
                return;
            }
            $b = $v['buckets'];
            $complete = ! empty( $b['complete'] );
            WP_CLI::log( 'Scanned                    : ' . (int) $v['total_scanned'] );
            WP_CLI::log( 'Stored-content missing     : ' . (int) $b['stored_missing'] . ' (raw — not customer-facing)' );
            WP_CLI::log( 'Rendered-verified examined : ' . (int) $b['examined'] );
            WP_CLI::log( 'Not yet verified           : ' . (int) $b['unverified_remaining'] . ( $complete ? '' : ' (raise --limit to cover all)' ) );
            WP_CLI::log( ( $complete ? 'CONFIRMED missing H1       : ' : 'Confirmed missing H1 (so far): ' ) . (int) $b['rendered_missing_confirmed'] . ( $complete ? ' (full-site real number)' : ' — LOWER BOUND, not full-site' ) );
            WP_CLI::log( 'Theme renders an H1        : ' . (int) $b['theme_renders_h1'] );
            WP_CLI::log( 'Inconclusive (skipped)     : ' . (int) $b['inconclusive'] );
            WP_CLI::log( 'Bulk-excluded (utility)    : ' . (int) $b['bulk_excluded'] );
            $confirmed = array_values( array_filter( $v['items'], static fn( $i ) => $i['verification'] === 'rendered_missing_confirmed' ) );
            if ( empty( $confirmed ) ) {
                WP_CLI::success( 'No CONFIRMED rendered missing-H1 pages.' );
                return;
            }
            $rows = [];
            foreach ( array_slice( $confirmed, 0, 100 ) as $m ) {
                $rows[] = [
                    'id'      => (int) $m['id'],
                    'title'   => (string) $m['title'],
                    'type'    => (string) $m['type'],
                    'source'  => (string) $m['source'],
                    'utility' => ! empty( $m['bulk_excluded'] ) ? 'yes' : 'no',
                ];
            }
            WP_CLI\Utils\format_items( 'table', $rows, [ 'id', 'title', 'type', 'source', 'utility' ] );
            return;
        }

        $scan = $auditor->scan();

        if ( ( $assoc_args['format'] ?? 'table' ) === 'json' ) {
            WP_CLI::log( (string) wp_json_encode( $scan ) );
            return;
        }

        WP_CLI::log( 'Scanned             : ' . (int) $scan['total_scanned'] );
        WP_CLI::log( 'Stored-content miss : ' . (int) $scan['stored_missing'] . ' (raw — run with --verify for the real number)' );
        WP_CLI::log( 'High confidence     : ' . (int) $scan['high_confidence_count'] . ' (Elementor — authoritative)' );
        WP_CLI::log( 'Needs verification  : ' . (int) $scan['needs_verification_count'] . ' (classic/Gutenberg — theme may render H1)' );

        if ( empty( $scan['missing'] ) ) {
            WP_CLI::success( 'No missing-H1 pages found.' );
            return;
        }

        $rows = [];
        foreach ( array_slice( $scan['missing'], 0, 100 ) as $m ) {
            $rows[] = [
                'id'         => (int) $m['id'],
                'title'      => (string) $m['title'],
                'type'       => (string) $m['type'],
                'source'     => (string) $m['source'],
                'confidence' => (string) $m['confidence'],
                'fixable'    => ! empty( $m['fixable'] ) ? 'yes' : 'no',
            ];
        }
        WP_CLI\Utils\format_items( 'table', $rows, [ 'id', 'title', 'type', 'source', 'confidence', 'fixable' ] );
    }

    /**
     * Repair a single post's missing H1.
     *
     * ## OPTIONS
     *
     * --post_id=<id>
     * : The post to repair.
     *
     * [--commit]
     * : Actually write. Without this the command is a dry run.
     *
     * [--skip-rendered-check]
     * : Skip the live rendered-HTML check (advanced; may create a double H1).
     *
     * @when after_wp_load
     */
    public function repair( $args, $assoc_args ): void {
        $post_id = (int) ( $assoc_args['post_id'] ?? 0 );
        if ( $post_id <= 0 || ! get_post( $post_id ) ) {
            WP_CLI::error( 'A valid --post_id is required.' );
            return;
        }

        $dry_run = ! isset( $assoc_args['commit'] );
        $skip    = isset( $assoc_args['skip-rendered-check'] );

        $result = ( new H1Repairer() )->repair_post( $post_id, $dry_run, $skip );

        WP_CLI::log( 'Post     : ' . $post_id . ' — ' . get_the_title( $post_id ) );
        WP_CLI::log( 'Mode     : ' . ( $dry_run ? 'DRY RUN (no changes written)' : 'COMMIT' ) );
        WP_CLI::log( 'Source   : ' . (string) ( $result['source'] ?? ( $result['before']['source'] ?? 'unknown' ) ) );
        WP_CLI::log( 'Mutated  : ' . ( ! empty( $result['mutated'] ) ? 'yes' : 'no' ) );
        WP_CLI::log( 'Reason   : ' . (string) ( $result['reason'] ?? '' ) );

        if ( ! empty( $result['mutated'] ) ) {
            if ( $dry_run ) {
                WP_CLI::success( 'Repair available — re-run with --commit to apply.' );
            } else {
                WP_CLI::success( 'H1 repaired (a restorable backup was saved).' );
            }
        } else {
            WP_CLI::warning( 'No change applied: ' . (string) ( $result['reason'] ?? 'not fixable' ) );
        }
    }

    /**
     * Restore the most-recent pre-repair backup for a post.
     *
     * ## OPTIONS
     *
     * --post_id=<id>
     * : The post to restore.
     *
     * [--commit]
     * : Actually restore. Without this the command is a dry run.
     *
     * @when after_wp_load
     */
    public function restore( $args, $assoc_args ): void {
        $post_id = (int) ( $assoc_args['post_id'] ?? 0 );
        if ( $post_id <= 0 || ! get_post( $post_id ) ) {
            WP_CLI::error( 'A valid --post_id is required.' );
            return;
        }

        $dry_run = ! isset( $assoc_args['commit'] );
        $result  = ( new H1Repairer() )->restore_post( $post_id, $dry_run );

        if ( ( $result['reason'] ?? '' ) === 'no_backup' ) {
            WP_CLI::error( 'No restorable backup exists for post ' . $post_id . '.' );
            return;
        }

        WP_CLI::log( 'Post        : ' . $post_id . ' — ' . get_the_title( $post_id ) );
        WP_CLI::log( 'Backup from : ' . (string) ( $result['backed_up_at'] ?? '?' ) );
        WP_CLI::log( 'Mode        : ' . ( $dry_run ? 'DRY RUN (no changes written)' : 'COMMIT' ) );

        if ( $dry_run ) {
            WP_CLI::success( 'A backup exists — re-run with --commit to restore it.' );
        } else {
            WP_CLI::success( 'Post restored from the pre-repair backup.' );
        }
    }

    /**
     * Bulk-repair missing H1 across the site.
     *
     * ## OPTIONS
     *
     * [--commit]
     * : Actually write. Without this the command is a dry run.
     *
     * [--high-confidence-only]
     * : Only touch high-confidence items. This is the DEFAULT behaviour.
     *
     * [--include-low-confidence]
     * : Also repair low-confidence items (Gutenberg posts where the theme may
     *   already render an H1). Off by default for safety.
     *
     * [--include-utility]
     * : Also repair noindex / WooCommerce utility pages (Cart, Checkout, My
     *   Account). Excluded by default.
     *
     * [--limit=<n>]
     * : Max posts to touch in this run. Default 50, hard cap 200.
     *
     * @subcommand repair-all
     * @when after_wp_load
     */
    public function repair_all( $args, $assoc_args ): void {
        $dry_run   = ! isset( $assoc_args['commit'] );
        $high_only = ! isset( $assoc_args['include-low-confidence'] );
        $include_utility = isset( $assoc_args['include-utility'] );
        $limit     = max( 1, min( 200, (int) ( $assoc_args['limit'] ?? 50 ) ) );

        $scan     = ( new H1Auditor() )->scan();
        $repairer = new H1Repairer();

        $examined = 0;
        $mutated  = 0;
        $skipped_low = 0;
        $skipped_utility = 0;
        $skipped_other = 0;

        foreach ( $scan['missing'] as $candidate ) {
            if ( $examined >= $limit ) {
                break;
            }
            if ( empty( $candidate['fixable'] ) ) {
                continue;
            }
            if ( $high_only && ( $candidate['confidence'] ?? 'low' ) !== 'high' ) {
                $skipped_low++;
                continue;
            }
            if ( ! $include_utility && ! empty( $candidate['bulk_excluded'] ) ) {
                $skipped_utility++;
                continue;
            }

            $examined++;
            $result = $repairer->repair_post( (int) $candidate['id'], $dry_run );
            if ( ! empty( $result['mutated'] ) ) {
                $mutated++;
                WP_CLI::log( sprintf( '  [%s] #%d %s', $dry_run ? 'would fix' : 'fixed', (int) $candidate['id'], (string) $candidate['title'] ) );
            } else {
                $skipped_other++;
            }
        }

        WP_CLI::log( '' );
        WP_CLI::log( 'Mode                  : ' . ( $dry_run ? 'DRY RUN' : 'COMMIT' ) );
        WP_CLI::log( 'High-confidence only  : ' . ( $high_only ? 'yes' : 'no (low-confidence included)' ) );
        WP_CLI::log( 'Limit                 : ' . $limit );
        WP_CLI::log( 'Examined              : ' . $examined );
        WP_CLI::log( ( $dry_run ? 'Repairable            : ' : 'Repaired              : ' ) . $mutated );
        WP_CLI::log( 'Skipped low-confidence: ' . $skipped_low );
        WP_CLI::log( 'Skipped utility pages : ' . $skipped_utility );
        WP_CLI::log( 'Skipped (other)       : ' . $skipped_other );

        WP_CLI::success(
            $dry_run
                ? $mutated . ' post(s) can be repaired — re-run with --commit to apply.'
                : $mutated . ' post(s) repaired (each has a restorable backup).'
        );
    }
}

if ( defined( 'WP_CLI' ) && WP_CLI ) {
    WP_CLI::add_command( 'prorank h1', 'ProRank\\SEO\\CLI\\H1Command' );
}
