<?php
/**
 * `wp blaminhor duplicator ...` — Content Duplicator commands.
 *
 * @package BlaminhorEssentials
 */

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

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

/**
 * Duplicate posts / pages / custom post types and taxonomy terms.
 *
 * Delegates to the module's public create_duplicate_post() /
 * create_duplicate_term() so meta, taxonomies, author and copy options
 * are honoured exactly like the admin row action.
 */
class Blaminhor_Essentials_CLI_Duplicator extends Blaminhor_Essentials_CLI_Module {

	/**
	 * @var string
	 */
	protected $module_id = 'duplicator';

	/**
	 * Show the current Duplicator settings.
	 *
	 * @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(
			array( 'key' => 'post_types',      'value' => implode( ',', (array) $settings['post_types'] ) ),
			array( 'key' => 'taxonomies',      'value' => implode( ',', (array) $settings['taxonomies'] ) ),
			array( 'key' => 'copy_title',      'value' => $settings['copy_title'] ? 'true' : 'false' ),
			array( 'key' => 'title_prefix',    'value' => (string) $settings['title_prefix'] ),
			array( 'key' => 'title_suffix',    'value' => (string) $settings['title_suffix'] ),
			array( 'key' => 'copy_date',       'value' => $settings['copy_date'] ? 'true' : 'false' ),
			array( 'key' => 'copy_status',     'value' => (string) $settings['copy_status'] ),
			array( 'key' => 'copy_author',     'value' => $settings['copy_author'] ? 'true' : 'false' ),
			array( 'key' => 'copy_taxonomies', 'value' => $settings['copy_taxonomies'] ? 'true' : 'false' ),
			array( 'key' => 'copy_meta',       'value' => $settings['copy_meta'] ? 'true' : 'false' ),
			array( 'key' => 'copy_children',   'value' => $settings['copy_children'] ? 'true' : 'false' ),
			array( 'key' => 'copy_comments',   'value' => $settings['copy_comments'] ? 'true' : 'false' ),
			array( 'key' => 'redirect_after',  'value' => (string) $settings['redirect_after'] ),
		);
		$this->format_for_output( $rows, $assoc_args, array( 'key', 'value' ) );
	}

	/**
	 * Update Duplicator settings (which post types/taxonomies are duplicable, copy flags…).
	 *
	 * ## OPTIONS
	 *
	 * [--copy-title=<bool>]
	 * [--title-prefix=<str>]
	 * [--title-suffix=<str>]
	 * [--copy-date=<bool>]
	 * [--copy-status=<status>]
	 * : One of WordPress core post statuses (draft, pending, private, publish…).
	 * [--copy-author=<bool>]
	 * [--copy-taxonomies=<bool>]
	 * [--copy-meta=<bool>]
	 * [--copy-children=<bool>]
	 * [--copy-comments=<bool>]
	 *
	 * @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(
			'post_types'      => (array) $current['post_types'],
			'taxonomies'      => (array) $current['taxonomies'],
			'title_prefix'    => isset( $assoc_args['title-prefix'] ) ? (string) $assoc_args['title-prefix'] : (string) $current['title_prefix'],
			'title_suffix'    => isset( $assoc_args['title-suffix'] ) ? (string) $assoc_args['title-suffix'] : (string) $current['title_suffix'],
			'copy_status'     => isset( $assoc_args['copy-status'] ) ? (string) $assoc_args['copy-status'] : (string) $current['copy_status'],
			'redirect_after'  => (string) $current['redirect_after'],
		);

		$bool_map = array(
			'copy-title'      => 'copy_title',
			'copy-date'       => 'copy_date',
			'copy-author'     => 'copy_author',
			'copy-taxonomies' => 'copy_taxonomies',
			'copy-meta'       => 'copy_meta',
			'copy-children'   => 'copy_children',
			'copy-comments'   => 'copy_comments',
		);
		foreach ( $bool_map as $flag => $key ) {
			if ( array_key_exists( $flag, $assoc_args ) ) {
				if ( $this->parse_bool( $assoc_args[ $flag ] ) ) {
					$data[ $key ] = '1';
				}
			} elseif ( ! empty( $current[ $key ] ) ) {
				$data[ $key ] = '1';
			}
		}

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

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

	/**
	 * Duplicate a single post / page / CPT entry.
	 *
	 * ## OPTIONS
	 *
	 * <id>
	 * : Numeric post ID.
	 *
	 * [--porcelain]
	 * : Print only the new post ID.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor duplicator duplicate-post 42
	 *     $ NEW=$(wp blaminhor duplicator duplicate-post 42 --porcelain)
	 *
	 * @subcommand duplicate-post
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args: [<id>].
	 * @param array $assoc_args Associative args.
	 */
	public function duplicate_post( $args, $assoc_args ) {
		$this->ensure_capabilities();
		if ( empty( $args[0] ) || ! is_numeric( $args[0] ) ) {
			WP_CLI::error( __( 'Missing or invalid post ID.', 'blaminhor-essentials' ) );
		}
		$post_id = (int) $args[0];
		$post    = get_post( $post_id );
		if ( ! $post ) {
			WP_CLI::error( sprintf(
				/* translators: %d: post ID */
				__( 'No post found with ID %d.', 'blaminhor-essentials' ),
				$post_id
			) );
		}

		$module = $this->get_module_instance();
		if ( ! method_exists( $module, 'create_duplicate_post' ) ) {
			WP_CLI::error( __( 'This build of Duplicator does not expose create_duplicate_post().', 'blaminhor-essentials' ) );
		}

		$new_id = $module->create_duplicate_post( $post_id );
		if ( is_wp_error( $new_id ) ) {
			WP_CLI::error( $new_id->get_error_message() );
		}
		if ( ! $new_id ) {
			WP_CLI::error( __( 'Duplication failed (unknown error).', 'blaminhor-essentials' ) );
		}

		blaminhor_essentials_log( sprintf( 'Post #%d duplicated to #%d via WP-CLI.', $post_id, (int) $new_id ), 'info', 'cli', array( 'event' => 'duplicate_post' ) );

		if ( isset( $assoc_args['porcelain'] ) ) {
			WP_CLI::line( (string) (int) $new_id );
			return;
		}

		WP_CLI::success( sprintf(
			/* translators: 1: source post ID, 2: new post ID */
			__( 'Post #%1$d duplicated → new post #%2$d.', 'blaminhor-essentials' ),
			$post_id,
			(int) $new_id
		) );
	}

	/**
	 * Duplicate a single taxonomy term.
	 *
	 * ## OPTIONS
	 *
	 * <id>
	 * : Numeric term ID.
	 *
	 * --taxonomy=<taxonomy>
	 * : Taxonomy slug (category, post_tag, my-custom-tax…).
	 *
	 * [--porcelain]
	 * : Print only the new term ID.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor duplicator duplicate-term 7 --taxonomy=category
	 *
	 * @subcommand duplicate-term
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args: [<id>].
	 * @param array $assoc_args Associative args.
	 */
	public function duplicate_term( $args, $assoc_args ) {
		$this->ensure_capabilities();
		if ( empty( $args[0] ) || ! is_numeric( $args[0] ) ) {
			WP_CLI::error( __( 'Missing or invalid term ID.', 'blaminhor-essentials' ) );
		}
		if ( empty( $assoc_args['taxonomy'] ) ) {
			WP_CLI::error( __( 'Missing --taxonomy=<slug>.', 'blaminhor-essentials' ) );
		}
		$term_id  = (int) $args[0];
		$taxonomy = sanitize_key( (string) $assoc_args['taxonomy'] );

		if ( ! get_term( $term_id, $taxonomy ) ) {
			WP_CLI::error( sprintf(
				/* translators: 1: term ID, 2: taxonomy slug */
				__( 'No term #%1$d in taxonomy "%2$s".', 'blaminhor-essentials' ),
				$term_id,
				$taxonomy
			) );
		}

		$module = $this->get_module_instance();
		if ( ! method_exists( $module, 'create_duplicate_term' ) ) {
			WP_CLI::error( __( 'This build of Duplicator does not expose create_duplicate_term().', 'blaminhor-essentials' ) );
		}

		$new_id = $module->create_duplicate_term( $term_id, $taxonomy );
		if ( is_wp_error( $new_id ) ) {
			WP_CLI::error( $new_id->get_error_message() );
		}
		if ( ! $new_id ) {
			WP_CLI::error( __( 'Duplication failed (unknown error).', 'blaminhor-essentials' ) );
		}

		blaminhor_essentials_log( sprintf( 'Term #%d (%s) duplicated to #%d via WP-CLI.', $term_id, $taxonomy, (int) $new_id ), 'info', 'cli', array( 'event' => 'duplicate_term' ) );

		if ( isset( $assoc_args['porcelain'] ) ) {
			WP_CLI::line( (string) (int) $new_id );
			return;
		}

		WP_CLI::success( sprintf(
			/* translators: 1: source term ID, 2: new term ID */
			__( 'Term #%1$d duplicated → new term #%2$d.', 'blaminhor-essentials' ),
			$term_id,
			(int) $new_id
		) );
	}

	/**
	 * Duplicate several posts in one shot from a comma-separated ID list.
	 *
	 * Iterates create_duplicate_post() in order. Posts that no longer exist
	 * or fail to duplicate are counted as skipped — the command never stops
	 * on the first failure so a CI step can process partial batches safely.
	 *
	 * ## OPTIONS
	 *
	 * --ids=<csv>
	 * : Comma-separated list of post IDs to duplicate.
	 *
	 * [--porcelain]
	 * : Print only the new post IDs (one per line) for piping into other commands.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor duplicator bulk-duplicate --ids=12,34,56
	 *     $ wp blaminhor duplicator bulk-duplicate --ids="$(wp post list --post_type=page --format=ids)" --porcelain
	 *
	 * @subcommand bulk-duplicate
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args.
	 */
	public function bulk_duplicate( $args, $assoc_args ) {
		$this->ensure_capabilities();
		if ( empty( $assoc_args['ids'] ) ) {
			WP_CLI::error( __( 'Missing --ids=<csv>.', 'blaminhor-essentials' ) );
		}
		$ids = array_filter( array_map( 'intval', explode( ',', (string) $assoc_args['ids'] ) ) );
		if ( empty( $ids ) ) {
			WP_CLI::error( __( 'No valid post IDs provided.', 'blaminhor-essentials' ) );
		}

		$module = $this->get_module_instance();
		if ( ! method_exists( $module, 'create_duplicate_post' ) ) {
			WP_CLI::error( __( 'This build of Duplicator does not expose create_duplicate_post().', 'blaminhor-essentials' ) );
		}

		$porcelain = isset( $assoc_args['porcelain'] );
		$created   = array();
		$skipped   = 0;

		foreach ( $ids as $id ) {
			if ( ! get_post( $id ) ) {
				$skipped++;
				continue;
			}
			$new_id = $module->create_duplicate_post( $id );
			if ( is_wp_error( $new_id ) || ! $new_id ) {
				$skipped++;
				continue;
			}
			$created[] = (int) $new_id;
			if ( $porcelain ) {
				WP_CLI::line( (string) (int) $new_id );
			}
		}

		blaminhor_essentials_log(
			sprintf( 'Bulk-duplicate via WP-CLI: %d created, %d skipped.', count( $created ), $skipped ),
			'info',
			'cli',
			array( 'event' => 'duplicate_bulk', 'created' => count( $created ), 'skipped' => $skipped )
		);

		if ( $porcelain ) {
			return;
		}
		WP_CLI::success( sprintf(
			/* translators: 1: count of created posts, 2: count of skipped IDs */
			__( '%1$d duplicated, %2$d skipped.', 'blaminhor-essentials' ),
			count( $created ),
			$skipped
		) );
	}

	/**
	 * Dump Duplicator settings as a JSON envelope on STDOUT.
	 *
	 * Read-only — no settings are mutated. Suitable for piping into
	 * version control or another site's `wp blaminhor duplicator import`
	 * (importer to be added later).
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor duplicator export > duplicator-settings.json
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args (unused).
	 */
	public function export( $args, $assoc_args ) {
		$module  = $this->get_module_instance();
		$payload = array(
			'module'         => 'duplicator',
			'plugin_version' => defined( 'BLAMINHOR_ESSENTIALS_VERSION' ) ? BLAMINHOR_ESSENTIALS_VERSION : '',
			'exported_at'    => gmdate( 'c' ),
			'settings'       => $module->get_effective_settings(),
		);
		WP_CLI::line( wp_json_encode( $payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) );
	}
}
