<?php

namespace CustomPostOrder;

use CustomPostOrder\Includes\OrderSaver;
use CustomPostOrder\Includes\PostTypeHelper;
use CustomPostOrder\Includes\QueryModifier;
use WP_PowerSuite\Core\AbstractModule;
use WP_PowerSuite\Core\Contracts\DataLocationsInterface;
use WP_PowerSuite\Ordering\Admin\ReorderAssets;
use WP_PowerSuite\Ordering\ConflictDetector;

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

require_once __DIR__ . '/includes/class-post-type-helper.php';

class Module extends AbstractModule implements DataLocationsInterface {

	private const MODULE_ID      = 'custom-post-order';
	private const MODULE_VERSION = '2.3.0';
	private const NONCE_ACTION   = 'wpps_custom_post_order';

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

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

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

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

	public function getDescription(): string {
		return __( 'Drag and drop to reorder posts, pages, and custom post types. Apply custom menu_order on the frontend and in supported queries.', 'wp-powersuite' );
	}

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

	public function getDataLocations(): array {
		return [
			'option_store_keys' => [ self::MODULE_ID ],
		];
	}

	protected function registerHooks(): void {
		$this->loadIncludes();

		if ( wp_doing_ajax() ) {
			add_action( 'wp_ajax_wpps_cpo_save_order', [ $this, 'ajaxSaveOrder' ] );
			add_action( 'wp_ajax_wpps_cpo_reset_order', [ $this, 'ajaxResetOrder' ] );
			return;
		}

		ConflictDetector::registerAdminNotices();

		$this->registerQueryModifierHooks();

		if ( is_admin() ) {
			$this->registerAdminHooks();
		}
	}

	private function registerQueryModifierHooks(): void {
		$settings = $this->getSettings();
		if ( empty( $settings['apply_frontend_main'] )
			&& empty( $settings['apply_frontend_archive'] )
			&& empty( $settings['apply_frontend_search'] )
			&& empty( $settings['apply_admin'] )
		) {
			return;
		}

		( new QueryModifier( $settings ) )->registerHooks();
	}

	private function loadIncludes(): void {
		$base = __DIR__ . '/includes/';
		require_once $base . 'class-order-saver.php';
		require_once $base . 'class-query-modifier.php';
	}

	private function registerAdminHooks(): void {
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueAdminAssets' ] );
		add_action( 'load-edit.php', [ $this, 'maybeBootListTableOrdering' ] );
	}

	public function enqueueAdminAssets( string $hook ): void {
		unset( $hook );
		$settings = $this->getSettings();

		if ( ! empty( $settings['enable_list_table_dnd'] ) && $this->isListTableReorderContext() ) {
			$this->enqueueListTableAssets();
		}
	}

	private function enqueueListTableAssets(): void {
		ReorderAssets::enqueue();
		wp_enqueue_script(
			'wp-powersuite-custom-post-order-lt',
			$this->getAssetUrl( 'list-table-sortable.js' ),
			[ ReorderAssets::SCRIPT_HANDLE ],
			self::MODULE_VERSION,
			true
		);
		wp_localize_script( 'wp-powersuite-custom-post-order-lt', 'wppsCustomPostOrder', $this->getScriptConfig( true ) );
	}

	/** @return array<string, mixed> */
	private function getScriptConfig( bool $list_table = false ): array {
		global $typenow;

		return [
			'ajaxUrl'    => admin_url( 'admin-ajax.php' ),
			'nonce'      => wp_create_nonce( self::NONCE_ACTION ),
			'listTable'  => $list_table,
			'objectType' => $list_table && is_string( $typenow ) ? sanitize_key( $typenow ) : '',
			'settings'   => [
				'chunkSize' => 50,
			],
			'strings'    => ReorderAssets::sharedStrings(),
		];
	}

	public function maybeBootListTableOrdering(): void {
		$settings = $this->getSettings();
		if ( empty( $settings['enable_list_table_dnd'] ) ) {
			return;
		}

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

		add_action( 'admin_notices', [ $this, 'renderListTableReorderNotice' ] );
	}

	public function renderListTableReorderNotice(): void {
		echo '<div class="notice notice-info"><p>';
		esc_html_e( 'Drag the handle at the start of each row to reorder. Changes save automatically when you drop a row.', 'wp-powersuite' );
		echo ' ';
		printf(
			'<a href="%1$s">%2$s</a>',
			esc_url( add_query_arg( [ 'orderby' => 'menu_order', 'order' => 'asc' ] ) ),
			esc_html__( 'Sort by menu order', 'wp-powersuite' )
		);
		echo '</p></div>';
	}

	private function isListTableReorderContext(): bool {
		global $pagenow, $typenow;

		if ( $pagenow !== 'edit.php' || ! is_string( $typenow ) || $typenow === '' ) {
			return false;
		}

		$settings = $this->getSettings();
		if ( ! in_array( $typenow, $settings['post_types'], true ) ) {
			return false;
		}

		if ( ! PostTypeHelper::userCanReorderPostType( $typenow, $settings['reorder_capability'] ) ) {
			return false;
		}

		if ( isset( $_GET['s'] ) && is_scalar( $_GET['s'] ) && sanitize_text_field( wp_unslash( (string) $_GET['s'] ) ) !== '' ) {
			return false;
		}

		$orderby = isset( $_GET['orderby'] ) ? sanitize_key( wp_unslash( (string) $_GET['orderby'] ) ) : '';
		if ( $orderby !== '' && $orderby !== 'menu_order' ) {
			return false;
		}

		return true;
	}

	public function ajaxSaveOrder(): void {
		$this->verifyAjaxRequest();

		$post_type = isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( (string) $_POST['post_type'] ) ) : '';
		$settings  = $this->getSettings();

		if ( ! in_array( $post_type, $settings['post_types'], true ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid post type.', 'wp-powersuite' ) ] );
		}

		$order = [];
		if ( isset( $_POST['order'] ) && is_array( $_POST['order'] ) ) {
			$order = array_map( 'intval', wp_unslash( $_POST['order'] ) );
		}

		$parent_id    = isset( $_POST['parent_id'] ) ? max( 0, (int) $_POST['parent_id'] ) : 0;
		$order_offset = isset( $_POST['order_offset'] ) ? max( 0, (int) $_POST['order_offset'] ) : 0;
		$is_chunk     = ! empty( $_POST['is_chunk'] );
		$scope        = isset( $_POST['scope'] ) ? sanitize_key( wp_unslash( (string) $_POST['scope'] ) ) : '';

		// List table drag-and-drop presents a single flattened, mixed-parent list,
		// so ordering must not be scoped to one parent and partial validation is fine.
		$is_list_table = $scope === 'list_table';
		$mode          = $is_list_table ? 'global' : 'within_parent';
		$is_chunk      = $is_chunk || $is_list_table;

		$result = OrderSaver::saveOrder(
			$post_type,
			$order,
			$settings['reorder_capability'],
			$parent_id,
			$mode,
			$order_offset,
			$is_chunk
		);

		if ( ! $result['success'] ) {
			wp_send_json_error( [ 'message' => $result['message'] ] );
		}

		wp_send_json_success( [ 'message' => $result['message'], 'updated' => $result['updated'] ] );
	}

	public function ajaxResetOrder(): void {
		$this->verifyAjaxRequest();

		$post_type = isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( (string) $_POST['post_type'] ) ) : '';
		$settings  = $this->getSettings();

		if ( ! in_array( $post_type, $settings['post_types'], true ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid post type.', 'wp-powersuite' ) ] );
		}

		$parent_id = isset( $_POST['parent_id'] ) ? max( 0, (int) $_POST['parent_id'] ) : 0;

		// A full reset must clear order for every hierarchy level, not just the
		// currently scoped parent.
		$reset_all = ! empty( $_POST['reset_all'] );
		$mode      = $reset_all ? 'global' : 'within_parent';

		$result = OrderSaver::resetOrder(
			$post_type,
			$settings['reorder_capability'],
			$parent_id,
			$mode
		);

		if ( ! $result['success'] ) {
			wp_send_json_error( [ 'message' => $result['message'] ] );
		}

		wp_send_json_success( [ 'message' => $result['message'], 'updated' => $result['updated'] ] );
	}

	private function verifyAjaxRequest(): void {
		if ( ! check_ajax_referer( self::NONCE_ACTION, 'nonce', false ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid security token.', 'wp-powersuite' ) ] );
		}

		if ( ! $this->userCanAccessReorderUi() ) {
			wp_send_json_error( [ 'message' => __( 'Permission denied.', 'wp-powersuite' ) ] );
		}
	}

	private function userCanAccessReorderUi(): bool {
		$settings = $this->getSettings();
		$cap      = PostTypeHelper::sanitizeCapabilityKey( $settings['reorder_capability'] );

		return current_user_can( $cap );
	}

	/** @return array<string, mixed> */
	public static function getDefaultSettings(): array {
		return [
			'post_types'             => [ 'post', 'page' ],
			'enable_list_table_dnd'  => true,
			'apply_frontend_main'    => true,
			'apply_frontend_archive' => false,
			'apply_frontend_search'  => false,
			'apply_admin'            => false,
			'respect_existing_order' => true,
			'reorder_capability'     => 'edit_others_posts',
		];
	}

	/**
	 * @param array<string, mixed> $raw
	 * @return array<string, mixed>
	 */
	public static function normalizeSettings( array $raw ): array {
		$settings = wp_parse_args( $raw, self::getDefaultSettings() );

		$settings['post_types'] = PostTypeHelper::sanitizeSelectedPostTypes(
			is_array( $settings['post_types'] ?? null ) ? $settings['post_types'] : []
		);

		$settings['enable_list_table_dnd']  = ! empty( $settings['enable_list_table_dnd'] );
		$settings['apply_frontend_main']    = ! empty( $settings['apply_frontend_main'] );
		$settings['apply_frontend_archive'] = ! empty( $settings['apply_frontend_archive'] );
		$settings['apply_frontend_search']  = ! empty( $settings['apply_frontend_search'] );
		$settings['apply_admin']            = ! empty( $settings['apply_admin'] );
		$settings['respect_existing_order'] = ! empty( $settings['respect_existing_order'] );

		$settings['reorder_capability'] = PostTypeHelper::sanitizeCapabilityKey( (string) ( $settings['reorder_capability'] ?? 'edit_others_posts' ) );

		return $settings;
	}

	/** @return array<string, mixed> */
	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;
	}

	private function getAssetUrl( string $file ): string {
		$base = defined( 'WP_POWERSUITE_PLUGIN_URL' ) ? WP_POWERSUITE_PLUGIN_URL : '';
		return $base . 'modules/' . self::MODULE_ID . '/assets/' . $file;
	}
}
