<?php
/**
 * KooKoa Folders — Folder CRUD AJAX handlers.
 *
 * Structural operations on a folder: create, rename, delete, move (with
 * sibling reordering) and duplicate. Registered and dispatched by
 * KookoaWPF_Ajax.
 *
 * @package KookoaWPF
 * @license GPL-2.0-or-later
 */

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

/**
 * AJAX handlers for folder create / rename / delete / move / duplicate.
 */
class KookoaWPF_Ajax_Folders extends KookoaWPF_Ajax_Handler {

	/**
	 * Singleton instance.
	 *
	 * @var self|null
	 */
	private static $instance = null;

	/**
	 * Return the singleton instance.
	 *
	 * @return self
	 */
	public static function instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor. The action map lives on the KookoaWPF_Ajax dispatcher, so
	 * this collaborator registers nothing of its own.
	 */
	private function __construct() {
		// Registration is owned by KookoaWPF_Ajax (the dispatch surface).
	}

	/**
	 * Create folder
	 */
	public function create_folder(): void {
		check_ajax_referer( 'kookoawpf_nonce', 'nonce' );
		$this->verify_request();

		$name      = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
		$post_type = self::require_managed_post_type(
			isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( $_POST['post_type'] ) ) : ''
		);
		$parent_id = isset( $_POST['parent_id'] ) ? absint( wp_unslash( $_POST['parent_id'] ) ) : 0;

		if ( empty( $name ) || empty( $post_type ) ) {
			wp_send_json_error( array( 'message' => __( 'Missing required fields.', 'kookoa-folders' ) ) );
		}

		// Verify parent (if any) exists in the same taxonomy.
		if ( $parent_id > 0 && ! self::folder_in_taxonomy( $parent_id, $post_type ) ) {
			wp_send_json_error( array( 'message' => __( 'Parent folder not found.', 'kookoa-folders' ) ) );
		}

		// Cannot create subfolder inside a locked folder.
		if ( $parent_id > 0 && KookoaWPF_Folders::get_meta( $parent_id, 'folder_locked' ) ) {
			wp_send_json_error( array( 'message' => __( 'Locked folders cannot contain subfolders.', 'kookoa-folders' ) ) );
		}

		$result = KookoaWPF_Folders::create( $name, $post_type, $parent_id );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( array( 'message' => $result->get_error_message() ) );
		}

		wp_send_json_success( $result );
	}

	/**
	 * Rename folder
	 */
	public function rename_folder(): void {
		check_ajax_referer( 'kookoawpf_nonce', 'nonce' );
		$this->verify_request();

		$folder_id = isset( $_POST['folder_id'] ) ? absint( wp_unslash( $_POST['folder_id'] ) ) : 0;
		$new_name  = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
		$post_type = self::require_managed_post_type(
			isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( $_POST['post_type'] ) ) : ''
		);

		if ( ! $folder_id || empty( $new_name ) || empty( $post_type ) ) {
			wp_send_json_error( array( 'message' => __( 'Missing required fields.', 'kookoa-folders' ) ) );
		}

		if ( KookoaWPF_Folders::get_meta( $folder_id, 'folder_locked' ) ) {
			wp_send_json_error( array( 'message' => __( 'This folder is locked.', 'kookoa-folders' ) ) );
		}

		$result = KookoaWPF_Folders::rename( $folder_id, $new_name, $post_type );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( array( 'message' => $result->get_error_message() ) );
		}

		wp_send_json_success( array( 'message' => __( 'Folder renamed.', 'kookoa-folders' ) ) );
	}

	/**
	 * Delete folder
	 */
	public function delete_folder(): void {
		check_ajax_referer( 'kookoawpf_nonce', 'nonce' );
		$this->verify_request();

		$folder_id = isset( $_POST['folder_id'] ) ? absint( wp_unslash( $_POST['folder_id'] ) ) : 0;
		$post_type = self::require_managed_post_type(
			isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( $_POST['post_type'] ) ) : ''
		);

		if ( ! $folder_id || empty( $post_type ) ) {
			wp_send_json_error( array( 'message' => __( 'Missing required fields.', 'kookoa-folders' ) ) );
		}

		// Check if locked.
		$locked = KookoaWPF_Folders::get_meta( $folder_id, 'folder_locked' );
		if ( $locked ) {
			wp_send_json_error( array( 'message' => __( 'This folder is locked and cannot be deleted.', 'kookoa-folders' ) ) );
		}

		// Check if any descendant is locked.
		$taxonomy = KookoaWPF_Folders::get_taxonomy( $post_type );
		if ( KookoaWPF_Folders::has_locked_descendant( $folder_id, $taxonomy ) ) {
			wp_send_json_error( array( 'message' => __( 'Cannot delete: a subfolder is locked.', 'kookoa-folders' ) ) );
		}

		$result = KookoaWPF_Folders::delete( $folder_id, $post_type );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( array( 'message' => $result->get_error_message() ) );
		}

		wp_send_json_success( array( 'message' => __( 'Folder deleted.', 'kookoa-folders' ) ) );
	}

	/**
	 * Move folder to new parent
	 */
	public function move_folder(): void {
		check_ajax_referer( 'kookoawpf_nonce', 'nonce' );
		$this->verify_request();

		$folder_id     = isset( $_POST['folder_id'] ) ? absint( wp_unslash( $_POST['folder_id'] ) ) : 0;
		$new_parent_id = isset( $_POST['new_parent_id'] ) ? absint( wp_unslash( $_POST['new_parent_id'] ) ) : 0;
		$post_type     = self::require_managed_post_type(
			isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( $_POST['post_type'] ) ) : ''
		);
		$sibling_order = isset( $_POST['sibling_order'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['sibling_order'] ) ) : array();

		if ( ! $folder_id || empty( $post_type ) ) {
			wp_send_json_error( array( 'message' => __( 'Missing required fields.', 'kookoa-folders' ) ) );
		}

		// Verify the folder exists in the correct taxonomy.
		$taxonomy = KookoaWPF_Folders::get_taxonomy( $post_type );
		$term     = get_term( $folder_id, $taxonomy );

		if ( ! $term || is_wp_error( $term ) ) {
			wp_send_json_error(
				array(
					'message' => __( 'Folder not found.', 'kookoa-folders' ),
				)
			);
		}

		// Locked folder: can reorder among siblings but cannot change parent.
		if ( KookoaWPF_Folders::get_meta( $folder_id, 'folder_locked' ) ) {
			if ( (int) $term->parent !== (int) $new_parent_id ) {
				wp_send_json_error( array( 'message' => __( 'This folder is locked and cannot be moved to another parent.', 'kookoa-folders' ) ) );
			}
		}

		// Locked destination: cannot receive subfolders (items only).
		if ( $new_parent_id > 0 && KookoaWPF_Folders::get_meta( $new_parent_id, 'folder_locked' ) ) {
			wp_send_json_error( array( 'message' => __( 'Locked folders cannot contain subfolders.', 'kookoa-folders' ) ) );
		}

		// If new_parent_id > 0, verify the parent also exists.
		if ( $new_parent_id > 0 ) {
			$parent_term = get_term( $new_parent_id, $taxonomy );
			if ( ! $parent_term || is_wp_error( $parent_term ) ) {
				wp_send_json_error(
					array(
						'message' => __( 'Parent folder not found.', 'kookoa-folders' ),
					)
				);
			}
		}

		$result = KookoaWPF_Folders::move( $folder_id, $new_parent_id, $post_type );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( array( 'message' => $result->get_error_message() ) );
		}

		// Apply sibling order — enforce pinned folders always first.
		if ( ! empty( $sibling_order ) ) {
			// Scope the incoming IDs to folders that actually live in THIS
			// taxonomy before writing any termmeta. Without this guard a
			// crafted sibling_order array could write folder_order meta onto
			// arbitrary term IDs (categories, tags, other post types' folders).
			$sibling_order = array_values(
				array_filter(
					$sibling_order,
					static function ( $sid ) use ( $taxonomy ) {
						return (bool) term_exists( (int) $sid, $taxonomy );
					}
				)
			);
		}
		if ( ! empty( $sibling_order ) ) {
			update_termmeta_cache( $sibling_order );
			$pinned_ids  = array();
			$regular_ids = array();
			foreach ( $sibling_order as $sid ) {
				if ( KookoaWPF_Folders::get_meta( $sid, 'folder_pinned' ) ) {
					$pinned_ids[] = $sid;
				} else {
					$regular_ids[] = $sid;
				}
			}
			$sibling_order = array_merge( $pinned_ids, $regular_ids );

			foreach ( $sibling_order as $index => $sid ) {
				KookoaWPF_Folders::update_meta( $sid, 'folder_order', $index );
			}

			/*
			 * Note: this branch used to reset sort_pref to 'custom' here so
			 * that drag operations had a visible effect even when an auto sort
			 * was active in the library. That trick is no longer needed: the
			 * client-side _resortChildren helper (admin.js) now mirrors the
			 * server's auto-sort logic, so a drag in auto sort lands in the
			 * correct sorted spot immediately. Resetting sort_pref here would
			 * also clobber the user's preference whenever a media modal
			 * operation moves a folder, which is a bug we explicitly want to
			 * avoid. The user changes sort only via the explicit Sort menu.
			 */
		} else {
			// No sibling_order provided (e.g. Move to Folder dialog) —
			// re-sort new parent's children: pinned first, then non-pinned,
			// with moved folder appended at end of its group.
			$new_siblings = KookoaWPF_Folders::get_children_ordered( $new_parent_id, $taxonomy );
			if ( ! empty( $new_siblings ) ) {
				// Prime termmeta cache to avoid N+1 queries.
				update_termmeta_cache( wp_list_pluck( $new_siblings, 'term_id' ) );
				$pinned_ids  = array();
				$regular_ids = array();
				foreach ( $new_siblings as $sib ) {
					if ( KookoaWPF_Folders::get_meta( $sib->term_id, 'folder_pinned' ) ) {
						$pinned_ids[] = $sib->term_id;
					} else {
						$regular_ids[] = $sib->term_id;
					}
				}
				$ordered = array_merge( $pinned_ids, $regular_ids );
				foreach ( $ordered as $index => $tid ) {
					KookoaWPF_Folders::update_meta( $tid, 'folder_order', $index );
				}
			}
		}

		wp_send_json_success( array( 'message' => __( 'Folder moved.', 'kookoa-folders' ) ) );
	}

	/**
	 * Duplicate folder (name, color, icon and the whole subtree).
	 */
	public function duplicate_folder(): void {
		check_ajax_referer( 'kookoawpf_nonce', 'nonce' );
		$this->verify_request();

		$folder_id     = isset( $_POST['folder_id'] ) ? absint( wp_unslash( $_POST['folder_id'] ) ) : 0;
		$post_type     = self::require_managed_post_type(
			isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( $_POST['post_type'] ) ) : ''
		);
		$target_parent = isset( $_POST['target_parent'] ) ? absint( wp_unslash( $_POST['target_parent'] ) ) : null;

		if ( ! $folder_id || empty( $post_type ) ) {
			wp_send_json_error( array( 'message' => __( 'Missing data.', 'kookoa-folders' ) ) );
		}

		// Source folder must live in the taxonomy of the given post type.
		if ( ! self::folder_in_taxonomy( $folder_id, $post_type ) ) {
			wp_send_json_error( array( 'message' => __( 'Folder not found.', 'kookoa-folders' ) ) );
		}

		// If a target parent is specified, validate it and enforce lock rules.
		if ( $target_parent > 0 ) {
			if ( ! self::folder_in_taxonomy( $target_parent, $post_type ) ) {
				wp_send_json_error( array( 'message' => __( 'Parent folder not found.', 'kookoa-folders' ) ) );
			}
			if ( KookoaWPF_Folders::get_meta( $target_parent, 'folder_locked' ) ) {
				wp_send_json_error( array( 'message' => __( 'Locked folders cannot contain subfolders.', 'kookoa-folders' ) ) );
			}
		}

		$result = KookoaWPF_Folders::duplicate( $folder_id, $post_type, $target_parent );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( array( 'message' => $result->get_error_message() ) );
		}

		wp_send_json_success( $result );
	}
}
