<?php

namespace CustomTaxonomyOrder;

use CustomTaxonomyOrder\Includes\OrderSaver;
use CustomTaxonomyOrder\Includes\QueryModifier;
use CustomTaxonomyOrder\Includes\TaxonomyHelper;
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-taxonomy-helper.php';

class Module extends AbstractModule implements DataLocationsInterface {

	private const MODULE_ID      = 'custom-taxonomy-order';
	private const MODULE_VERSION = '2.3.0';
	private const NONCE_ACTION   = 'wpps_custom_taxonomy_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 Taxonomy Order', 'wp-powersuite' );
	}

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

	public function getDescription(): string {
		return __( 'Drag and drop to reorder categories, tags, and custom taxonomy terms, then apply that order in widgets, menus, archives, and supported queries.', 'wp-powersuite' );
	}

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

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

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

		if ( wp_doing_ajax() ) {
			add_action( 'wp_ajax_wpps_cto_save_order', [ $this, 'ajaxSaveOrder' ] );
			add_action( 'wp_ajax_wpps_cto_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'] )
			&& empty( $settings['apply_archives'] )
			&& empty( $settings['apply_widgets'] )
			&& empty( $settings['apply_menus'] )
			&& 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-tags.php', [ $this, 'maybeBootListTableIntegration' ] );
	}

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

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

		if ( ! empty( $settings['enable_list_table_dnd'] ) ) {
			$this->enqueueListTableAssets();
		}
		if ( ! empty( $settings['enable_list_table_column'] ) ) {
			ReorderAssets::enqueue();
		}
	}

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

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

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

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

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

		global $taxonomy;
		$tax = is_string( $taxonomy ) ? $taxonomy : '';

		if ( ! empty( $settings['enable_list_table_column'] ) && $tax !== '' ) {
			add_filter( "manage_edit-{$tax}_columns", [ $this, 'addOrderColumn' ] );
			add_filter( "manage_{$tax}_custom_column", [ $this, 'renderOrderColumn' ], 10, 3 );
		}

		if ( ! empty( $settings['enable_list_table_dnd'] ) && $this->isListTableReorderContext() ) {
			add_action( 'admin_notices', [ $this, 'renderListTableReorderNotice' ] );
		}
	}

	/** @param array<string, string> $columns */
	public function addOrderColumn( array $columns ): array {
		$columns['wpps_term_order'] = __( 'Order', 'wp-powersuite' );
		return $columns;
	}

	public function renderOrderColumn( string $content, string $column, int $term_id ): string {
		if ( $column !== 'wpps_term_order' ) {
			return $content;
		}

		$order = get_term_meta( $term_id, TaxonomyHelper::getOrderMetaKey(), true );
		return esc_html( $order !== '' && $order !== false ? (string) $order : '—' );
	}

	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 terms. Changes save automatically when you drop a row.', 'wp-powersuite' );
		echo '</p></div>';
	}

	private function isListTableContext(): bool {
		global $pagenow, $taxonomy;

		return $pagenow === 'edit-tags.php' && is_string( $taxonomy ) && $taxonomy !== '';
	}

	private function isListTableReorderContext(): bool {
		if ( ! $this->isListTableContext() ) {
			return false;
		}

		global $taxonomy;
		$settings = $this->getSettings();

		if ( ! in_array( $taxonomy, $settings['taxonomies'], true ) ) {
			return false;
		}

		if ( ! TaxonomyHelper::userCanReorderTaxonomy( $taxonomy, $settings['reorder_capability'] ) ) {
			return false;
		}

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

		return true;
	}

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

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

		if ( ! in_array( $taxonomy, $settings['taxonomies'], true ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid taxonomy.', '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(
			$taxonomy,
			$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();

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

		if ( ! in_array( $taxonomy, $settings['taxonomies'], true ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid taxonomy.', '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(
			$taxonomy,
			$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      = TaxonomyHelper::sanitizeCapabilityKey( $settings['reorder_capability'] );

		return current_user_can( $cap );
	}

	/** @return array<string, mixed> */
	public static function getDefaultSettings(): array {
		$taxonomies = [ 'category' ];
		if ( taxonomy_exists( 'post_tag' ) ) {
			$taxonomies[] = 'post_tag';
		}

		return [
			'taxonomies'               => $taxonomies,
			'enable_list_table_dnd'    => true,
			'enable_list_table_column' => true,
			'apply_frontend'           => true,
			'apply_archives'           => false,
			'apply_widgets'            => true,
			'apply_menus'              => false,
			'apply_admin'              => false,
			'respect_existing_order'   => true,
			'reorder_capability'       => 'manage_categories',
		];
	}

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

		$settings['taxonomies'] = TaxonomyHelper::sanitizeSelectedTaxonomies(
			is_array( $settings['taxonomies'] ?? null ) ? $settings['taxonomies'] : []
		);

		$settings['enable_list_table_dnd']    = ! empty( $settings['enable_list_table_dnd'] );
		$settings['enable_list_table_column'] = ! empty( $settings['enable_list_table_column'] );
		$settings['apply_frontend']           = ! empty( $settings['apply_frontend'] );
		$settings['apply_archives']           = ! empty( $settings['apply_archives'] );
		$settings['apply_widgets']            = ! empty( $settings['apply_widgets'] );
		$settings['apply_menus']              = ! empty( $settings['apply_menus'] );
		$settings['apply_admin']              = ! empty( $settings['apply_admin'] );
		$settings['respect_existing_order']   = ! empty( $settings['respect_existing_order'] );

		$settings['reorder_capability'] = TaxonomyHelper::sanitizeCapabilityKey( (string) ( $settings['reorder_capability'] ?? 'manage_categories' ) );

		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;
	}
}
