<?php
/**
 * WP-CLI commands.
 *
 * Every format these commands can emit is emitted for everyone. `--format=json`
 * is not gated, metered, or licence-checked: the report is assembled locally from
 * data already on the site, and encoding it costs nothing to give away.
 *
 * @package Upgrade_Pilot
 */

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

/**
 * Class UPilot_CLI
 */
class UPilot_CLI {

	/**
	 * Formats the report commands can emit.
	 *
	 * @var string[]
	 */
	private static $formats = array( 'table', 'json' );

	/**
	 * Plugin container.
	 *
	 * @var UPilot_Plugin
	 */
	private $plugin;

	/**
	 * Register commands.
	 *
	 * @param UPilot_Plugin $plugin Plugin container.
	 */
	public static function register( $plugin ) {
		$instance         = new self();
		$instance->plugin = $plugin;
		WP_CLI::add_command( 'upgrade-pilot', $instance );
	}

	/**
	 * Resolve --format.
	 *
	 * A usage check, not an entitlement check: it rejects formats that do not
	 * exist, never formats the caller is not allowed to have.
	 *
	 * @param array $assoc_args Named args.
	 * @return string
	 */
	private function format( $assoc_args ) {
		$format = isset( $assoc_args['format'] ) ? (string) $assoc_args['format'] : 'table';
		if ( ! in_array( $format, self::$formats, true ) ) {
			WP_CLI::error( sprintf( 'Unrecognised --format value "%s". Use one of: %s.', $format, implode( ', ', self::$formats ) ) );
		}
		return $format;
	}

	/**
	 * Emit an assembled report in the requested format.
	 *
	 * @param array  $report Report payload.
	 * @param string $format table|json.
	 */
	private function emit( $report, $format ) {
		if ( 'json' === $format ) {
			WP_CLI::line( (string) wp_json_encode( UPilot_Report::shape( $report ) ) );
			return;
		}
		$this->print_report( $report );
	}

	/**
	 * Run a full readiness scan synchronously and print the report.
	 *
	 * ## OPTIONS
	 *
	 * [--target-php=<version>]
	 * : Target PHP version for this run (e.g. 8.4). Defaults to the saved setting.
	 *
	 * [--format=<format>]
	 * : Output format.
	 * ---
	 * default: table
	 * options:
	 *   - table
	 *   - json
	 * ---
	 *
	 * ## EXAMPLES
	 *
	 *     wp upgrade-pilot scan
	 *     wp upgrade-pilot scan --target-php=8.4
	 *     wp upgrade-pilot scan --format=json
	 *
	 * @param array $args       Positional args.
	 * @param array $assoc_args Named args.
	 */
	public function scan( $args, $assoc_args ) {
		$format = $this->format( $assoc_args );
		$quiet  = ( 'json' === $format );

		$restore_target = null;
		if ( isset( $assoc_args['target-php'] ) ) {
			$target = preg_replace( '/[^0-9.]/', '', (string) $assoc_args['target-php'] );
			if ( ! preg_match( '/^\d\.\d$/', $target ) ) {
				WP_CLI::error( 'Invalid --target-php value. Use major.minor, e.g. --target-php=8.4' );
			}
			$settings               = UPilot_Store::get_option( 'upilot_settings', array() );
			$restore_target         = isset( $settings['target_php'] ) ? $settings['target_php'] : '8.4';
			$settings['target_php'] = $target;
			UPilot_Store::update_option( 'upilot_settings', $settings );
		}

		$scanner = $this->plugin->get( 'scanner' );

		// Progress chatter shares STDOUT with the payload, so it is silenced for
		// --format=json. `wp upgrade-pilot scan --format=json | jq .` has to parse.
		if ( ! $quiet ) {
			WP_CLI::log( 'Building scan queue…' );
		}
		$state = $scanner->start_scan();
		if ( ! $quiet ) {
			WP_CLI::log( sprintf( '%d files queued. Scanning…', isset( $state['total'] ) ? (int) $state['total'] : 0 ) );
		}

		// CLI has no web timeouts: loop ticks until done.
		$spins = 0;
		while ( isset( $state['status'] ) && 'running' === $state['status'] ) {
			$state = $scanner->process_tick();
			$spins++;
			if ( $spins > 10000 ) {
				WP_CLI::error( 'Scan did not converge; aborting.' );
			}
			if ( ! $quiet && 0 === $spins % 5 ) {
				WP_CLI::log( sprintf( '  … %d / %d files', isset( $state['done'] ) ? (int) $state['done'] : 0, isset( $state['total'] ) ? (int) $state['total'] : 0 ) );
			}
		}

		if ( ! $quiet ) {
			WP_CLI::log( 'Assembling report…' );
		}
		$report = $this->plugin->get( 'report' )->get( true );

		// --target-php applies to this run only: restore the saved setting.
		if ( null !== $restore_target ) {
			$settings               = UPilot_Store::get_option( 'upilot_settings', array() );
			$settings['target_php'] = $restore_target;
			UPilot_Store::update_option( 'upilot_settings', $settings );
		}

		$this->emit( $report, $format );
	}

	/**
	 * Show the current scan/report status.
	 *
	 * ## EXAMPLES
	 *
	 *     wp upgrade-pilot status
	 *
	 * @param array $args       Positional args.
	 * @param array $assoc_args Named args.
	 */
	public function status( $args, $assoc_args ) {
		$state = $this->plugin->get( 'scanner' )->get_state();
		if ( empty( $state ) ) {
			WP_CLI::log( 'No scan has been run yet. Run: wp upgrade-pilot scan' );
			return;
		}
		WP_CLI::log( sprintf( 'Scan status: %s', isset( $state['status'] ) ? $state['status'] : 'unknown' ) );
		WP_CLI::log( sprintf( 'Files: %d / %d (skipped: %d)', isset( $state['done'] ) ? (int) $state['done'] : 0, isset( $state['total'] ) ? (int) $state['total'] : 0, isset( $state['skipped'] ) ? (int) $state['skipped'] : 0 ) );
	}

	/**
	 * Print the last readiness report.
	 *
	 * ## OPTIONS
	 *
	 * [--format=<format>]
	 * : Output format.
	 * ---
	 * default: table
	 * options:
	 *   - table
	 *   - json
	 * ---
	 *
	 * ## EXAMPLES
	 *
	 *     wp upgrade-pilot report
	 *     wp upgrade-pilot report --format=json
	 *
	 * @param array $args       Positional args.
	 * @param array $assoc_args Named args.
	 */
	public function report( $args, $assoc_args ) {
		$this->emit( $this->plugin->get( 'report' )->get(), $this->format( $assoc_args ) );
	}

	/**
	 * Render a report to the terminal.
	 *
	 * @param array $report Report payload.
	 */
	private function print_report( $report ) {
		$verdict = isset( $report['verdict'] ) ? strtoupper( $report['verdict'] ) : '?';
		$badge   = 'GREEN' === $verdict ? '%G' : ( 'RED' === $verdict ? '%R' : '%Y' );

		WP_CLI::log( '' );
		WP_CLI::log( WP_CLI::colorize( sprintf( '%s■%%n  Verdict: %s — %s', $badge, $verdict, isset( $report['headline'] ) ? $report['headline'] : '' ) ) );
		WP_CLI::log( sprintf( 'Target PHP: %s', isset( $report['target_php'] ) ? $report['target_php'] : '?' ) );
		WP_CLI::log( '' );

		WP_CLI::log( '— Server checks —' );
		foreach ( (array) $report['server_items'] as $item ) {
			WP_CLI::log( sprintf( ' [%s] %s: %s', strtoupper( $item['status'] ), $item['label'], $item['detail'] ) );
		}

		WP_CLI::log( '' );
		WP_CLI::log( '— Plugins (WordPress.org metadata) —' );
		foreach ( (array) $report['metadata']['items'] as $item ) {
			if ( 'pass' === $item['status'] && 'ok' === $item['kind'] ) {
				continue; // Keep terminal output focused on what needs attention.
			}
			WP_CLI::log( sprintf( ' [%s] %s %s — %s', strtoupper( $item['status'] ), $item['name'], $item['version'], $item['detail'] ) );
		}

		$counts = isset( $report['scan_counts'] ) ? $report['scan_counts'] : array();
		WP_CLI::log( '' );
		WP_CLI::log(
			sprintf(
				'— Static scan (advisory) — %d possible errors, %d warnings, %d suppressed',
				isset( $counts['error'] ) ? (int) $counts['error'] : 0,
				isset( $counts['warning'] ) ? (int) $counts['warning'] : 0,
				isset( $counts['suppressed'] ) ? (int) $counts['suppressed'] : 0
			)
		);
		WP_CLI::log( 'Static findings are advisory: version-guarded or dead code paths commonly trigger them. Review in wp-admin → Upgrade Pilot → Scan Results.' );
		WP_CLI::log( '' );
	}
}
