<?php
namespace ChangePostType;

use WP_PowerSuite\Core\AbstractModule;

class Module extends AbstractModule {

	private const MODULE_ID      = 'change-post-type';
	private const MODULE_VERSION = '2.0.2';
	private const NONCE_ACTION   = 'wpps_change_post_type';
	private const AJAX_ACTION    = 'wpps_change_post_type';

	/** @var list<string>|null */
	private ?array $enabled_types_cache = null;

	/** @var list<string>|null */
	private ?array $excluded_types_cache = null;

	/** @var array<string, mixed>|null */
	private ?array $settings_cache = null;

	/** @var array<string, \WP_Post_Type>|null */
	private ?array $target_types_cache = null;

	private bool $admin_ui_registered = false;

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Change Post Type', 'wp-powersuite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'Convert content between post, page, and other types from the editor, Quick Edit, or bulk actions.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return [ 'admin', 'ajax' ];
	}

	public function getDependencies(): array {
		return [];
	}

	protected function registerHooks(): void {
		// Always register transfer handlers (AJAX boots before `init`; enabled types are validated inside).
		add_action( 'wp_ajax_' . self::AJAX_ACTION, [ $this, 'ajaxChangePostType' ] );
		add_filter( 'wp_insert_post_data', [ $this, 'applyTypeFromPostRequest' ], 10, 2 );

		if ( did_action( 'init' ) ) {
			$this->registerAdminUi();
		} else {
			add_action( 'init', [ $this, 'registerAdminUi' ], 20 );
		}
	}

	/**
	 * Admin UI hooks — registered after `init` so post types exist.
	 */
	public function registerAdminUi(): void {
		if ( $this->admin_ui_registered || ! is_admin() ) {
			return;
		}

		if ( $this->getEnabledTypes() === [] ) {
			return;
		}

		$this->admin_ui_registered = true;

		add_action( 'add_meta_boxes', [ $this, 'registerMetaBox' ] );
		add_action( 'quick_edit_custom_box', [ $this, 'renderQuickEdit' ], 10, 2 );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueAssets' ] );
		add_action( 'admin_notices', [ $this, 'renderBulkNotice' ] );

		foreach ( $this->getEnabledTypes() as $post_type ) {
			add_filter( "bulk_actions-edit-{$post_type}", [ $this, 'addBulkActions' ] );
			add_filter( "handle_bulk_actions-edit-{$post_type}", [ $this, 'handleBulkAction' ], 10, 3 );
		}
	}

	public function ajaxChangePostType(): void {
		check_ajax_referer( self::NONCE_ACTION, 'nonce' );

		$post_id  = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
		$new_type = isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( $_POST['post_type'] ) ) : '';

		if ( $post_id <= 0 || $new_type === '' ) {
			wp_send_json_error( [ 'message' => __( 'Invalid request.', 'wp-powersuite' ) ], 400 );
		}

		$validated = $this->validateTransfer( $post_id, $new_type );
		if ( $validated === null ) {
			wp_send_json_error( [ 'message' => __( 'You cannot change this post to that type.', 'wp-powersuite' ) ], 403 );
		}

		if ( ! $this->executeTransfer( $post_id, $validated ) ) {
			wp_send_json_error( [ 'message' => __( 'Could not change post type.', 'wp-powersuite' ) ], 500 );
		}

		clean_post_cache( $post_id );

		$redirect = get_edit_post_link( $post_id, 'raw' );
		if ( ! is_string( $redirect ) || $redirect === '' ) {
			wp_send_json_error( [ 'message' => __( 'Post type changed but redirect failed.', 'wp-powersuite' ) ], 500 );
		}

		wp_send_json_success(
			[
				'post_type' => $validated,
				'redirect'  => $redirect,
			]
		);
	}

	/**
	 * @param array<string, mixed> $data
	 * @param array<string, mixed> $postarr
	 * @return array<string, mixed>
	 */
	public function applyTypeFromPostRequest( array $data, array $postarr ): array {
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return $data;
		}

		$post_id = isset( $postarr['ID'] ) ? (int) $postarr['ID'] : 0;
		if ( $post_id <= 0 || wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
			return $data;
		}

		$new_type = $this->readTypeFromPostRequest();
		if ( $new_type === null ) {
			return $data;
		}

		$validated = $this->validateTransfer( $post_id, $new_type );
		if ( $validated !== null ) {
			$data['post_type'] = $validated;
		}

		return $data;
	}

	public function registerMetaBox(): void {
		foreach ( $this->getEnabledTypes() as $post_type ) {
			add_meta_box(
				'wpps-change-post-type',
				__( 'Post Type', 'wp-powersuite' ),
				[ $this, 'renderMetaBox' ],
				$post_type,
				'side',
				'high',
				[ '__block_editor_compatible_meta_box' => true ]
			);
		}
	}

	public function renderMetaBox( \WP_Post $post ): void {
		$current_type = get_post_type( $post );
		if ( ! is_string( $current_type ) || ! $this->isEnabledType( $current_type ) ) {
			return;
		}

		$targets = $this->getTargetTypes();
		$current = get_post_type_object( $current_type );
		if ( $targets === [] || ! $current || ! current_user_can( 'edit_post', $post->ID ) ) {
			return;
		}

		wp_nonce_field( self::NONCE_ACTION, 'wpps_change_post_type_nonce' );
		?>
		<div
			class="wpps-change-post-type"
			data-post-id="<?php echo esc_attr( (string) $post->ID ); ?>"
			data-current-type="<?php echo esc_attr( $current_type ); ?>"
			data-nonce="<?php echo esc_attr( wp_create_nonce( self::NONCE_ACTION ) ); ?>"
			data-ajax-url="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>"
			data-action="<?php echo esc_attr( self::AJAX_ACTION ); ?>"
		>
			<label class="wpps-change-post-type__label" for="wpps_post_type_select">
				<?php esc_html_e( 'Post Type', 'wp-powersuite' ); ?>
			</label>
			<select name="wpps_post_type" id="wpps_post_type_select" class="wpps-change-post-type__select">
				<?php foreach ( $targets as $slug => $obj ) : ?>
					<?php if ( ! current_user_can( $obj->cap->edit_posts ) ) : continue; endif; ?>
					<option value="<?php echo esc_attr( $slug ); ?>" <?php selected( $current_type, $slug ); ?>>
						<?php echo esc_html( $obj->labels->singular_name ); ?>
					</option>
				<?php endforeach; ?>
			</select>
			<button type="button" class="button button-secondary wpps-change-post-type__apply" id="wpps_change_post_type_apply">
				<?php esc_html_e( 'Change post type', 'wp-powersuite' ); ?>
			</button>
			<p class="wpps-change-post-type__hint description">
				<?php esc_html_e( 'Select a type and click Change. Quick Edit and bulk actions are also available on list screens.', 'wp-powersuite' ); ?>
			</p>
		</div>
		<?php
	}

	public function renderQuickEdit( string $column_name, string $post_type ): void {
		if ( $column_name !== 'title' || ! $this->isEnabledType( $post_type ) ) {
			return;
		}

		static $rendered = false;
		if ( $rendered ) {
			return;
		}
		$rendered = true;

		$targets = $this->getTargetTypes();
		if ( $targets === [] ) {
			return;
		}
		?>
		<fieldset class="inline-edit-col-wpps inline-edit-col-right">
			<div class="inline-edit-group wp-clearfix">
				<label class="alignleft">
					<span class="title"><?php esc_html_e( 'Post Type', 'wp-powersuite' ); ?></span>
					<?php wp_nonce_field( self::NONCE_ACTION, 'wpps_change_post_type_nonce' ); ?>
					<select name="wpps_post_type" class="wpps-change-post-type__quickedit">
						<?php foreach ( $targets as $slug => $obj ) : ?>
							<?php if ( ! current_user_can( $obj->cap->edit_posts ) ) : continue; endif; ?>
							<option value="<?php echo esc_attr( $slug ); ?>"><?php echo esc_html( $obj->labels->singular_name ); ?></option>
						<?php endforeach; ?>
					</select>
				</label>
			</div>
		</fieldset>
		<?php
	}

	public function addBulkActions( array $actions ): array {
		foreach ( $this->getTargetTypes() as $slug => $obj ) {
			if ( ! current_user_can( $obj->cap->edit_posts ) ) {
				continue;
			}
			$actions[ 'wpps_change_type_' . $slug ] = sprintf(
				/* translators: %s: post type singular name */
				__( 'Change to %s', 'wp-powersuite' ),
				$obj->labels->singular_name
			);
		}

		return $actions;
	}

	public function handleBulkAction( string $redirect_to, string $action, array $post_ids ): string {
		if ( ! str_starts_with( $action, 'wpps_change_type_' ) ) {
			return $redirect_to;
		}

		$new_type = sanitize_key( substr( $action, strlen( 'wpps_change_type_' ) ) );
		$targets  = $this->getTargetTypes();
		if ( ! isset( $targets[ $new_type ] ) || ! current_user_can( $targets[ $new_type ]->cap->edit_posts ) ) {
			return $redirect_to;
		}

		$changed = 0;
		foreach ( $post_ids as $post_id ) {
			$post_id = (int) $post_id;
			if ( $this->validateTransfer( $post_id, $new_type ) === null ) {
				continue;
			}
			if ( $this->executeTransfer( $post_id, $new_type ) ) {
				++$changed;
			}
		}

		return add_query_arg( 'wpps_changed_post_type', $changed, $redirect_to );
	}

	public function renderBulkNotice(): void {
		$screen = get_current_screen();
		if ( ! $screen || $screen->base !== 'edit' ) {
			return;
		}

		$list_type = is_string( $screen->post_type ) ? $screen->post_type : 'post';
		if ( ! $this->isEnabledType( $list_type ) ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only notice
		if ( empty( $_GET['wpps_changed_post_type'] ) ) {
			return;
		}

		$count = (int) $_GET['wpps_changed_post_type'];
		if ( $count <= 0 ) {
			return;
		}

		echo '<div class="notice notice-success is-dismissible"><p>';
		printf(
			esc_html( _n( '%s post type changed.', '%s post types changed.', $count, 'wp-powersuite' ) ),
			esc_html( number_format_i18n( $count ) )
		);
		echo '</p></div>';
	}

	public function enqueueAssets( string $hook ): void {
		$base = defined( 'WP_POWERSUITE_PLUGIN_URL' ) ? WP_POWERSUITE_PLUGIN_URL : '';
		if ( $base === '' ) {
			return;
		}

		if ( in_array( $hook, [ 'post.php', 'post-new.php' ], true ) ) {
			$screen = get_current_screen();
			if ( ! $screen || ! $this->isEnabledType( (string) $screen->post_type ) ) {
				return;
			}

			$post_id = isset( $_GET['post'] ) ? (int) $_GET['post'] : 0;
			if ( $post_id <= 0 ) {
				return;
			}

			wp_enqueue_style(
				'wpps-change-post-type',
				$base . 'modules/change-post-type/assets/change-post-type.css',
				[],
				$this->assetVersion( 'change-post-type.css' )
			);
			wp_enqueue_script(
				'wpps-change-post-type-editor',
				$base . 'modules/change-post-type/assets/change-post-type-editor.js',
				[ 'jquery' ],
				$this->assetVersion( 'change-post-type-editor.js' ),
				true
			);
			wp_localize_script(
				'wpps-change-post-type-editor',
				'wppsChangePostType',
				[
					'i18n' => [
						'apply'   => __( 'Change post type', 'wp-powersuite' ),
						'working' => __( 'Changing post type…', 'wp-powersuite' ),
						'error'   => __( 'Could not change post type.', 'wp-powersuite' ),
					],
				]
			);

			return;
		}

		if ( $hook !== 'edit.php' ) {
			return;
		}

		global $typenow;
		$list_type = isset( $_GET['post_type'] ) ? sanitize_key( wp_unslash( $_GET['post_type'] ) ) : '';
		if ( $list_type === '' ) {
			$list_type = is_string( $typenow ) && $typenow !== '' ? $typenow : 'post';
		}

		if ( ! $this->isEnabledType( $list_type ) ) {
			return;
		}

		wp_enqueue_style(
			'wpps-change-post-type',
			$base . 'modules/change-post-type/assets/change-post-type.css',
			[],
			$this->assetVersion( 'change-post-type.css' )
		);
		wp_enqueue_script(
			'wpps-change-post-type',
			$base . 'modules/change-post-type/assets/change-post-type.js',
			[ 'jquery', 'inline-edit-post' ],
			$this->assetVersion( 'change-post-type.js' ),
			true
		);
		wp_localize_script(
			'wpps-change-post-type',
			'wppsChangePostTypeList',
			[
				'listType' => $list_type,
			]
		);
	}

	/**
	 * @return string|null
	 */
	private function validateTransfer( int $post_id, string $new_type ): ?string {
		$new_type = sanitize_key( $new_type );
		if ( $post_id <= 0 || $new_type === '' || ! post_type_exists( $new_type ) ) {
			return null;
		}

		$current_type = get_post_type( $post_id );
		if ( ! is_string( $current_type ) || $current_type === $new_type || ! $this->isEnabledType( $current_type ) ) {
			return null;
		}

		$targets = $this->getTargetTypes();
		if ( ! isset( $targets[ $new_type ] ) ) {
			return null;
		}

		if ( ! current_user_can( 'edit_post', $post_id ) || ! current_user_can( $targets[ $new_type ]->cap->edit_posts ) ) {
			return null;
		}

		return $new_type;
	}

	private function executeTransfer( int $post_id, string $new_type ): bool {
		$updated = wp_update_post(
			[
				'ID'        => $post_id,
				'post_type' => $new_type,
			],
			true
		);

		if ( is_wp_error( $updated ) ) {
			return false;
		}

		return get_post_type( $post_id ) === $new_type;
	}

	private function readTypeFromPostRequest(): ?string {
		if ( empty( $_POST['wpps_post_type'] ) || empty( $_POST['wpps_change_post_type_nonce'] ) ) {
			return null;
		}

		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['wpps_change_post_type_nonce'] ) ), self::NONCE_ACTION ) ) {
			return null;
		}

		$type = sanitize_key( wp_unslash( $_POST['wpps_post_type'] ) );

		return $type !== '' ? $type : null;
	}

	/**
	 * @return list<string>
	 */
	private function getEnabledTypes(): array {
		if ( ! did_action( 'init' ) ) {
			return $this->getSettings()['enabled_post_types'];
		}

		if ( $this->enabled_types_cache !== null ) {
			return $this->enabled_types_cache;
		}

		$out = [];
		foreach ( $this->getSettings()['enabled_post_types'] as $slug ) {
			$key = sanitize_key( (string) $slug );
			if ( $key !== '' && post_type_exists( $key ) && $this->isAllowedType( $key ) ) {
				$out[] = $key;
			}
		}

		$this->enabled_types_cache = array_values( array_unique( $out ) );

		return $this->enabled_types_cache;
	}

	private function isEnabledType( string $post_type ): bool {
		return in_array( $post_type, $this->getEnabledTypes(), true );
	}

	/**
	 * @return array<string, \WP_Post_Type>
	 */
	private function getTargetTypes(): array {
		if ( $this->target_types_cache !== null ) {
			return $this->target_types_cache;
		}

		$excluded = array_flip( $this->getExcludedTypes() );
		$types    = get_post_types( [ 'public' => true, 'show_ui' => true ], 'objects' );

		foreach ( array_keys( $types ) as $slug ) {
			if ( isset( $excluded[ $slug ] ) || ! $this->isAllowedType( $slug ) ) {
				unset( $types[ $slug ] );
			}
		}

		$this->target_types_cache = $types;

		return $types;
	}

	/**
	 * @return list<string>
	 */
	private function getExcludedTypes(): array {
		if ( $this->excluded_types_cache !== null ) {
			return $this->excluded_types_cache;
		}

		$this->excluded_types_cache = $this->getSettings()['excluded_types'];

		return $this->excluded_types_cache;
	}

	/**
	 * @return array{enabled_post_types: list<string>, excluded_types: list<string>}
	 */
	private function getSettings(): array {
		if ( $this->settings_cache !== null ) {
			return $this->settings_cache;
		}

		$raw = $this->moduleSettings()->all( [] );
		$this->settings_cache = self::normalizeSettings( is_array( $raw ) ? $raw : [] );

		return $this->settings_cache;
	}

	/**
	 * @return array{enabled_post_types: list<string>, excluded_types: list<string>}
	 */
	public static function getDefaultSettings(): array {
		return [
			'enabled_post_types' => [ 'post', 'page' ],
			'excluded_types'     => [],
		];
	}

	/**
	 * @param array<string, mixed> $raw
	 * @return array{enabled_post_types: list<string>, excluded_types: list<string>}
	 */
	public static function normalizeSettings( array $raw ): array {
		$defaults = self::getDefaultSettings();
		$allowed  = array_flip( self::getSelectableTypeSlugs() );

		$enabled = [];
		if ( isset( $raw['enabled_post_types'] ) && is_array( $raw['enabled_post_types'] ) ) {
			foreach ( $raw['enabled_post_types'] as $slug ) {
				$key = sanitize_key( (string) $slug );
				if ( $key !== '' && ( $allowed === [] || isset( $allowed[ $key ] ) ) ) {
					$enabled[] = $key;
				}
			}
		}
		if ( $enabled === [] ) {
			$enabled = $defaults['enabled_post_types'];
		}

		$excluded = [];
		if ( isset( $raw['excluded_types'] ) && is_array( $raw['excluded_types'] ) ) {
			foreach ( $raw['excluded_types'] as $slug ) {
				$key = sanitize_key( (string) $slug );
				if ( $key !== '' && ( $allowed === [] || isset( $allowed[ $key ] ) ) ) {
					$excluded[] = $key;
				}
			}
		}

		return [
			'enabled_post_types' => array_values( array_unique( $enabled ) ),
			'excluded_types'     => array_values( array_unique( $excluded ) ),
		];
	}

	private function isAllowedType( string $post_type ): bool {
		$blocked = apply_filters( 'wpps_change_post_type_excluded', self::getHardExcludedTypes() );

		return $post_type !== '' && ! in_array( $post_type, (array) $blocked, true );
	}

	private function assetVersion( string $filename ): string {
		$path = __DIR__ . '/assets/' . $filename;

		return is_readable( $path ) ? (string) filemtime( $path ) : self::MODULE_VERSION;
	}

	/**
	 * @return list<string>
	 */
	public static function getHardExcludedTypes(): array {
		return [ 'attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'wp_block' ];
	}

	/**
	 * @return list<string>
	 */
	public static function getSelectableTypeSlugs(): array {
		if ( ! did_action( 'init' ) ) {
			return self::getDefaultSettings()['enabled_post_types'];
		}

		return array_values(
			array_diff(
				get_post_types( [ 'public' => true, 'show_ui' => true ], 'names' ),
				self::getHardExcludedTypes()
			)
		);
	}
}
