<?php
/**
 * `wp blaminhor db-optimizer ...` — Database Optimizer commands.
 *
 * @package BlaminhorEssentials
 */

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

if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
	return;
}

/**
 * Scan, clean and optimize the WordPress database from the command line.
 *
 * Every cleanup task delegates to the module's existing public cleanup_*()
 * methods so the deletion criteria are identical to what the admin UI uses.
 */
class Blaminhor_Essentials_CLI_DB_Optimizer extends Blaminhor_Essentials_CLI_Module {

	/**
	 * @var string
	 */
	protected $module_id = 'db-optimizer';

	/**
	 * CLI task slug → module method + stats key.
	 *
	 * The stats key is used by `clean --dry-run` to report what would be
	 * removed without executing the cleanup. Order matters: it controls
	 * the default execution order when no --tasks is supplied.
	 */
	private function task_map() {
		return array(
			'revisions'              => array( 'method' => 'cleanup_revisions',              'stat' => 'revisions' ),
			'auto_drafts'            => array( 'method' => 'cleanup_auto_drafts',            'stat' => 'auto_drafts' ),
			'trashed_posts'          => array( 'method' => 'cleanup_trashed_posts',          'stat' => 'trashed_posts' ),
			'spam_comments'          => array( 'method' => 'cleanup_spam_comments',          'stat' => 'spam_comments' ),
			'trashed_comments'       => array( 'method' => 'cleanup_trashed_comments',       'stat' => 'trashed_comments' ),
			'pending_comments'       => array( 'method' => 'cleanup_pending_comments',       'stat' => 'pending_comments' ),
			'transients'             => array( 'method' => 'cleanup_transients',             'stat' => 'transients' ),
			'orphan_postmeta'        => array( 'method' => 'cleanup_orphan_postmeta',        'stat' => 'orphan_postmeta' ),
			'orphan_commentmeta'     => array( 'method' => 'cleanup_orphan_commentmeta',     'stat' => 'orphan_commentmeta' ),
			'orphan_termmeta'        => array( 'method' => 'cleanup_orphan_termmeta',        'stat' => 'orphan_termmeta' ),
			'orphan_relationships'   => array( 'method' => 'cleanup_orphan_relationships',   'stat' => 'orphan_relationships' ),
			'orphan_usermeta'        => array( 'method' => 'cleanup_orphan_usermeta',        'stat' => 'orphan_usermeta' ),
			'duplicated_postmeta'    => array( 'method' => 'cleanup_duplicated_postmeta',    'stat' => 'duplicated_postmeta' ),
			'duplicated_commentmeta' => array( 'method' => 'cleanup_duplicated_commentmeta', 'stat' => 'duplicated_commentmeta' ),
			'duplicated_usermeta'    => array( 'method' => 'cleanup_duplicated_usermeta',    'stat' => 'duplicated_usermeta' ),
			'oembed_postmeta'        => array( 'method' => 'cleanup_oembed_postmeta',        'stat' => 'oembed_postmeta' ),
			'dead_cron'              => array( 'method' => 'cleanup_dead_cron',              'stat' => 'dead_cron' ),
		);
	}

	/**
	 * Show the current DB Optimizer settings.
	 *
	 * ## OPTIONS
	 *
	 * [--format=<format>]
	 * : Render format (table, json, csv, yaml). Default: table.
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args.
	 */
	public function get( $args, $assoc_args ) {
		$module   = $this->get_module_instance();
		$settings = $module->get_effective_settings();

		$rows = array();
		foreach ( $settings as $k => $v ) {
			if ( is_bool( $v ) ) {
				$v = $v ? 'true' : 'false';
			}
			$rows[] = array( 'key' => $k, 'value' => (string) $v );
		}
		$this->format_for_output( $rows, $assoc_args, array( 'key', 'value' ) );
	}

	/**
	 * Update DB Optimizer settings (which tasks the auto-cleanup runs, schedule…).
	 *
	 * ## OPTIONS
	 *
	 * [--auto-cleanup=<bool>]
	 * : Run cleanup tasks automatically on the schedule below.
	 *
	 * [--schedule=<schedule>]
	 * : WP cron schedule slug (hourly, twicedaily, daily, weekly). Default: weekly.
	 *
	 * [--revisions-to-keep=<n>]
	 * : Number of recent revisions kept per post when cleanup_revisions runs.
	 *
	 * [--tasks=<csv>]
	 * : Comma-separated list of tasks to enable in the auto-cleanup. Any task
	 *   not listed is disabled. Pass `all` to enable every task. See `clean
	 *   --help` for the task slugs.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor db-optimizer set --auto-cleanup=true --schedule=daily
	 *     $ wp blaminhor db-optimizer set --tasks=revisions,spam_comments,transients
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args.
	 */
	public function set( $args, $assoc_args ) {
		$this->ensure_capabilities();
		$module  = $this->get_module_instance();
		$current = $module->get_effective_settings();

		$data = array(
			'schedule'           => isset( $assoc_args['schedule'] ) ? (string) $assoc_args['schedule'] : (string) $current['schedule'],
			'revisions_to_keep'  => isset( $assoc_args['revisions-to-keep'] ) ? (string) (int) $assoc_args['revisions-to-keep'] : (string) (int) $current['revisions_to_keep'],
		);

		$task_keys = array_keys( $this->task_map() );

		// If --tasks is supplied, that list determines the active task set.
		// Otherwise we preserve the current setting for each cleanup_*.
		if ( isset( $assoc_args['tasks'] ) ) {
			$wanted = 'all' === (string) $assoc_args['tasks']
				? $task_keys
				: array_filter( array_map( 'trim', explode( ',', (string) $assoc_args['tasks'] ) ) );

			foreach ( $task_keys as $task ) {
				$key = 'cleanup_' . $task;
				if ( in_array( $task, $wanted, true ) ) {
					$data[ $key ] = '1';
				}
			}
		} else {
			foreach ( $task_keys as $task ) {
				$key = 'cleanup_' . $task;
				if ( ! empty( $current[ $key ] ) ) {
					$data[ $key ] = '1';
				}
			}
		}

		// auto_cleanup boolean (forwarded last so the cron reschedule sees a
		// consistent setting snapshot).
		if ( isset( $assoc_args['auto-cleanup'] ) ) {
			if ( $this->parse_bool( $assoc_args['auto-cleanup'] ) ) {
				$data['auto_cleanup'] = '1';
			}
		} elseif ( ! empty( $current['auto_cleanup'] ) ) {
			$data['auto_cleanup'] = '1';
		}

		$result = $module->apply_settings( $data );
		if ( is_wp_error( $result ) ) {
			WP_CLI::error( $result->get_error_message() );
		}

		blaminhor_essentials_log( 'DB Optimizer settings updated via WP-CLI.', 'info', 'cli', array( 'event' => 'dbopt_set' ) );
		WP_CLI::success( __( 'DB Optimizer settings updated.', 'blaminhor-essentials' ) );
	}

	/**
	 * Scan the database and print what could be cleaned (read-only).
	 *
	 * ## OPTIONS
	 *
	 * [--format=<format>]
	 * : Render format (table, json, csv, yaml). Default: table.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor db-optimizer scan
	 *     $ wp blaminhor db-optimizer scan --format=json
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args.
	 */
	public function scan( $args, $assoc_args ) {
		$module = $this->get_module_instance();
		$stats  = (array) $module->get_stats();

		$rows = array();
		foreach ( $this->task_map() as $task => $info ) {
			$count = isset( $stats[ $info['stat'] ] ) ? (int) $stats[ $info['stat'] ] : 0;
			$rows[] = array(
				'task'  => $task,
				'count' => (string) $count,
			);
		}

		$rows[] = array( 'task' => '── totals ──',     'count' => '' );
		$rows[] = array( 'task' => 'total_items',      'count' => isset( $stats['total_items'] ) ? (string) (int) $stats['total_items'] : '0' );
		$rows[] = array( 'task' => 'total_size',       'count' => isset( $stats['total_size'] ) ? blaminhor_essentials_format_size( (int) $stats['total_size'] ) : '' );
		$rows[] = array( 'task' => 'total_overhead',   'count' => isset( $stats['total_overhead'] ) ? blaminhor_essentials_format_size( (int) $stats['total_overhead'] ) : '' );

		$this->format_for_output( $rows, $assoc_args, array( 'task', 'count' ) );
	}

	/**
	 * Run one or more cleanup tasks.
	 *
	 * Each task slug maps 1:1 to a public cleanup_*() method on the module,
	 * so behaviour is identical to clicking the corresponding row in the
	 * admin UI. Run `scan` first to see how many rows each task would touch.
	 *
	 * ## OPTIONS
	 *
	 * [--tasks=<csv>]
	 * : Comma-separated task slugs to execute, OR `all`. Defaults to `all`.
	 *   Available: revisions, auto_drafts, trashed_posts, spam_comments,
	 *   trashed_comments, pending_comments, transients, orphan_postmeta,
	 *   orphan_commentmeta, orphan_termmeta, orphan_relationships,
	 *   orphan_usermeta, duplicated_postmeta, duplicated_commentmeta,
	 *   duplicated_usermeta, oembed_postmeta, dead_cron.
	 *
	 * [--dry-run]
	 * : Don't execute anything — print the row counts that `scan` would
	 *   show for the selected tasks.
	 *
	 * [--yes]
	 * : Skip the interactive confirmation prompt.
	 *
	 * [--format=<format>]
	 * : Render format for the result table. Default: table.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor db-optimizer clean --tasks=revisions,spam_comments --dry-run
	 *     $ wp blaminhor db-optimizer clean --tasks=all --yes
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args.
	 */
	public function clean( $args, $assoc_args ) {
		$this->ensure_capabilities();
		$module = $this->get_module_instance();
		$map    = $this->task_map();
		$known  = array_keys( $map );

		$requested = isset( $assoc_args['tasks'] ) ? (string) $assoc_args['tasks'] : 'all';
		$tasks     = 'all' === $requested
			? $known
			: array_filter( array_map( 'trim', explode( ',', $requested ) ) );

		$unknown = array_diff( $tasks, $known );
		if ( ! empty( $unknown ) ) {
			WP_CLI::error( sprintf(
				/* translators: %s: comma-separated list of unknown task names */
				__( 'Unknown task(s): %s. Run `wp blaminhor db-optimizer clean --help` for the supported list.', 'blaminhor-essentials' ),
				implode( ', ', $unknown )
			) );
		}

		$dry_run = isset( $assoc_args['dry-run'] );

		if ( $dry_run ) {
			$stats = (array) $module->get_stats();
			$rows  = array();
			foreach ( $tasks as $task ) {
				$count  = isset( $stats[ $map[ $task ]['stat'] ] ) ? (int) $stats[ $map[ $task ]['stat'] ] : 0;
				$rows[] = array(
					'task'  => $task,
					'count' => (string) $count,
				);
			}
			$this->format_for_output( $rows, $assoc_args, array( 'task', 'count' ) );
			WP_CLI::log( __( '(dry-run — nothing was deleted.)', 'blaminhor-essentials' ) );
			return;
		}

		WP_CLI::confirm(
			sprintf(
				/* translators: %s: comma-separated list of tasks */
				__( 'About to run cleanup on: %s. Continue?', 'blaminhor-essentials' ),
				implode( ', ', $tasks )
			),
			$assoc_args
		);

		$rows = array();
		foreach ( $tasks as $task ) {
			$method = $map[ $task ]['method'];
			$count  = (int) call_user_func( array( $module, $method ) );
			$rows[] = array(
				'task'    => $task,
				'deleted' => (string) $count,
			);
		}

		blaminhor_essentials_log( sprintf( 'DB Optimizer cleanup via WP-CLI: %s.', implode( ',', $tasks ) ), 'info', 'cli', array( 'event' => 'dbopt_clean' ) );
		$this->format_for_output( $rows, $assoc_args, array( 'task', 'deleted' ) );
		WP_CLI::success( __( 'Cleanup complete.', 'blaminhor-essentials' ) );
	}

	/**
	 * Run OPTIMIZE TABLE on every WordPress table.
	 *
	 * ## OPTIONS
	 *
	 * [--yes]
	 * : Skip the interactive confirmation prompt.
	 *
	 * [--format=<format>]
	 * : Render format. Default: table.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor db-optimizer optimize-tables --yes
	 *
	 * @subcommand optimize-tables
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args.
	 */
	public function optimize_tables( $args, $assoc_args ) {
		$this->ensure_capabilities();
		$module = $this->get_module_instance();

		WP_CLI::confirm(
			__( 'Run OPTIMIZE TABLE on every WordPress table?', 'blaminhor-essentials' ),
			$assoc_args
		);

		$result = (array) $module->optimize_tables();

		$rows = array();
		foreach ( $result as $row ) {
			$row = (array) $row;
			$rows[] = array(
				'table'  => isset( $row['table'] ) ? (string) $row['table'] : '',
				'status' => isset( $row['status'] ) ? (string) $row['status'] : '',
				'message' => isset( $row['message'] ) ? (string) $row['message'] : '',
			);
		}

		blaminhor_essentials_log( 'DB Optimizer OPTIMIZE TABLE run via WP-CLI.', 'info', 'cli', array( 'event' => 'dbopt_optimize' ) );
		$this->format_for_output( $rows, $assoc_args, array( 'table', 'status', 'message' ) );
		WP_CLI::success( __( 'Tables optimized.', 'blaminhor-essentials' ) );
	}
}
