<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Handles form actions and downloads.
 */
class NNAXFUF_Handlers {
    const NOTICE_TRANSIENT_PREFIX = 'nnaxfuf_frontend_notices_';

    /**
     * Admin/frontend notices collected during request.
     * @var array
     */
    private $notices = array();
    private function base64url_decode( $value ) {
        $value = (string) $value;
        if ( $value === '' ) {
            return '';
        }

        $value = strtr( $value, '-_', '+/' );
        $pad   = strlen( $value ) % 4;
        if ( $pad > 0 ) {
            $value .= str_repeat( '=', 4 - $pad );
        }

        $decoded = base64_decode( $value, true );
        return $decoded === false ? '' : $decoded;
    }

    public function __construct() {
        $post_actions = array(
            'nnaxfuf_upload'                  => array( $this, 'handle_upload' ),
            'nnaxfuf_share_file'              => array( $this, 'handle_share_file' ),
            'nnaxfuf_delete_file'             => array( $this, 'handle_delete_file' ),
            'nnaxfuf_move_file'               => array( $this, 'handle_move_file' ),
            'nnaxfuf_create_folder'           => array( $this, 'handle_create_folder' ),
            'nnaxfuf_delete_folder'           => array( $this, 'handle_delete_folder' ),
            'nnaxfuf_delete_folder_recursive' => array( $this, 'handle_delete_folder_recursive' ),
            'nnaxfuf_move_folder'             => array( $this, 'handle_move_folder' ),
            'nnaxfuf_share_folder'            => array( $this, 'handle_share_folder' ),
            'nnaxfuf_sync_folder_share'       => array( $this, 'handle_sync_folder_share' ),
            'nnaxfuf_restore_item'            => array( $this, 'handle_restore_item' ),
            'nnaxfuf_delete_item_permanently' => array( $this, 'handle_delete_item_permanently' ),
            'nnaxfuf_bulk_delete'             => array( $this, 'handle_bulk_delete' ),
            'nnaxfuf_bulk_move'               => array( $this, 'handle_bulk_move' ),
            'nnaxfuf_bulk_delete_recursive'   => array( $this, 'handle_bulk_delete_recursive' ),
            'nnaxfuf_empty_trash'             => array( $this, 'handle_empty_trash' ),
        );

        foreach ( $post_actions as $action => $callback ) {
            add_action( 'admin_post_' . $action, $callback );
        }

        add_action( 'template_redirect', array( $this, 'handle_download' ) );
        add_action( 'admin_post_nnaxfuf_zip_download', array( $this, 'handle_zip_download_admin' ) );
    }

    public function handle_zip_download_admin() {
        $this->handle_zip_download();
    }

    private function add_notice( $status, $message ) {
        $allowed = array( 'success', 'warning', 'danger', 'primary' );
        $status  = in_array( $status, $allowed, true ) ? $status : 'primary';

        $this->notices[] = array(
            'status'  => $status,
            'message' => (string) $message,
        );
    }

    private function normalize_extension_list( $raw_list ) {
        $items = explode( ',', (string) $raw_list );
        $items = array_map( 'trim', $items );
        $items = array_filter( $items );
        $items = array_map(
            function ( $ext ) {
                $ext = ltrim( strtolower( (string) $ext ), '.' );
                return preg_replace( '/[^a-z0-9]/', '', $ext );
            },
            $items
        );

        return array_values( array_unique( array_filter( $items ) ) );
    }

    private function get_upload_error_message( $error_code ) {
        $error_code = (int) $error_code;

        switch ( $error_code ) {
            case UPLOAD_ERR_INI_SIZE:
            case UPLOAD_ERR_FORM_SIZE:
                return __( 'File is too large.', 'nnax-frontend-user-files' );
            case UPLOAD_ERR_PARTIAL:
                return __( 'File was only partially uploaded.', 'nnax-frontend-user-files' );
            case UPLOAD_ERR_NO_FILE:
                return __( 'No file was uploaded.', 'nnax-frontend-user-files' );
            case UPLOAD_ERR_NO_TMP_DIR:
                return __( 'Missing temporary upload directory.', 'nnax-frontend-user-files' );
            case UPLOAD_ERR_CANT_WRITE:
                return __( 'Failed to write file to disk.', 'nnax-frontend-user-files' );
            case UPLOAD_ERR_EXTENSION:
                return __( 'A server extension stopped the upload.', 'nnax-frontend-user-files' );
            default:
                return __( 'Upload failed due to an unknown error.', 'nnax-frontend-user-files' );
        }
    }

    private function get_uploaded_file_strings( $field, $key, $sanitize_callback ) {
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Called only after the upload nonce is verified; values are sanitized below.
        if ( ! isset( $_FILES[ $field ][ $key ] ) || ! is_array( $_FILES[ $field ][ $key ] ) ) {
            return array();
        }

        return array_map(
            static function ( $value ) use ( $sanitize_callback ) {
                return call_user_func( $sanitize_callback, (string) $value );
            },
            wp_unslash( $_FILES[ $field ][ $key ] )
        );
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    }

    private function get_uploaded_file_ints( $field, $key ) {
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Called only after the upload nonce is verified; values are normalized below.
        if ( ! isset( $_FILES[ $field ][ $key ] ) || ! is_array( $_FILES[ $field ][ $key ] ) ) {
            return array();
        }

        return array_map( 'absint', wp_unslash( $_FILES[ $field ][ $key ] ) );
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotValidated
    }

    private function normalize_uploaded_files() {
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Called only after the upload nonce is verified; values are sanitized before use.
        if ( isset( $_FILES['nnaxfuf_files']['name'] ) && is_array( $_FILES['nnaxfuf_files']['name'] ) ) {
            return array(
                'name'     => $this->get_uploaded_file_strings( 'nnaxfuf_files', 'name', 'sanitize_file_name' ),
                'type'     => $this->get_uploaded_file_strings( 'nnaxfuf_files', 'type', 'sanitize_mime_type' ),
                'tmp_name' => $this->get_uploaded_file_strings( 'nnaxfuf_files', 'tmp_name', 'sanitize_text_field' ),
                'error'    => $this->get_uploaded_file_ints( 'nnaxfuf_files', 'error' ),
                'size'     => $this->get_uploaded_file_ints( 'nnaxfuf_files', 'size' ),
            );
        }

        if ( empty( $_FILES['nnaxfuf_file']['name'] ) ) {
            return array();
        }
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotValidated

        return array(
            // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Called only after the upload nonce is verified; scalar upload values are sanitized here.
            'name'     => array( sanitize_file_name( wp_unslash( (string) $_FILES['nnaxfuf_file']['name'] ) ) ),
            'type'     => array( isset( $_FILES['nnaxfuf_file']['type'] ) ? sanitize_mime_type( wp_unslash( (string) $_FILES['nnaxfuf_file']['type'] ) ) : '' ),
            'tmp_name' => array( isset( $_FILES['nnaxfuf_file']['tmp_name'] ) ? sanitize_text_field( wp_unslash( (string) $_FILES['nnaxfuf_file']['tmp_name'] ) ) : '' ),
            'error'    => array( isset( $_FILES['nnaxfuf_file']['error'] ) ? absint( $_FILES['nnaxfuf_file']['error'] ) : UPLOAD_ERR_NO_FILE ),
            'size'     => array( isset( $_FILES['nnaxfuf_file']['size'] ) ? absint( $_FILES['nnaxfuf_file']['size'] ) : 0 ),
            // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotValidated
        );
    }

    private function add_share_update_notice( $subject_label, $old_ids, $new_ids, $rules_changed = false ) {
        $old_ids = is_array( $old_ids ) ? array_map( array( $this, 'sanitize_share_id' ), $old_ids ) : array();
        $new_ids = is_array( $new_ids ) ? array_map( array( $this, 'sanitize_share_id' ), $new_ids ) : array();

        $added   = array_values( array_diff( $new_ids, $old_ids ) );
        $removed = array_values( array_diff( $old_ids, $new_ids ) );

        $added_count   = count( $added );
        $removed_count = count( $removed );

        if ( $added_count === 0 && $removed_count === 0 ) {
            if ( $rules_changed ) {
                $this->add_notice( 'success', $subject_label . ': ' . __( 'permissions updated.', 'nnax-frontend-user-files' ) );
            } else {
                $this->add_notice( 'success', $subject_label . ': ' . __( 'no changes to shares.', 'nnax-frontend-user-files' ) );
            }
            return;
        }

        $parts = array();
        if ( $added_count > 0 ) {
            /* translators: %d: number of users/roles added to sharing. */
            $parts[] = sprintf( __( '%d added', 'nnax-frontend-user-files' ), $added_count );
        }
        if ( $removed_count > 0 ) {
            /* translators: %d: number of users/roles removed from sharing. */
            $parts[] = sprintf( __( '%d removed', 'nnax-frontend-user-files' ), $removed_count );
        }

        $notice_text = implode( ', ', $parts ) . '.';
        if ( $rules_changed ) {
            $notice_text .= ' ' . __( 'Permissions updated.', 'nnax-frontend-user-files' );
        }

        $this->add_notice( 'success', $subject_label . ': ' . $notice_text );
    }

    private function sanitize_notice_key( $value ) {
        $value = strtolower( (string) $value );
        return preg_replace( '/[^a-z0-9-]/', '', $value );
    }

    private function get_notice_key_from_request() {
        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Read-only flash notice key retrieval for redirects. Input is sanitized via sanitize_notice_key().
        if ( isset( $_POST['nnaxfuf_notice_key'] ) ) {
            return $this->sanitize_notice_key( wp_unslash( (string) $_POST['nnaxfuf_notice_key'] ) );
        }
        if ( isset( $_GET['nnaxfuf_notice_key'] ) ) {
            return $this->sanitize_notice_key( wp_unslash( (string) $_GET['nnaxfuf_notice_key'] ) );
        }
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

        return '';
    }

    public function get_frontend_notice_key() {
        $notice_key = $this->get_notice_key_from_request();
        if ( $notice_key !== '' ) {
            return $notice_key;
        }

        return $this->sanitize_notice_key( wp_generate_uuid4() );
    }

    private function pop_flash_notices( $notice_key ) {
        $notice_key = $this->sanitize_notice_key( $notice_key );
        if ( $notice_key === '' ) {
            return array();
        }

        $transient_key = self::NOTICE_TRANSIENT_PREFIX . $notice_key;
        $notices       = get_transient( $transient_key );
        delete_transient( $transient_key );

        return is_array( $notices ) ? $notices : array();
    }

    private function persist_flash_notices( $notice_key ) {
        $notice_key = $this->sanitize_notice_key( $notice_key );
        if ( $notice_key === '' || empty( $this->notices ) ) {
            return;
        }

        set_transient( self::NOTICE_TRANSIENT_PREFIX . $notice_key, $this->notices, 120 );
    }

    private function get_redirect_url_from_request( $default_url = '' ) {
        $redirect_url = '';

        // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Redirect target is validated before use.
        if ( isset( $_POST['nnaxfuf_redirect_to'] ) ) {
            $redirect_url = esc_url_raw( wp_unslash( (string) $_POST['nnaxfuf_redirect_to'] ) );
        } elseif ( isset( $_GET['nnaxfuf_redirect_to'] ) ) {
            $redirect_url = esc_url_raw( wp_unslash( (string) $_GET['nnaxfuf_redirect_to'] ) );
        }
        // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended

        if ( $redirect_url === '' ) {
            $redirect_url = (string) wp_get_referer();
        }

        if ( $redirect_url === '' ) {
            $redirect_url = (string) $default_url;
        }

        if ( $redirect_url === '' ) {
            $redirect_url = home_url( '/' );
        }

        return wp_validate_redirect( $redirect_url, home_url( '/' ) );
    }

    private function finish_action_request( $default_url = '' ) {
        $notice_key = $this->get_frontend_notice_key();
        $this->persist_flash_notices( $notice_key );

        $redirect_url = $this->get_redirect_url_from_request( $default_url );
        if ( $notice_key !== '' ) {
            $redirect_url = add_query_arg( 'nnaxfuf_notice_key', $notice_key, $redirect_url );
        }

        wp_safe_redirect( $redirect_url );
        exit;
    }

    private function redirect_to_login_and_exit() {
        wp_safe_redirect( wp_login_url( $this->get_redirect_url_from_request( home_url( '/' ) ) ) );
        exit;
    }

    public function get_notices() {
        $notices    = $this->notices;
        $notice_key = $this->get_notice_key_from_request();

        if ( $notice_key !== '' ) {
            $notices = array_merge( $notices, $this->pop_flash_notices( $notice_key ) );
        }

        return $notices;
    }

    public function handle_form_actions( $current_user_id = 0, $current_folder_id = 0 ) {
        return;
    }

    public function handle_upload() {
        if ( ! is_user_logged_in() ) {
            wp_safe_redirect( wp_login_url() );
            exit;
        }

        check_admin_referer( 'nnaxfuf_upload', 'nnaxfuf_nonce' );

        $current_folder_id = isset( $_POST['nnaxfuf_folder_id'] ) ? absint( wp_unslash( $_POST['nnaxfuf_folder_id'] ) ) : 0;
        $custom_title      = isset( $_POST['nnaxfuf_title'] ) ? sanitize_text_field( wp_unslash( $_POST['nnaxfuf_title'] ) ) : '';
        $current_user_id   = get_current_user_id();

        $this->process_upload( $current_user_id, $current_folder_id, $custom_title );
        $this->finish_action_request();
    }

    public function handle_share_file() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_share_file', 'nnaxfuf_nonce' );

        $raw_post          = wp_unslash( $_POST );
        $file_id           = isset( $raw_post['nnaxfuf_file_id'] ) ? absint( $raw_post['nnaxfuf_file_id'] ) : 0;
        $share_user_ids    = isset( $raw_post['nnaxfuf_share_user_ids'] ) && is_array( $raw_post['nnaxfuf_share_user_ids'] ) ? (array) $raw_post['nnaxfuf_share_user_ids'] : array();
        $share_user_configs = isset( $raw_post['nnaxfuf_share_user_configs'] ) && is_array( $raw_post['nnaxfuf_share_user_configs'] ) ? $raw_post['nnaxfuf_share_user_configs'] : array();
        $send_notification = ! empty( $raw_post['nnaxfuf_send_notification'] );
        $custom_msg        = isset( $raw_post['nnaxfuf_notification_msg'] ) ? wp_kses_post( $raw_post['nnaxfuf_notification_msg'] ) : '';

        $this->process_share_file( get_current_user_id(), $file_id, $share_user_ids, $share_user_configs, $send_notification, $custom_msg );
        $this->finish_action_request();
    }

    public function handle_delete_file() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_delete_file', 'nnaxfuf_nonce' );

        $file_id = isset( $_POST['nnaxfuf_file_id'] ) ? absint( wp_unslash( $_POST['nnaxfuf_file_id'] ) ) : 0;

        $this->process_delete_file( get_current_user_id(), $file_id );
        $this->finish_action_request();
    }

    public function handle_move_file() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_move_file', 'nnaxfuf_nonce' );

        $file_id          = isset( $_POST['nnaxfuf_file_id'] ) ? absint( wp_unslash( $_POST['nnaxfuf_file_id'] ) ) : 0;
        $target_parent_id = isset( $_POST['nnaxfuf_target_folder_id'] ) ? absint( wp_unslash( $_POST['nnaxfuf_target_folder_id'] ) ) : 0;

        $this->process_move_file( get_current_user_id(), $file_id, $target_parent_id );
        $this->finish_action_request();
    }

    public function handle_create_folder() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_create_folder', 'nnaxfuf_nonce' );

        $parent_folder_id = isset( $_POST['nnaxfuf_folder_id'] ) ? absint( wp_unslash( $_POST['nnaxfuf_folder_id'] ) ) : 0;
        $title            = isset( $_POST['nnaxfuf_folder_title'] ) ? sanitize_text_field( wp_unslash( $_POST['nnaxfuf_folder_title'] ) ) : '';

        $this->process_create_folder( get_current_user_id(), $parent_folder_id, $title );
        $this->finish_action_request();
    }

    public function handle_delete_folder() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_delete_folder', 'nnaxfuf_nonce' );

        $folder_id = isset( $_POST['nnaxfuf_folder_id'] ) ? absint( wp_unslash( $_POST['nnaxfuf_folder_id'] ) ) : 0;

        $this->process_delete_folder( get_current_user_id(), $folder_id );
        $this->finish_action_request();
    }

    public function handle_delete_folder_recursive() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_delete_folder_recursive', 'nnaxfuf_nonce' );

        $folder_id = isset( $_POST['nnaxfuf_folder_id'] ) ? absint( wp_unslash( $_POST['nnaxfuf_folder_id'] ) ) : 0;

        $this->process_delete_folder_recursive( get_current_user_id(), $folder_id );
        $this->finish_action_request();
    }

    public function handle_move_folder() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_move_folder', 'nnaxfuf_nonce' );

        $folder_id        = isset( $_POST['nnaxfuf_folder_id'] ) ? absint( wp_unslash( $_POST['nnaxfuf_folder_id'] ) ) : 0;
        $target_parent_id = isset( $_POST['nnaxfuf_target_folder_id'] ) ? absint( wp_unslash( $_POST['nnaxfuf_target_folder_id'] ) ) : 0;

        $this->process_move_folder( get_current_user_id(), $folder_id, $target_parent_id );
        $this->finish_action_request();
    }

    public function handle_share_folder() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_share_folder', 'nnaxfuf_nonce' );

        $raw_post           = wp_unslash( $_POST );
        $folder_id          = isset( $raw_post['nnaxfuf_folder_id'] ) ? absint( $raw_post['nnaxfuf_folder_id'] ) : 0;
        $share_user_ids     = isset( $raw_post['nnaxfuf_share_user_ids'] ) && is_array( $raw_post['nnaxfuf_share_user_ids'] ) ? (array) $raw_post['nnaxfuf_share_user_ids'] : array();
        $share_user_configs = isset( $raw_post['nnaxfuf_share_user_configs'] ) && is_array( $raw_post['nnaxfuf_share_user_configs'] ) ? $raw_post['nnaxfuf_share_user_configs'] : array();
        $send_notification  = ! empty( $raw_post['nnaxfuf_send_notification'] );
        $custom_msg         = isset( $raw_post['nnaxfuf_notification_msg'] ) ? wp_kses_post( $raw_post['nnaxfuf_notification_msg'] ) : '';

        $this->process_share_folder( get_current_user_id(), $folder_id, $share_user_ids, $share_user_configs, $send_notification, $custom_msg );
        $this->finish_action_request();
    }

    public function handle_sync_folder_share() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_sync_folder_share', 'nnaxfuf_nonce' );

        $folder_id = isset( $_POST['nnaxfuf_folder_id'] ) ? absint( wp_unslash( $_POST['nnaxfuf_folder_id'] ) ) : 0;

        $this->process_sync_folder_share( get_current_user_id(), $folder_id );
        $this->finish_action_request();
    }

    public function handle_restore_item() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_restore_item', 'nnaxfuf_nonce' );

        $item_id = isset( $_POST['nnaxfuf_item_id'] ) ? absint( wp_unslash( $_POST['nnaxfuf_item_id'] ) ) : 0;

        $this->process_restore_item( get_current_user_id(), $item_id );
        $this->finish_action_request();
    }

    public function handle_delete_item_permanently() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_delete_item_permanently', 'nnaxfuf_nonce' );

        $item_id = isset( $_POST['nnaxfuf_item_id'] ) ? absint( wp_unslash( $_POST['nnaxfuf_item_id'] ) ) : 0;

        $this->process_delete_item_permanently( get_current_user_id(), $item_id );
        $this->finish_action_request();
    }

    public function handle_bulk_delete() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_bulk_delete', 'nnaxfuf_nonce' );

        $items_raw = isset( $_POST['nnaxfuf_bulk_items'] ) ? sanitize_text_field( wp_unslash( $_POST['nnaxfuf_bulk_items'] ) ) : '';

        $this->process_bulk_delete( get_current_user_id(), $items_raw );
        $this->finish_action_request();
    }

    public function handle_bulk_move() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_bulk_move', 'nnaxfuf_nonce' );

        $items_raw        = isset( $_POST['nnaxfuf_bulk_items'] ) ? sanitize_text_field( wp_unslash( $_POST['nnaxfuf_bulk_items'] ) ) : '';
        $target_parent_id = isset( $_POST['nnaxfuf_target_folder_id'] ) ? absint( wp_unslash( $_POST['nnaxfuf_target_folder_id'] ) ) : 0;

        $this->process_bulk_move( get_current_user_id(), $items_raw, $target_parent_id );
        $this->finish_action_request();
    }

    public function handle_bulk_delete_recursive() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_bulk_delete_recursive', 'nnaxfuf_nonce' );

        $items_raw = isset( $_POST['nnaxfuf_bulk_items'] ) ? sanitize_text_field( wp_unslash( $_POST['nnaxfuf_bulk_items'] ) ) : '';

        $this->process_bulk_delete_recursive( get_current_user_id(), $items_raw );
        $this->finish_action_request();
    }

    public function handle_empty_trash() {
        if ( ! is_user_logged_in() ) {
            $this->redirect_to_login_and_exit();
        }

        check_admin_referer( 'nnaxfuf_empty_trash', 'nnaxfuf_nonce' );

        $this->process_empty_trash( get_current_user_id() );
        $this->finish_action_request();
    }

    private function process_upload( $current_user_id, $current_folder_id = 0, $custom_title = '' ) {
        // phpcs:disable WordPress.Security.NonceVerification.Missing
        $files = $this->normalize_uploaded_files();
        if ( empty( $files['name'] ) ) {
            $this->add_notice( 'warning', __( 'No file selected for upload.', 'nnax-frontend-user-files' ) );
            return;
        }
    
        $upload_names = array();
        foreach ( $files['name'] as $n ) {
            if ( ! empty( $n ) ) {
                $upload_names[] = $n;
            }
        }
    
        if ( empty( $upload_names ) ) {
            $this->add_notice( 'warning', __( 'No valid files found for upload.', 'nnax-frontend-user-files' ) );
            return;
        }
    
        $upload_count = count( $upload_names );
        $settings = NNAXFUF_Helpers::get_settings();
    
        $allowed_types = ! empty( $settings['allowed_types'] )
            ? NNAXFUF_Helpers::normalize_safe_upload_extension_list( $settings['allowed_types'] )
            : array();
        $blocked_types      = ! empty( $settings['blocked_types'] ) ? $this->normalize_extension_list( $settings['blocked_types'] ) : array();
        $enforce_mime_check = ! empty( $settings['enforce_mime_check'] );
        $max_file_size_mb   = isset( $settings['max_file_size_mb'] ) ? max( 0, (float) $settings['max_file_size_mb'] ) : 0;
    
        $quota = NNAXFUF_Helpers::get_user_quota_for( $current_user_id );
        $max_files = ! empty( $quota['max_files'] ) ? (int) $quota['max_files'] : 0;
        $max_mb    = ! empty( $quota['max_mb'] ) ? (float) $quota['max_mb'] : 0;
    
        $usage = NNAXFUF_Helpers::get_user_usage( $current_user_id );
        $current_count = $usage['file_count'];
        $current_size  = $usage['total_bytes'];
    
        require_once ABSPATH . 'wp-admin/includes/file.php';

        $inherited_shared_ids = array();
        $inherited_share_rules = array();
        if ( $current_folder_id > 0 ) {
            $parent_folder = get_post( $current_folder_id );
            if ( ! $parent_folder || $parent_folder->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) {
                $this->add_notice( 'danger', __( 'Target folder not found.', 'nnax-frontend-user-files' ) );
                return;
            }

            if ( ! NNAXFUF_Helpers::user_can_manage_item( $current_user_id, $current_folder_id ) ) {
                $this->add_notice( 'danger', __( 'No permission to upload to this folder.', 'nnax-frontend-user-files' ) );
                return;
            }

            $parent_rules = NNAXFUF_Helpers::get_active_share_rules( $current_folder_id );
            // New files inside a shared folder inherit the folder shares (except owner).
            $inherited_share_rules = array_filter( $parent_rules, function ( $rule, $id ) use ( $current_user_id ) {
                if ( ! is_array( $rule ) ) {
                    return false;
                }
                if ( is_numeric( $id ) ) {
                    return ( (int) $id > 0 ) && ( (int) $id !== (int) $current_user_id );
                }
                return ( strpos( (string) $id, 'role:' ) === 0 );
            }, ARRAY_FILTER_USE_BOTH );
            $inherited_shared_ids = array_values( array_filter( array_map( 'intval', array_keys( $inherited_share_rules ) ) ) );

            // If a manager uploads into a shared folder, ensure the folder owner keeps access.
            $parent_owner_id = (int) get_post_meta( $current_folder_id, NNAXFUF_Plugin::META_OWNER, true );
            if ( $parent_owner_id <= 0 ) {
                $parent_owner_id = (int) $parent_folder->post_author;
            }
            if ( $parent_owner_id > 0 && $parent_owner_id !== (int) $current_user_id ) {
                $owner_key = (string) $parent_owner_id;
                $existing  = isset( $inherited_share_rules[ $owner_key ] ) && is_array( $inherited_share_rules[ $owner_key ] )
                    ? $inherited_share_rules[ $owner_key ]
                    : array();
                $inherited_share_rules[ $owner_key ] = array(
                    'starts_ts'    => isset( $existing['starts_ts'] ) ? (int) $existing['starts_ts'] : 0,
                    'expires_ts'   => isset( $existing['expires_ts'] ) ? (int) $existing['expires_ts'] : 0,
                    'owner_rights' => true,
                    'can_delete'   => ! empty( $existing['can_delete'] ),
                );
                $inherited_shared_ids[] = $parent_owner_id;
                $inherited_shared_ids   = array_values( array_unique( array_map( 'intval', $inherited_shared_ids ) ) );
            }
        }
        $created_count = 0;
        foreach ( $files['name'] as $index => $filename ) {
            if ( empty( $filename ) ) {
                continue;
            }

            $single_file = array(
                'name'     => isset( $files['name'][ $index ] ) ? $files['name'][ $index ] : '',
                'type'     => isset( $files['type'][ $index ] ) ? $files['type'][ $index ] : '',
                'tmp_name' => isset( $files['tmp_name'][ $index ] ) ? $files['tmp_name'][ $index ] : '',
                'error'    => isset( $files['error'][ $index ] ) ? (int) $files['error'][ $index ] : UPLOAD_ERR_NO_FILE,
                'size'     => isset( $files['size'][ $index ] ) ? (int) $files['size'][ $index ] : 0,
            );

            if ( $single_file['error'] !== UPLOAD_ERR_OK ) {
                $this->add_notice(
                    'danger',
                    sprintf(
                        /* translators: 1: filename, 2: upload error message. */
                        __( 'Upload failed for "%1$s": %2$s', 'nnax-frontend-user-files' ),
                        sanitize_file_name( (string) $filename ),
                        $this->get_upload_error_message( $single_file['error'] )
                    )
                );
                continue;
            }

            $ext = ltrim( strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) ), '.' );
            $ext = preg_replace( '/[^a-z0-9]/', '', $ext );

            if ( $ext !== '' && NNAXFUF_Helpers::is_restricted_upload_extension( $ext ) ) {
                /* translators: %s: file extension. */
                $this->add_notice( 'danger', sprintf( __( 'File type "%s" is not allowed.', 'nnax-frontend-user-files' ), $ext ) );
                continue;
            }

            if ( ! empty( $ext ) && in_array( $ext, $blocked_types, true ) ) {
                /* translators: %s: file extension. */
                $this->add_notice( 'danger', sprintf( __( 'File type "%s" is blocked.', 'nnax-frontend-user-files' ), $ext ) );
                continue;
            }

            if ( ! empty( $allowed_types ) && ! in_array( $ext, $allowed_types, true ) ) {
                /* translators: %s: file extension. */
                $this->add_notice( 'danger', sprintf( __( 'File type "%s" is not allowed.', 'nnax-frontend-user-files' ), $ext ) );
                continue;
            }

            if ( $max_file_size_mb > 0 ) {
                $max_file_size_bytes = (int) round( $max_file_size_mb * 1024 * 1024 );
                if ( $single_file['size'] > $max_file_size_bytes ) {
                    $this->add_notice(
                        'danger',
                        sprintf(
                            /* translators: 1: filename, 2: max size in MB. */
                            __( 'File "%1$s" exceeds the maximum size of %2$s MB.', 'nnax-frontend-user-files' ),
                            sanitize_file_name( (string) $filename ),
                            rtrim( rtrim( number_format( $max_file_size_mb, 2, '.', '' ), '0' ), '.' )
                        )
                    );
                    continue;
                }
            }

            if ( $enforce_mime_check ) {
                $tmp_name = (string) $single_file['tmp_name'];
                $mime_check = array();
                if ( $tmp_name !== '' ) {
                    $mime_check = wp_check_filetype_and_ext( $tmp_name, $filename );
                }

                $detected_ext  = isset( $mime_check['ext'] ) ? strtolower( (string) $mime_check['ext'] ) : '';
                $detected_mime = isset( $mime_check['type'] ) ? (string) $mime_check['type'] : '';

                if ( $detected_ext === '' || $detected_mime === '' ) {
                    /* translators: %s: filename. */
                    $this->add_notice( 'danger', sprintf( __( 'File "%s" was rejected by MIME validation.', 'nnax-frontend-user-files' ), sanitize_file_name( (string) $filename ) ) );
                    continue;
                }

                if ( $ext !== '' && $detected_ext !== $ext ) {
                    /* translators: %s: filename. */
                    $this->add_notice( 'danger', sprintf( __( 'File "%s" has inconsistent extension and MIME type.', 'nnax-frontend-user-files' ), sanitize_file_name( (string) $filename ) ) );
                    continue;
                }
            }
    
            // Renaming filter to obfuscate filename on server
            $obfuscate_filter = function( $file ) {
                $ext  = pathinfo( $file['name'], PATHINFO_EXTENSION );
                $name = md5( uniqid( (string) wp_rand(), true ) );
                $file['name'] = $name . ( $ext ? '.' . $ext : '' );
                return $file;
            };
            add_filter( 'wp_handle_upload_prefilter', $obfuscate_filter );

            $uploaded = wp_handle_upload( $single_file, array( 'test_form' => false ) );

            remove_filter( 'wp_handle_upload_prefilter', $obfuscate_filter );

            if ( isset( $uploaded['error'] ) || empty( $uploaded['file'] ) || empty( $uploaded['url'] ) ) {
                $error_message = isset( $uploaded['error'] ) ? (string) $uploaded['error'] : __( 'Upload failed.', 'nnax-frontend-user-files' );
                $this->add_notice(
                    'danger',
                    sprintf(
                        /* translators: 1: filename, 2: upload error message. */
                        __( 'Upload failed for "%1$s": %2$s', 'nnax-frontend-user-files' ),
                        sanitize_file_name( (string) $filename ),
                        $error_message
                    )
                );
                continue;
            }
    
            $file_url  = $uploaded['url'];
            $file_path = $uploaded['file'];
            do_action( 'nnax_file_manager_after_upload_file', $uploaded, $single_file, $current_user_id );
            do_action_deprecated(
                'nnaxfuf_after_upload_file',
                array( $uploaded, $single_file, $current_user_id ),
                '1.0.2',
                'nnax_file_manager_after_upload_file'
            );
    
            if ( ( $max_files > 0 ) || ( $max_mb > 0 ) ) {
                // Quotas are enforced after upload and we remove the temporary file
                // if limits are exceeded to avoid orphaned uploads.
                $new_file_size = file_exists( $file_path ) ? filesize( $file_path ) : 0;
                if ( $max_files > 0 ) {
                    $future_count = $current_count + 1;
                    if ( $future_count > $max_files ) {
                        wp_delete_file( $file_path );
                        $this->add_notice( 'danger', __( 'Upload limit reached (number of files).', 'nnax-frontend-user-files' ) );
                        continue;
                    }
                } else {
                    $future_count = $current_count + 1;
                }
                if ( $max_mb > 0 ) {
                    $future_size = $current_size + $new_file_size;
                    $future_mb   = $future_size / ( 1024 * 1024 );
                    if ( $future_mb > $max_mb ) {
                        wp_delete_file( $file_path );
                        $this->add_notice( 'danger', __( 'Upload limit reached (storage space).', 'nnax-frontend-user-files' ) );
                        continue;
                    }
                } else {
                    $future_size = $current_size + $new_file_size;
                }
                $current_count = $future_count;
                $current_size  = $future_size;
            }
    
            if ( $upload_count === 1 && ! empty( $custom_title ) ) {
                $title = $custom_title;
            } else {
                $title = $filename; // Use original filename (before obfuscation)
            }
    
            $post_id = wp_insert_post( array(
                'post_type'   => NNAXFUF_Plugin::CPT,
                'post_status' => 'publish',
                'post_title'  => $title,
                'post_author' => $current_user_id,
                'post_parent' => $current_folder_id,
            ) );
    
            if ( $post_id && ! is_wp_error( $post_id ) ) {
                update_post_meta( $post_id, NNAXFUF_Plugin::META_OWNER,    $current_user_id );
                update_post_meta( $post_id, NNAXFUF_Plugin::META_FILEURL,  esc_url_raw( $file_url ) );
                update_post_meta( $post_id, NNAXFUF_Plugin::META_FILEPATH, $file_path );
                update_post_meta( $post_id, NNAXFUF_Plugin::META_SHARED,   $inherited_shared_ids );
                NNAXFUF_Helpers::set_item_share_rules( $post_id, $inherited_share_rules );

                NNAXFUF_Helpers::log_activity( $post_id, 'upload' );

                $created_count++;
            }
        }

        if ( $created_count > 0 ) {
            /* translators: %d: number of uploaded files. */
            $this->add_notice( 'success', sprintf( __( '%d files uploaded successfully.', 'nnax-frontend-user-files' ), $created_count ) );
        } else {
            $this->add_notice( 'warning', __( 'No file was uploaded.', 'nnax-frontend-user-files' ) );
        }
        // phpcs:enable WordPress.Security.NonceVerification.Missing
    }

    /**
     * Build share rules from selected user ids + current sharing settings.
     */
    private function build_share_rules_from_request( $owner_id, $new_ids, $is_folder_share = false, $raw_configs = array() ) {
        $settings      = NNAXFUF_Helpers::get_settings();
        $share_options = isset( $settings['share_options'] ) && is_array( $settings['share_options'] ) ? $settings['share_options'] : array();
        $allow_time_limit   = ! empty( $share_options['allow_time_limit'] );
        $allow_owner_rights = ! empty( $share_options['allow_owner_rights'] );
        $allow_shared_folder_file_delete = ! empty( $share_options['allow_shared_folder_file_delete'] );
        $raw_configs = is_array( $raw_configs ) ? $raw_configs : array();

        $rules = array();
        foreach ( $new_ids as $user_id ) {
            $user_id = $this->sanitize_share_id( $user_id );
            if ( ! $user_id || $user_id === (int) $owner_id ) {
                continue;
            }

            $config = ( isset( $raw_configs[ $user_id ] ) && is_array( $raw_configs[ $user_id ] ) )
                ? $raw_configs[ $user_id ]
                : array();

            $owner_rights = false;
            $role = 'normal';
            if ( $allow_owner_rights ) {
                $role = isset( $config['role'] ) ? sanitize_key( (string) $config['role'] ) : 'normal';
                $owner_rights = in_array( $role, array( 'manager', 'admin' ), true );
            }

            $can_delete = false;
            if ( $is_folder_share && $allow_shared_folder_file_delete ) {
                $can_delete = ( $role === 'admin' );
            }

            $starts_ts  = 0;
            $expires_ts = 0;
            if ( $allow_time_limit ) {
                $starts_raw = isset( $config['starts_on'] ) ? (string) $config['starts_on'] : '';
                $expires_raw = isset( $config['expires_on'] ) ? (string) $config['expires_on'] : '';
                $starts_ts   = NNAXFUF_Helpers::parse_share_start_date( $starts_raw );
                $expires_ts  = NNAXFUF_Helpers::parse_share_expiry_date( $expires_raw );

                if ( $starts_ts > 0 && $expires_ts > 0 && $starts_ts > $expires_ts ) {
                    $tmp = $starts_ts;
                    $starts_ts = $expires_ts;
                    $expires_ts = $tmp;
                }
            }

            $rules[ $user_id ] = array(
                'starts_ts'   => $starts_ts,
                'expires_ts'   => $expires_ts,
                'owner_rights' => $owner_rights,
                'can_delete'   => $can_delete,
            );
        }
        return $rules;
    }

    /**
     * Prevent non-owners from modifying their own effective share rule.
     */
    private function enforce_non_owner_self_share_lock( $current_user_id, $owner_id, $old_rules, $new_rules ) {
        $current_user_id = (int) $current_user_id;
        $owner_id        = (int) $owner_id;
        $old_rules       = NNAXFUF_Helpers::normalize_share_rules( is_array( $old_rules ) ? $old_rules : array() );
        $new_rules       = NNAXFUF_Helpers::normalize_share_rules( is_array( $new_rules ) ? $new_rules : array() );

        if ( $current_user_id <= 0 || $owner_id <= 0 || $current_user_id === $owner_id ) {
            return $new_rules;
        }

        $self_key = (string) $current_user_id;
        if ( isset( $old_rules[ $self_key ] ) ) {
            $new_rules[ $self_key ] = $old_rules[ $self_key ];
        } else {
            unset( $new_rules[ $self_key ] );
        }

        return $new_rules;
    }

    /**
     * Detect users who lost owner-rights on this folder share update.
     */
    private function get_owner_rights_downgrade_user_ids( $old_rules, $new_rules, $folder_owner_id ) {
        $folder_owner_id = (int) $folder_owner_id;
        $old_rules       = NNAXFUF_Helpers::normalize_share_rules( is_array( $old_rules ) ? $old_rules : array() );
        $new_rules       = NNAXFUF_Helpers::normalize_share_rules( is_array( $new_rules ) ? $new_rules : array() );
        $affected        = array();

        foreach ( $old_rules as $sid => $old_rule ) {
            if ( ! is_numeric( $sid ) ) {
                continue;
            }

            $uid = (int) $sid;
            if ( $uid <= 0 || $uid === $folder_owner_id ) {
                continue;
            }

            $old_has_owner_rights = ! empty( $old_rule['owner_rights'] );
            if ( ! $old_has_owner_rights ) {
                continue;
            }

            $new_has_owner_rights = ( isset( $new_rules[ $sid ] ) && ! empty( $new_rules[ $sid ]['owner_rights'] ) );
            if ( ! $new_has_owner_rights ) {
                $affected[] = $uid;
            }
        }

        return array_values( array_unique( array_map( 'intval', $affected ) ) );
    }

    /**
     * Transfer ownership of all descendants from selected users to the folder owner.
     */
    private function transfer_folder_descendant_ownership_for_users( $folder_id, $from_user_ids, $to_user_id ) {
        $folder_id      = (int) $folder_id;
        $to_user_id     = (int) $to_user_id;
        $from_user_ids  = array_values( array_filter( array_map( 'intval', (array) $from_user_ids ) ) );
        $stats          = array(
            'files' => 0,
            'folders' => 0,
            'total' => 0,
        );
        if ( $folder_id <= 0 || $to_user_id <= 0 || empty( $from_user_ids ) ) {
            return $stats;
        }

        $this->transfer_folder_descendant_ownership_recursive( $folder_id, $from_user_ids, $to_user_id, $folder_id, $stats );
        return $stats;
    }

    private function transfer_folder_descendant_ownership_recursive( $folder_id, $from_user_ids, $to_user_id, $root_folder_id, &$stats ) {
        $files = get_posts( array(
            'post_type'      => NNAXFUF_Plugin::CPT,
            'post_status'    => array( 'publish', 'trash' ),
            'posts_per_page' => -1,
            'post_parent'    => (int) $folder_id,
            'fields'         => 'ids',
        ) );
        foreach ( $files as $file_id ) {
            $file_id     = (int) $file_id;
            $current_uid = (int) get_post_meta( $file_id, NNAXFUF_Plugin::META_OWNER, true );
            if ( in_array( $current_uid, $from_user_ids, true ) ) {
                update_post_meta( $file_id, NNAXFUF_Plugin::META_OWNER, $to_user_id );
                $post = get_post( $file_id );
                if ( $post && (int) $post->post_author !== $to_user_id ) {
                    wp_update_post(
                        array(
                            'ID'          => $file_id,
                            'post_author' => $to_user_id,
                        )
                    );
                }
                NNAXFUF_Helpers::log_activity( $file_id, 'ownership_transfer', array(
                    'from_owner_id'    => $current_uid,
                    'to_owner_id'      => $to_user_id,
                    'trigger_folder_id'=> (int) $root_folder_id,
                ) );
                $stats['files']++;
                $stats['total']++;
            }
        }

        $subfolders = get_posts( array(
            'post_type'      => NNAXFUF_Plugin::CPT_FOLDER,
            'post_status'    => array( 'publish', 'trash' ),
            'posts_per_page' => -1,
            'post_parent'    => (int) $folder_id,
            'fields'         => 'ids',
        ) );
        foreach ( $subfolders as $subfolder_id ) {
            $subfolder_id = (int) $subfolder_id;
            $current_uid  = (int) get_post_meta( $subfolder_id, NNAXFUF_Plugin::META_OWNER, true );
            if ( in_array( $current_uid, $from_user_ids, true ) ) {
                update_post_meta( $subfolder_id, NNAXFUF_Plugin::META_OWNER, $to_user_id );
                $post = get_post( $subfolder_id );
                if ( $post && (int) $post->post_author !== $to_user_id ) {
                    wp_update_post(
                        array(
                            'ID'          => $subfolder_id,
                            'post_author' => $to_user_id,
                        )
                    );
                }
                NNAXFUF_Helpers::log_activity( $subfolder_id, 'ownership_transfer', array(
                    'from_owner_id'    => $current_uid,
                    'to_owner_id'      => $to_user_id,
                    'trigger_folder_id'=> (int) $root_folder_id,
                ) );
                $stats['folders']++;
                $stats['total']++;
            }

            $this->transfer_folder_descendant_ownership_recursive( $subfolder_id, $from_user_ids, $to_user_id, $root_folder_id, $stats );
        }
    }

    private function process_share_file( $current_user_id, $file_id, $new_ids, $raw_configs = array(), $send_notification = false, $custom_msg = '' ) {
        if ( $file_id <= 0 ) {
            $this->add_notice( 'warning', __( 'No file selected for sharing.', 'nnax-frontend-user-files' ) );
            return;
        }
        $post    = get_post( $file_id );
        if ( ! $post || $post->post_type !== NNAXFUF_Plugin::CPT ) {
            $this->add_notice( 'danger', __( 'File to share not found.', 'nnax-frontend-user-files' ) );
            return;
        }
        if ( ! NNAXFUF_Helpers::can_user_do( $current_user_id, $file_id, 'share' ) ) {
            $this->add_notice( 'danger', __( 'No permission to share this file.', 'nnax-frontend-user-files' ) );
            return;
        }
        
        $shared_with_before = NNAXFUF_Helpers::get_active_shared_user_ids( $file_id );
        $owner_id = (int) get_post_meta( $file_id, NNAXFUF_Plugin::META_OWNER, true );
        if ( $owner_id <= 0 ) {
            $owner_id = (int) $post->post_author;
        }

        $allowed_slugs = NNAXFUF_Helpers::get_allowed_share_roles_for_user( $current_user_id );
        foreach ( $new_ids as $id ) {
            $sid = $this->sanitize_share_id( $id );
            if ( $sid && strpos( $sid, 'role:' ) === 0 ) {
                $role_slug = substr( $sid, 5 );
                if ( ! in_array( $role_slug, $allowed_slugs, true ) && ! in_array( $sid, $shared_with_before, true ) ) {
                    /* translators: %s: role slug. */
                    $this->add_notice( 'danger', sprintf( __( 'No permission to share with role: %s.', 'nnax-frontend-user-files' ), $role_slug ) );
                    return;
                }
            }
        }

        $new_ids = array_values( array_filter( array_unique( $new_ids ), function ( $id ) use ( $owner_id ) {
            $sid = $this->sanitize_share_id( $id );
            // Note: $shared_with_before check earlier ensures we don't block existing roles that current user shouldn't normally target
            return $sid && $sid !== $owner_id;
        } ) );

        $old_rules = NNAXFUF_Helpers::get_item_share_rules( $file_id );
        $incoming_ids = array_map( array( $this, 'sanitize_share_id' ), $new_ids );
        $share_rules = $this->build_share_rules_from_request( $owner_id, $incoming_ids, false, $raw_configs );
        $share_rules = $this->enforce_non_owner_self_share_lock( $current_user_id, $owner_id, $old_rules, $share_rules );
        $all_ids = array_keys( $share_rules );
        
        $rules_changed = ( $old_rules !== $share_rules );
        
        NNAXFUF_Helpers::set_item_share_rules( $file_id, $share_rules );
        
        NNAXFUF_Helpers::log_activity( $file_id, 'share', array(
            'shared_with' => $all_ids
        ) );

        $newly_added = array_diff( $all_ids, $shared_with_before );
        if ( $send_notification ) {
            NNAXFUF_Helpers::send_share_notifications( $file_id, $newly_added, $custom_msg );
        }

        $this->add_share_update_notice( __( 'File shares updated', 'nnax-frontend-user-files' ), $shared_with_before, $all_ids, $rules_changed );
    }

    private function process_delete_file( $current_user_id, $file_id ) {
        if ( $file_id <= 0 ) {
            return;
        }
        $post    = get_post( $file_id );
        if ( ! $post || $post->post_type !== NNAXFUF_Plugin::CPT ) return;
        if ( ! NNAXFUF_Helpers::user_can_delete_item( $current_user_id, $file_id ) ) return;
        wp_trash_post( $file_id );
        NNAXFUF_Helpers::log_activity( $file_id, 'trash' );
        $this->add_notice( 'success', __( 'File moved to trash.', 'nnax-frontend-user-files' ) );
    }

    private function process_move_file( $current_user_id, $file_id, $target_parent_id ) {
        if ( $file_id <= 0 ) {
            $this->add_notice( 'danger', __( 'File not found.', 'nnax-frontend-user-files' ) );
            return;
        }

        $file = get_post( $file_id );
        if ( ! $file || $file->post_type !== NNAXFUF_Plugin::CPT ) {
            $this->add_notice( 'danger', __( 'File not found.', 'nnax-frontend-user-files' ) );
            return;
        }

        if ( ! NNAXFUF_Helpers::user_can_manage_item( $current_user_id, $file_id ) ) {
            $this->add_notice( 'danger', __( 'No permission to move this file.', 'nnax-frontend-user-files' ) );
            return;
        }

        if ( $target_parent_id > 0 ) {
            $target_parent = get_post( $target_parent_id );
            if ( ! $target_parent || $target_parent->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) {
                $this->add_notice( 'danger', __( 'Target folder not found.', 'nnax-frontend-user-files' ) );
                return;
            }

            if ( ! NNAXFUF_Helpers::user_can_manage_item( $current_user_id, $target_parent_id ) ) {
                $this->add_notice( 'danger', __( 'No permission for target folder.', 'nnax-frontend-user-files' ) );
                return;
            }
        }

        $source_parent_id = (int) $file->post_parent;
        $source_owner_id  = (int) get_post_meta( $file_id, NNAXFUF_Plugin::META_OWNER, true );
        if ( $source_owner_id <= 0 ) {
            $source_owner_id = (int) $file->post_author;
        }

        // Non-owners may not move files out of the shared folder scope.
        // Allowed destinations are only the same folder or its descendants.
        if ( $source_owner_id !== (int) $current_user_id && $source_parent_id > 0 ) {
            $is_inside_same_scope = ( $target_parent_id === $source_parent_id );
            if ( ! $is_inside_same_scope && $target_parent_id > 0 ) {
                $target_ancestors = array_map( 'intval', get_post_ancestors( $target_parent_id ) );
                $is_inside_same_scope = in_array( $source_parent_id, $target_ancestors, true );
            }

            if ( ! $is_inside_same_scope ) {
                $this->add_notice( 'danger', __( 'No permission to move this file outside the shared folder.', 'nnax-frontend-user-files' ) );
                return;
            }
        }

        if ( (int) $file->post_parent === $target_parent_id ) {
            $this->add_notice( 'warning', __( 'File is already in this location.', 'nnax-frontend-user-files' ) );
            return;
        }

        $old_parent_id = (int) $file->post_parent;

        $updated = wp_update_post(
            array(
                'ID'          => $file_id,
                'post_parent' => $target_parent_id,
            ),
            true
        );

        if ( is_wp_error( $updated ) ) {
            $this->add_notice( 'danger', __( 'File could not be moved.', 'nnax-frontend-user-files' ) );
            return;
        }

        $share_rules_synced = false;
        if ( $target_parent_id > 0 ) {
            // Keep moved files aligned with destination sharing while preserving
            // existing direct shares on the file.
            $existing_file_rules = NNAXFUF_Helpers::get_item_share_rules( $file_id );
            $target_folder_rules = NNAXFUF_Helpers::get_active_share_rules( $target_parent_id );
            $merged_share_rules  = array_replace(
                is_array( $existing_file_rules ) ? $existing_file_rules : array(),
                is_array( $target_folder_rules ) ? $target_folder_rules : array()
            );

            // Folder owners should always see/manage files inside their folder.
            $target_parent_post = get_post( $target_parent_id );
            $target_owner_id    = (int) get_post_meta( $target_parent_id, NNAXFUF_Plugin::META_OWNER, true );
            if ( $target_owner_id <= 0 ) {
                $target_owner_id = $target_parent_post ? (int) $target_parent_post->post_author : 0;
            }
            if ( $target_owner_id > 0 ) {
                $target_owner_key = (string) $target_owner_id;
                $merged_share_rules[ $target_owner_key ] = array(
                    'starts_ts'    => 0,
                    'expires_ts'   => 0,
                    'owner_rights' => true,
                    'can_delete'   => true,
                );
            }

            NNAXFUF_Helpers::set_item_share_rules( $file_id, $merged_share_rules );
            $share_rules_synced = true;
        }

        NNAXFUF_Helpers::log_activity( $file_id, 'move_file', array(
            'from_parent' => $old_parent_id,
            'to_parent'   => (int) $target_parent_id,
            'share_synced_with_parent' => $share_rules_synced,
        ) );

        $this->add_notice( 'success', __( 'File moved successfully.', 'nnax-frontend-user-files' ) );
    }

    private function process_create_folder( $current_user_id, $parent_folder_id = 0, $title = '' ) {
        if ( $title === '' ) {
            return;
        }

        $inherited_shared_ids = array();
        $inherited_share_rules = array();
        if ( $parent_folder_id > 0 ) {
            $parent_folder = get_post( $parent_folder_id );
            if ( ! $parent_folder || $parent_folder->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) {
                $this->add_notice( 'danger', __( 'Parent folder not found.', 'nnax-frontend-user-files' ) );
                return;
            }

            if ( ! NNAXFUF_Helpers::user_can_manage_item( $current_user_id, $parent_folder_id ) ) {
                $this->add_notice( 'danger', __( 'No permission to create subfolders.', 'nnax-frontend-user-files' ) );
                return;
            }

            $parent_rules = NNAXFUF_Helpers::get_active_share_rules( $parent_folder_id );
            $inherited_share_rules = array_filter( $parent_rules, function ( $rule, $id ) use ( $current_user_id ) {
                if ( ! is_array( $rule ) ) {
                    return false;
                }
                if ( is_numeric( $id ) ) {
                    return ( (int) $id > 0 ) && ( (int) $id !== (int) $current_user_id );
                }
                return ( strpos( (string) $id, 'role:' ) === 0 );
            }, ARRAY_FILTER_USE_BOTH );
            $inherited_shared_ids = array_map( 'intval', array_keys( $inherited_share_rules ) );

            // If a manager creates a subfolder in a shared folder, keep parent owner access.
            $parent_owner_id = (int) get_post_meta( $parent_folder_id, NNAXFUF_Plugin::META_OWNER, true );
            if ( $parent_owner_id <= 0 ) {
                $parent_owner_id = (int) $parent_folder->post_author;
            }
            if ( $parent_owner_id > 0 && $parent_owner_id !== (int) $current_user_id ) {
                $owner_key = (string) $parent_owner_id;
                $existing  = isset( $inherited_share_rules[ $owner_key ] ) && is_array( $inherited_share_rules[ $owner_key ] )
                    ? $inherited_share_rules[ $owner_key ]
                    : array();
                $inherited_share_rules[ $owner_key ] = array(
                    'starts_ts'    => isset( $existing['starts_ts'] ) ? (int) $existing['starts_ts'] : 0,
                    'expires_ts'   => isset( $existing['expires_ts'] ) ? (int) $existing['expires_ts'] : 0,
                    'owner_rights' => true,
                    'can_delete'   => true,
                );
                $inherited_shared_ids[] = $parent_owner_id;
                $inherited_shared_ids   = array_values( array_unique( array_map( 'intval', $inherited_shared_ids ) ) );
            }
        }

        $folder_id = wp_insert_post( array(
            'post_type'   => NNAXFUF_Plugin::CPT_FOLDER,
            'post_status' => 'publish',
            'post_title'  => $title,
            'post_parent' => $parent_folder_id,
            'post_author' => $current_user_id,
        ) );
        if ( $folder_id && ! is_wp_error( $folder_id ) ) {
            update_post_meta( $folder_id, NNAXFUF_Plugin::META_OWNER,  $current_user_id );
            update_post_meta( $folder_id, NNAXFUF_Plugin::META_SHARED, $inherited_shared_ids );
            NNAXFUF_Helpers::set_item_share_rules( $folder_id, $inherited_share_rules );
            $this->add_notice( 'success', __( 'Folder created successfully.', 'nnax-frontend-user-files' ) );
            return;
        }
        $this->add_notice( 'danger', __( 'Folder could not be created.', 'nnax-frontend-user-files' ) );
    }

    private function process_delete_folder( $current_user_id, $folder_id ) {
        if ( $folder_id <= 0 ) {
            return;
        }
        $post      = get_post( $folder_id );
        if ( ! $post || $post->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) return;
        if ( ! NNAXFUF_Helpers::user_can_delete_item( $current_user_id, $folder_id ) ) return;
        $this->trash_folder_non_recursive( $folder_id );
        NNAXFUF_Helpers::log_activity( $folder_id, 'trash' );
        $this->add_notice( 'success', __( 'Folder moved to trash.', 'nnax-frontend-user-files' ) );
    }

    private function process_move_folder( $current_user_id, $folder_id, $target_parent_id ) {
        if ( $folder_id <= 0 ) {
            $this->add_notice( 'danger', __( 'Folder not found.', 'nnax-frontend-user-files' ) );
            return;
        }

        $folder = get_post( $folder_id );
        if ( ! $folder || $folder->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) {
            $this->add_notice( 'danger', __( 'Folder not found.', 'nnax-frontend-user-files' ) );
            return;
        }

        if ( ! NNAXFUF_Helpers::user_can_manage_item( $current_user_id, $folder_id ) ) {
            $this->add_notice( 'danger', __( 'No permission to move this folder.', 'nnax-frontend-user-files' ) );
            return;
        }

        if ( $target_parent_id === $folder_id ) {
            $this->add_notice( 'danger', __( 'A folder cannot be moved into itself.', 'nnax-frontend-user-files' ) );
            return;
        }

        if ( $target_parent_id > 0 ) {
            $target_parent = get_post( $target_parent_id );
            if ( ! $target_parent || $target_parent->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) {
                $this->add_notice( 'danger', __( 'Target folder not found.', 'nnax-frontend-user-files' ) );
                return;
            }

            if ( ! NNAXFUF_Helpers::user_can_manage_item( $current_user_id, $target_parent_id ) ) {
                $this->add_notice( 'danger', __( 'No permission for target folder.', 'nnax-frontend-user-files' ) );
                return;
            }

            $target_ancestors = array_map( 'intval', get_post_ancestors( $target_parent_id ) );
            // Prevent cycles: a folder cannot be moved into any of its descendants.
            if ( in_array( $folder_id, $target_ancestors, true ) ) {
                $this->add_notice( 'danger', __( 'Folder cannot be moved into a subfolder.', 'nnax-frontend-user-files' ) );
                return;
            }
        }

        if ( (int) $folder->post_parent === $target_parent_id ) {
            $this->add_notice( 'warning', __( 'Folder is already in this location.', 'nnax-frontend-user-files' ) );
            return;
        }

        $old_parent_id = (int) $folder->post_parent;

        $updated = wp_update_post(
            array(
                'ID'          => $folder_id,
                'post_parent' => $target_parent_id,
            ),
            true
        );

        if ( is_wp_error( $updated ) ) {
            $this->add_notice( 'danger', __( 'Folder could not be moved.', 'nnax-frontend-user-files' ) );
            return;
        }

        NNAXFUF_Helpers::log_activity( $folder_id, 'move_folder', array(
            'from_parent' => $old_parent_id,
            'to_parent'   => (int) $target_parent_id,
        ) );

        $this->add_notice( 'success', __( 'Folder moved successfully.', 'nnax-frontend-user-files' ) );
    }

    private function process_delete_folder_recursive( $current_user_id, $folder_id ) {
        if ( $folder_id <= 0 ) {
            return;
        }
        $post      = get_post( $folder_id );
        if ( ! $post || $post->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) return;
        if ( ! NNAXFUF_Helpers::user_can_delete_item( $current_user_id, $folder_id ) ) return;
        NNAXFUF_Helpers::trash_folder_contents_recursive( $folder_id );
        wp_trash_post( $folder_id );
        NNAXFUF_Helpers::log_activity( $folder_id, 'trash_recursive' );
        $this->add_notice( 'success', __( 'Folder and contents moved to trash.', 'nnax-frontend-user-files' ) );
    }

    private function process_share_folder( $current_user_id, $folder_id, $new_ids, $raw_configs = array(), $send_notification = false, $custom_msg = '' ) {
        if ( $folder_id <= 0 ) {
            return;
        }
        $post      = get_post( $folder_id );
        if ( ! $post || $post->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) return;
        if ( ! NNAXFUF_Helpers::can_user_do( $current_user_id, $folder_id, 'share' ) ) {
            $this->add_notice( 'danger', __( 'No permission to share this folder.', 'nnax-frontend-user-files' ) );
            return;
        }

        $shared_with_before = NNAXFUF_Helpers::get_active_shared_user_ids( $folder_id );
        $owner_id = (int) get_post_meta( $folder_id, NNAXFUF_Plugin::META_OWNER, true );
        if ( $owner_id <= 0 ) {
            $owner_id = (int) $post->post_author;
        }

        $allowed_slugs = NNAXFUF_Helpers::get_allowed_share_roles_for_user( $current_user_id );
        foreach ( $new_ids as $id ) {
            $sid = $this->sanitize_share_id( $id );
            if ( $sid && strpos( $sid, 'role:' ) === 0 ) {
                $role_slug = substr( $sid, 5 );
                if ( ! in_array( $role_slug, $allowed_slugs, true ) && ! in_array( $sid, $shared_with_before, true ) ) {
                    /* translators: %s: role slug. */
                    $this->add_notice( 'danger', sprintf( __( 'No permission to share with role: %s.', 'nnax-frontend-user-files' ), $role_slug ) );
                    return;
                }
            }
        }

        $new_ids = array_values( array_filter( array_unique( $new_ids ), function( $id ) use ( $owner_id ) {
            $sid = $this->sanitize_share_id( $id );
            return $sid && $sid !== $owner_id;
        } ) );

        $old_rules = NNAXFUF_Helpers::get_item_share_rules( $folder_id );
        $incoming_ids = array_map( array( $this, 'sanitize_share_id' ), $new_ids );
        $share_rules = $this->build_share_rules_from_request( $owner_id, $incoming_ids, true, $raw_configs );
        $share_rules = $this->enforce_non_owner_self_share_lock( $current_user_id, $owner_id, $old_rules, $share_rules );
        $all_ids = array_keys( $share_rules );
        $ownership_transfer_user_ids = array();
        if ( (int) $current_user_id === (int) $owner_id ) {
            $ownership_transfer_user_ids = $this->get_owner_rights_downgrade_user_ids( $old_rules, $share_rules, $owner_id );
        }
        
        $rules_changed = ( $old_rules !== $share_rules );
        
        NNAXFUF_Helpers::set_item_share_rules( $folder_id, $share_rules );

        // Keep folder owner access on descendants, even if descendants are owned
        // by a manager who created them inside this shared folder.
        $propagation_rules = $share_rules;
        $propagation_ids   = $all_ids;
        if ( $owner_id > 0 ) {
            $owner_key = (string) $owner_id;
            $existing  = isset( $propagation_rules[ $owner_key ] ) && is_array( $propagation_rules[ $owner_key ] )
                ? $propagation_rules[ $owner_key ]
                : array();
            $propagation_rules[ $owner_key ] = array(
                'starts_ts'    => isset( $existing['starts_ts'] ) ? (int) $existing['starts_ts'] : 0,
                'expires_ts'   => isset( $existing['expires_ts'] ) ? (int) $existing['expires_ts'] : 0,
                'owner_rights' => true,
                'can_delete'   => true,
            );
            $propagation_ids[] = $owner_id;
            $propagation_ids   = array_values( array_unique( array_map( array( $this, 'sanitize_share_id' ), $propagation_ids ) ) );
        }

        NNAXFUF_Helpers::share_folder_contents_recursive( $folder_id, $propagation_ids, true, $propagation_rules );
        $ownership_transfer_stats = array(
            'files' => 0,
            'folders' => 0,
            'total' => 0,
        );
        if ( ! empty( $ownership_transfer_user_ids ) ) {
            $ownership_transfer_stats = $this->transfer_folder_descendant_ownership_for_users( $folder_id, $ownership_transfer_user_ids, $owner_id );
            if ( ! empty( $ownership_transfer_stats['total'] ) ) {
                NNAXFUF_Helpers::log_activity( $folder_id, 'ownership_transfer', array(
                    'mode' => 'bulk',
                    'to_owner_id' => $owner_id,
                    'from_owner_ids' => array_values( array_map( 'intval', $ownership_transfer_user_ids ) ),
                    'files' => (int) $ownership_transfer_stats['files'],
                    'folders' => (int) $ownership_transfer_stats['folders'],
                    'total' => (int) $ownership_transfer_stats['total'],
                ) );
            }
        }
        
        NNAXFUF_Helpers::log_activity( $folder_id, 'share_folder', array(
            'shared_with' => $all_ids
        ) );

        $newly_added = array_diff( $all_ids, $shared_with_before );
        if ( $send_notification ) {
            NNAXFUF_Helpers::send_share_notifications( $folder_id, $newly_added, $custom_msg );
        }

        $this->add_share_update_notice( __( 'Folder sharing saved', 'nnax-frontend-user-files' ), $shared_with_before, $all_ids, $rules_changed );
        if ( ! empty( $ownership_transfer_stats['total'] ) ) {
            $this->add_notice(
                'success',
                sprintf(
                    /* translators: %d: number of transferred items. */
                    __( 'Ownership transferred for %d item(s) after permission downgrade.', 'nnax-frontend-user-files' ),
                    (int) $ownership_transfer_stats['total']
                )
            );
        }
    }

    private function process_sync_folder_share( $current_user_id, $folder_id ) {
        if ( $folder_id <= 0 ) {
            return;
        }
        $post      = get_post( $folder_id );
        if ( ! $post || $post->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) return;
        if ( ! NNAXFUF_Helpers::can_user_do( $current_user_id, $folder_id, 'share' ) ) {
            $this->add_notice( 'danger', __( 'No permission to sync folder shares.', 'nnax-frontend-user-files' ) );
            return;
        }
        $owner_id = (int) get_post_meta( $folder_id, NNAXFUF_Plugin::META_OWNER, true );
        if ( $owner_id <= 0 && $post ) {
            $owner_id = (int) $post->post_author;
        }

        $shared_with = NNAXFUF_Helpers::get_active_shared_user_ids( $folder_id );
        // Rebuild from active rules only and drop owner id, then propagate with
        // replace_existing=true so descendants match the current folder state.
        $shared_with = array_values( array_filter( $shared_with, function ( $id ) use ( $owner_id ) {
            if ( is_numeric( $id ) ) {
                return ( (int) $id > 0 ) && ( (int) $id !== (int) $owner_id );
            }
            return strpos( (string) $id, 'role:' ) === 0;
        } ) );
        $folder_rules = NNAXFUF_Helpers::get_active_share_rules( $folder_id );
        $folder_rules = array_intersect_key( $folder_rules, array_flip( $shared_with ) );

        NNAXFUF_Helpers::set_item_share_rules( $folder_id, $folder_rules );

        // Keep folder owner access on descendants, even if descendants are owned
        // by a manager who created them inside this shared folder.
        $propagation_rules = $folder_rules;
        $propagation_ids   = $shared_with;
        if ( $owner_id > 0 ) {
            $owner_key = (string) $owner_id;
            $existing  = isset( $propagation_rules[ $owner_key ] ) && is_array( $propagation_rules[ $owner_key ] )
                ? $propagation_rules[ $owner_key ]
                : array();
            $propagation_rules[ $owner_key ] = array(
                'starts_ts'    => isset( $existing['starts_ts'] ) ? (int) $existing['starts_ts'] : 0,
                'expires_ts'   => isset( $existing['expires_ts'] ) ? (int) $existing['expires_ts'] : 0,
                'owner_rights' => true,
                'can_delete'   => true,
            );
            $propagation_ids[] = $owner_id;
            $propagation_ids   = array_values( array_unique( array_map( array( $this, 'sanitize_share_id' ), $propagation_ids ) ) );
        }

        NNAXFUF_Helpers::share_folder_contents_recursive( $folder_id, $propagation_ids, true, $propagation_rules );
        NNAXFUF_Helpers::log_activity( $folder_id, 'sync_folder_share', array(
            'shared_with' => $shared_with,
        ) );
        $this->add_notice( 'success', __( 'Shares synced successfully.', 'nnax-frontend-user-files' ) );
    }

    private function process_restore_item( $current_user_id, $item_id ) {
        if ( $item_id <= 0 ) {
            return;
        }
        $post    = get_post( $item_id );
        if ( ! $post || ( $post->post_type !== NNAXFUF_Plugin::CPT && $post->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) ) return;
        if ( ! NNAXFUF_Helpers::can_user_do( $current_user_id, $item_id, 'restore' ) ) {
            $this->add_notice( 'danger', __( 'No permission to restore this item.', 'nnax-frontend-user-files' ) );
            return;
        }
        if ( $post->post_type === NNAXFUF_Plugin::CPT_FOLDER ) {
            // Restore folder first so child restores can keep their valid parent relation.
            NNAXFUF_Helpers::restore_single_item( $item_id );
            NNAXFUF_Helpers::restore_folder_contents_recursive( $current_user_id, $item_id );
        } else {
            NNAXFUF_Helpers::restore_single_item( $item_id );
        }
        NNAXFUF_Helpers::log_activity( $item_id, 'restore', array(
            'post_type' => (string) $post->post_type,
        ) );
        $this->add_notice( 'success', __( 'Item restored successfully.', 'nnax-frontend-user-files' ) );
    }

    private function process_delete_item_permanently( $current_user_id, $item_id ) {
        if ( $item_id <= 0 ) {
            return;
        }
        $post    = get_post( $item_id );
        if ( ! $post || ( $post->post_type !== NNAXFUF_Plugin::CPT && $post->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) ) return;
        if ( ! NNAXFUF_Helpers::can_user_do( $current_user_id, $item_id, 'permanent_delete' ) ) {
            $this->add_notice( 'danger', __( 'No permission to permanently delete this item.', 'nnax-frontend-user-files' ) );
            return;
        }

        if ( $post->post_type === NNAXFUF_Plugin::CPT ) {
            $delete_result = $this->delete_physical_file_if_present( $item_id );
            if ( is_wp_error( $delete_result ) ) {
                $this->add_notice( 'danger', $delete_result->get_error_message() );
                return;
            }
        }

        NNAXFUF_Helpers::log_activity( $item_id, 'permanent_delete', array(
            'post_type' => (string) $post->post_type,
        ) );

        wp_delete_post( $item_id, true );
        $this->add_notice( 'success', __( 'Item deleted permanently.', 'nnax-frontend-user-files' ) );
    }

    private function process_bulk_delete( $current_user_id, $items_raw ) {
        $items = $this->parse_zip_items( $items_raw );
        if ( empty( $items ) ) {
            $this->add_notice( 'warning', __( 'No items selected for bulk delete.', 'nnax-frontend-user-files' ) );
            return;
        }

        $deleted = 0;
        $skipped = 0;

        foreach ( $items as $item ) {
            $type = isset( $item['type'] ) ? (string) $item['type'] : '';
            $id   = isset( $item['id'] ) ? (int) $item['id'] : 0;
            if ( $id <= 0 ) {
                $skipped++;
                continue;
            }

            $post = get_post( $id );
            if ( ! $post ) {
                $skipped++;
                continue;
            }

            if ( ! NNAXFUF_Helpers::user_can_delete_item( $current_user_id, $id ) ) {
                $skipped++;
                continue;
            }

            if ( $type === 'file' && $post->post_type === NNAXFUF_Plugin::CPT ) {
                wp_trash_post( $id );
                NNAXFUF_Helpers::log_activity( $id, 'bulk_delete', array( 'item_type' => 'file' ) );
                $deleted++;
                continue;
            }

            if ( $type === 'folder' && $post->post_type === NNAXFUF_Plugin::CPT_FOLDER ) {
                $this->trash_folder_non_recursive( $id );
                NNAXFUF_Helpers::log_activity( $id, 'bulk_delete', array( 'item_type' => 'folder' ) );
                $deleted++;
                continue;
            }

            $skipped++;
        }

        if ( $deleted > 0 ) {
            /* translators: %d: number of deleted items. */
            $this->add_notice( 'success', sprintf( __( '%d items deleted.', 'nnax-frontend-user-files' ), $deleted ) );
        }
        if ( $skipped > 0 ) {
            /* translators: %d: number of items that could not be deleted. */
            $this->add_notice( 'warning', sprintf( __( '%d items could not be deleted.', 'nnax-frontend-user-files' ), $skipped ) );
        }
    }

    private function process_bulk_move( $current_user_id, $items_raw, $target_parent_id ) {
        $items = $this->parse_zip_items( $items_raw );
        if ( empty( $items ) ) {
            $this->add_notice( 'warning', __( 'No items selected for bulk move.', 'nnax-frontend-user-files' ) );
            return;
        }
        if ( $target_parent_id > 0 ) {
            $target_parent = get_post( $target_parent_id );
            if ( ! $target_parent || $target_parent->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) {
                $this->add_notice( 'danger', __( 'Target folder not found.', 'nnax-frontend-user-files' ) );
                return;
            }

            if ( ! NNAXFUF_Helpers::user_can_manage_item( $current_user_id, $target_parent_id ) ) {
                $this->add_notice( 'danger', __( 'No permission for target folder.', 'nnax-frontend-user-files' ) );
                return;
            }
        }

        $moved   = 0;
        $skipped = 0;

        foreach ( $items as $item ) {
            $type = isset( $item['type'] ) ? (string) $item['type'] : '';
            $id   = isset( $item['id'] ) ? (int) $item['id'] : 0;
            if ( $id <= 0 ) {
                $skipped++;
                continue;
            }

            $post = get_post( $id );
            if ( ! $post ) {
                $skipped++;
                continue;
            }

            if ( ! NNAXFUF_Helpers::user_can_manage_item( $current_user_id, $id ) ) {
                $skipped++;
                continue;
            }

            if ( ( $type === 'file' && $post->post_type !== NNAXFUF_Plugin::CPT ) || ( $type === 'folder' && $post->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) ) {
                $skipped++;
                continue;
            }

            if ( (int) $post->post_parent === $target_parent_id ) {
                $skipped++;
                continue;
            }

            if ( $type === 'folder' ) {
                if ( $target_parent_id === $id ) {
                    $skipped++;
                    continue;
                }

                if ( $target_parent_id > 0 ) {
                    $target_ancestors = array_map( 'intval', get_post_ancestors( $target_parent_id ) );
                    if ( in_array( $id, $target_ancestors, true ) ) {
                        $skipped++;
                        continue;
                    }
                }
            }

            $old_parent_id = (int) $post->post_parent;
            $updated = wp_update_post(
                array(
                    'ID'          => $id,
                    'post_parent' => $target_parent_id,
                ),
                true
            );

            if ( is_wp_error( $updated ) ) {
                $skipped++;
                continue;
            }

            NNAXFUF_Helpers::log_activity(
                $id,
                'bulk_move',
                array(
                    'item_type'   => $type,
                    'from_parent' => $old_parent_id,
                    'to_parent'   => (int) $target_parent_id,
                )
            );

            $moved++;
        }

        if ( $moved > 0 ) {
            /* translators: %d: number of moved items. */
            $this->add_notice( 'success', sprintf( __( '%d items moved.', 'nnax-frontend-user-files' ), $moved ) );
        }
        if ( $skipped > 0 ) {
            /* translators: %d: number of items that could not be moved. */
            $this->add_notice( 'warning', sprintf( __( '%d items could not be moved.', 'nnax-frontend-user-files' ), $skipped ) );
        }
    }

    private function process_bulk_delete_recursive( $current_user_id, $items_raw ) {
        $items = $this->parse_zip_items( $items_raw );
        if ( empty( $items ) ) {
            $this->add_notice( 'warning', __( 'No items selected for recursive bulk delete.', 'nnax-frontend-user-files' ) );
            return;
        }

        $deleted = 0;
        $skipped = 0;

        foreach ( $items as $item ) {
            $type = isset( $item['type'] ) ? (string) $item['type'] : '';
            $id   = isset( $item['id'] ) ? (int) $item['id'] : 0;
            if ( $id <= 0 ) {
                $skipped++;
                continue;
            }

            $post = get_post( $id );
            if ( ! $post ) {
                $skipped++;
                continue;
            }

            if ( ! NNAXFUF_Helpers::user_can_delete_item( $current_user_id, $id ) ) {
                $skipped++;
                continue;
            }

            if ( $type === 'file' && $post->post_type === NNAXFUF_Plugin::CPT ) {
                wp_trash_post( $id );
                NNAXFUF_Helpers::log_activity( $id, 'bulk_delete_recursive', array( 'item_type' => 'file' ) );
                $deleted++;
                continue;
            }

            if ( $type === 'folder' && $post->post_type === NNAXFUF_Plugin::CPT_FOLDER ) {
                NNAXFUF_Helpers::trash_folder_contents_recursive( $id );
                wp_trash_post( $id );
                NNAXFUF_Helpers::log_activity( $id, 'bulk_delete_recursive', array( 'item_type' => 'folder' ) );
                $deleted++;
                continue;
            }

            $skipped++;
        }

        if ( $deleted > 0 ) {
            /* translators: %d: number of recursively deleted items. */
            $this->add_notice( 'success', sprintf( __( '%d items deleted recursively.', 'nnax-frontend-user-files' ), $deleted ) );
        }
        if ( $skipped > 0 ) {
            /* translators: %d: number of items that could not be recursively deleted. */
            $this->add_notice( 'warning', sprintf( __( '%d items could not be deleted recursively.', 'nnax-frontend-user-files' ), $skipped ) );
        }
    }

    private function process_empty_trash( $current_user_id ) {
        $deleted = 0;
        $skipped = 0;
        $errors  = 0;

        $trashed_files = get_posts(
            array(
                'post_type'      => NNAXFUF_Plugin::CPT,
                'post_status'    => 'trash',
                'posts_per_page' => -1,
                'fields'         => 'ids',
                // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Trash cleanup must restrict items to the current owner.
                'meta_query'     => array(
                    array(
                        'key'   => NNAXFUF_Plugin::META_OWNER,
                        'value' => (int) $current_user_id,
                        'type'  => 'NUMERIC',
                    ),
                ),
            )
        );

        foreach ( $trashed_files as $file_id ) {
            $file_id = (int) $file_id;
            if ( $file_id <= 0 ) {
                $skipped++;
                continue;
            }

            if ( ! NNAXFUF_Helpers::can_user_do( $current_user_id, $file_id, 'permanent_delete' ) ) {
                $skipped++;
                continue;
            }

            $delete_result = $this->delete_physical_file_if_present( $file_id );
            if ( is_wp_error( $delete_result ) ) {
                $errors++;
                continue;
            }

            NNAXFUF_Helpers::log_activity( $file_id, 'permanent_delete', array( 'post_type' => NNAXFUF_Plugin::CPT ) );
            wp_delete_post( $file_id, true );
            $deleted++;
        }

        $trashed_folders = get_posts(
            array(
                'post_type'      => NNAXFUF_Plugin::CPT_FOLDER,
                'post_status'    => 'trash',
                'posts_per_page' => -1,
                'fields'         => 'ids',
                'orderby'        => 'ID',
                'order'          => 'ASC',
                // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Trash cleanup must restrict items to the current owner.
                'meta_query'     => array(
                    array(
                        'key'   => NNAXFUF_Plugin::META_OWNER,
                        'value' => (int) $current_user_id,
                        'type'  => 'NUMERIC',
                    ),
                ),
            )
        );

        foreach ( $trashed_folders as $folder_id ) {
            $folder_id = (int) $folder_id;
            $post = get_post( $folder_id );
            if ( ! $post || $post->post_status !== 'trash' || $post->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) {
                continue;
            }
            $this->delete_trashed_folder_recursive( $current_user_id, $folder_id, $deleted, $skipped, $errors );
        }

        if ( $deleted > 0 ) {
            /* translators: %d: number of permanently deleted items from trash. */
            $this->add_notice( 'success', sprintf( __( 'Trash emptied: %d items permanently deleted.', 'nnax-frontend-user-files' ), $deleted ) );
        } else {
            $this->add_notice( 'warning', __( 'No items were permanently deleted from trash.', 'nnax-frontend-user-files' ) );
        }

        if ( $skipped > 0 ) {
            /* translators: %d: number of skipped trashed items. */
            $this->add_notice( 'warning', sprintf( __( '%d trashed item(s) were skipped due to missing permissions.', 'nnax-frontend-user-files' ), $skipped ) );
        }

        if ( $errors > 0 ) {
            /* translators: %d: number of files that could not be removed from storage. */
            $this->add_notice( 'danger', sprintf( __( '%d file(s) could not be deleted from server storage.', 'nnax-frontend-user-files' ), $errors ) );
        }
    }

    private function delete_trashed_folder_recursive( $current_user_id, $folder_id, &$deleted, &$skipped, &$errors ) {
        $folder_id = (int) $folder_id;
        $post = get_post( $folder_id );
        if ( ! $post || $post->post_status !== 'trash' || $post->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) {
            return;
        }

        if ( ! NNAXFUF_Helpers::can_user_do( $current_user_id, $folder_id, 'permanent_delete' ) ) {
            $skipped++;
            return;
        }

        $child_folders = get_posts(
            array(
                'post_type'      => NNAXFUF_Plugin::CPT_FOLDER,
                'post_status'    => 'trash',
                'posts_per_page' => -1,
                'post_parent'    => $folder_id,
                'fields'         => 'ids',
                // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Trash cleanup must restrict items to the current owner.
                'meta_query'     => array(
                    array(
                        'key'   => NNAXFUF_Plugin::META_OWNER,
                        'value' => (int) $current_user_id,
                        'type'  => 'NUMERIC',
                    ),
                ),
            )
        );

        foreach ( $child_folders as $child_folder_id ) {
            $this->delete_trashed_folder_recursive( $current_user_id, (int) $child_folder_id, $deleted, $skipped, $errors );
        }

        $child_files = get_posts(
            array(
                'post_type'      => NNAXFUF_Plugin::CPT,
                'post_status'    => 'trash',
                'posts_per_page' => -1,
                'post_parent'    => $folder_id,
                'fields'         => 'ids',
                // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Trash cleanup must restrict items to the current owner.
                'meta_query'     => array(
                    array(
                        'key'   => NNAXFUF_Plugin::META_OWNER,
                        'value' => (int) $current_user_id,
                        'type'  => 'NUMERIC',
                    ),
                ),
            )
        );

        foreach ( $child_files as $child_file_id ) {
            $child_file_id = (int) $child_file_id;
            if ( ! NNAXFUF_Helpers::can_user_do( $current_user_id, $child_file_id, 'permanent_delete' ) ) {
                $skipped++;
                continue;
            }

            $delete_result = $this->delete_physical_file_if_present( $child_file_id );
            if ( is_wp_error( $delete_result ) ) {
                $errors++;
                continue;
            }

            NNAXFUF_Helpers::log_activity( $child_file_id, 'permanent_delete', array( 'post_type' => NNAXFUF_Plugin::CPT ) );
            wp_delete_post( $child_file_id, true );
            $deleted++;
        }

        NNAXFUF_Helpers::log_activity( $folder_id, 'permanent_delete', array( 'post_type' => NNAXFUF_Plugin::CPT_FOLDER ) );
        wp_delete_post( $folder_id, true );
        $deleted++;
    }

    private function trash_folder_non_recursive( $folder_id ) {
        $child_files = get_posts( array(
            'post_type'      => NNAXFUF_Plugin::CPT,
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'post_parent'    => $folder_id,
            'fields'         => 'ids',
        ) );
        foreach ( $child_files as $fid ) {
            wp_update_post( array( 'ID' => $fid, 'post_parent' => 0 ) );
        }

        $child_folders = get_posts( array(
            'post_type'      => NNAXFUF_Plugin::CPT_FOLDER,
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'post_parent'    => $folder_id,
            'fields'         => 'ids',
        ) );
        foreach ( $child_folders as $cfid ) {
            wp_update_post( array( 'ID' => $cfid, 'post_parent' => 0 ) );
        }

        wp_trash_post( $folder_id );
    }

    // phpcs:enable WordPress.Security.NonceVerification.Missing
    public function handle_download() {
        // Read-only download endpoint (no state-changing mutation from query args).
        // phpcs:disable WordPress.Security.NonceVerification.Recommended
        $zip_items = get_query_var( 'nnaxfuf_zip_items' );
        if ( ! empty( $zip_items ) ) {
            $zip_nonce = isset( $_GET['nnaxfuf_zip_nonce'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['nnaxfuf_zip_nonce'] ) ) : '';
            if ( '' === $zip_nonce || ! wp_verify_nonce( $zip_nonce, 'nnaxfuf_zip_download' ) ) {
                wp_die( esc_html__( 'Invalid ZIP request.', 'nnax-frontend-user-files' ) );
            }
            $this->handle_zip_download();
            return;
        }

        if ( isset( $_GET['nnaxfuf_zip'] ) && sanitize_text_field( wp_unslash( (string) $_GET['nnaxfuf_zip'] ) ) === '1' ) {
            $zip_nonce = isset( $_GET['nnaxfuf_zip_nonce'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['nnaxfuf_zip_nonce'] ) ) : '';
            if ( '' === $zip_nonce || ! wp_verify_nonce( $zip_nonce, 'nnaxfuf_zip_download' ) ) {
                wp_die( esc_html__( 'Invalid ZIP request.', 'nnax-frontend-user-files' ) );
            }
            $this->handle_zip_download();
            return;
        }

        $download_id = get_query_var( NNAXFUF_Plugin::DOWNLOAD_VAR );
        if ( empty( $download_id ) ) return;

        if ( ! is_user_logged_in() ) {
            wp_die( esc_html__( 'Please log in to load this file.', 'nnax-frontend-user-files' ) );
        }

        $download_nonce = isset( $_GET['nnaxfuf_download_nonce'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['nnaxfuf_download_nonce'] ) ) : '';
        $download_id = intval( $download_id );
        $post        = get_post( $download_id );
        if ( ! $post ) {
            wp_die( esc_html__( 'Invalid file.', 'nnax-frontend-user-files' ) );
        }

        if ( $post->post_type === NNAXFUF_Plugin::CPT_FOLDER ) {
            $zip_nonce = isset( $_GET['nnaxfuf_zip_nonce'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['nnaxfuf_zip_nonce'] ) ) : '';
            if ( '' === $zip_nonce || ! wp_verify_nonce( $zip_nonce, 'nnaxfuf_zip_download' ) ) {
                wp_die( esc_html__( 'Invalid ZIP request.', 'nnax-frontend-user-files' ) );
            }
        } elseif ( '' === $download_nonce || ! wp_verify_nonce( $download_nonce, 'nnaxfuf_download' ) ) {
            wp_die( esc_html__( 'Invalid download request.', 'nnax-frontend-user-files' ) );
        }

        $current_user_id = get_current_user_id();
        if ( ! $this->can_user_access_item( $current_user_id, $download_id ) ) {
            wp_die( esc_html__( 'No permission for this file.', 'nnax-frontend-user-files' ) );
        }

        if ( $post->post_type === NNAXFUF_Plugin::CPT_FOLDER ) {
            $this->serve_zip_download_for_items(
                $current_user_id,
                array(
                    array(
                        'type' => 'folder',
                        'id'   => $download_id,
                    ),
                ),
                NNAXFUF_Helpers::get_raw_item_title( $download_id )
            );
            return;
        }

        if ( $post->post_type !== NNAXFUF_Plugin::CPT ) {
            wp_die( esc_html__( 'Invalid file.', 'nnax-frontend-user-files' ) );
        }

        $file_path = (string) get_post_meta( $download_id, NNAXFUF_Plugin::META_FILEPATH, true );
        if ( empty( $file_path ) || ! file_exists( $file_path ) || ! $this->is_safe_upload_path( $file_path ) ) {
            wp_die( esc_html__( 'File not found.', 'nnax-frontend-user-files' ) );
        }
        $mode = get_query_var( 'nnaxfuf_mode' );
        $mode = $mode ? sanitize_key( (string) $mode ) : 'download';
        if ( ! in_array( $mode, array( 'download', 'inline' ), true ) ) {
            wp_die( esc_html__( 'Invalid download mode.', 'nnax-frontend-user-files' ) );
        }
        $disposition = ( $mode === 'inline' ) ? 'inline' : 'attachment';
        $filetype = wp_check_filetype( $file_path );
        $mime     = ! empty( $filetype['type'] ) ? $filetype['type'] : 'application/octet-stream';
        
        // Use post_title as friendly filename
        $friendly_name = NNAXFUF_Helpers::get_raw_item_title( $download_id );
        $ext           = pathinfo( $file_path, PATHINFO_EXTENSION );
        if ( ! empty( $ext ) && strtolower( substr( $friendly_name, -strlen( $ext ) - 1 ) ) !== '.' . strtolower( $ext ) ) {
            $friendly_name .= '.' . $ext;
        }

        if ( ob_get_length() ) ob_end_clean();
        header( 'Content-Description: File Transfer' );
        header( 'Content-Type: ' . $mime );
        header( 'Content-Disposition: ' . $disposition . '; filename="' . rawurlencode( $friendly_name ) . '"' );
        header( 'Content-Transfer-Encoding: binary' );
        header( 'Content-Length: ' . filesize( $file_path ) );
        header( 'Cache-Control: private' );
        header( 'Pragma: public' );
        header( 'Expires: 0' );
        global $wp_filesystem;
        if ( ! ( $wp_filesystem instanceof WP_Filesystem_Base ) ) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
            WP_Filesystem();
        }
        $contents = ( $wp_filesystem instanceof WP_Filesystem_Base ) ? $wp_filesystem->get_contents( $file_path ) : false;
        if ( false !== $contents ) {
            echo $contents; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Binary file response.
            if ( $mode !== 'inline' ) {
                NNAXFUF_Helpers::log_activity( $download_id, 'download', array( 'mode' => $mode ) );
            }
        }
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
        exit;
    }

    private function handle_zip_download() {
        // Read-only download endpoint (no state-changing mutation from query args).
        // phpcs:disable WordPress.Security.NonceVerification.Recommended
        if ( ! is_user_logged_in() ) {
            wp_die( esc_html__( 'Please log in to load files.', 'nnax-frontend-user-files' ) );
        }

        $zip_nonce = isset( $_GET['nnaxfuf_zip_nonce'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['nnaxfuf_zip_nonce'] ) ) : '';
        if ( '' === $zip_nonce || ! wp_verify_nonce( $zip_nonce, 'nnaxfuf_zip_download' ) ) {
            wp_die( esc_html__( 'Invalid ZIP request.', 'nnax-frontend-user-files' ) );
        }

        $items_raw = '';
        $items_encoded = get_query_var( 'nnaxfuf_zip_items' );
        if ( ! empty( $items_encoded ) ) {
            $items_raw = $this->base64url_decode( sanitize_text_field( (string) $items_encoded ) );
        }
        if ( $items_raw === '' ) {
            $items_raw = isset( $_GET['items'] ) ? sanitize_text_field( wp_unslash( $_GET['items'] ) ) : '';
        }
        $items = $this->parse_zip_items( $items_raw );
        if ( empty( $items ) ) {
            wp_die( esc_html__( 'No items selected for ZIP download.', 'nnax-frontend-user-files' ) );
        }

        $download_name = get_query_var( 'nnaxfuf_zip_name' );
        if ( ! empty( $download_name ) ) {
            $download_name = sanitize_file_name( rawurldecode( (string) $download_name ) );
        } else {
            $download_name = isset( $_GET['name'] ) ? sanitize_file_name( wp_unslash( $_GET['name'] ) ) : 'download';
        }
        if ( $download_name === '' ) {
            $download_name = 'download';
        }

        $this->serve_zip_download_for_items( get_current_user_id(), $items, $download_name );
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
    }

    private function parse_zip_items( $items_raw ) {
        // Accepted compact token formats:
        // - "file:123" / "f:123"
        // - "folder:456" / "d:456"
        $tokens = explode( ',', (string) $items_raw );
        $items  = array();

        foreach ( $tokens as $token ) {
            $token = trim( strtolower( $token ) );
            if ( $token === '' ) {
                continue;
            }

            $matches = array();
            if ( preg_match( '/^(file|f):(\d+)$/', $token, $matches ) ) {
                $items[] = array(
                    'type' => 'file',
                    'id'   => (int) $matches[2],
                );
                continue;
            }

            if ( preg_match( '/^(folder|d):(\d+)$/', $token, $matches ) ) {
                $items[] = array(
                    'type' => 'folder',
                    'id'   => (int) $matches[2],
                );
            }
        }

        $unique = array();
        $result = array();

        foreach ( $items as $item ) {
            if ( empty( $item['id'] ) ) {
                continue;
            }

            $key = $item['type'] . ':' . (int) $item['id'];
            // De-duplicate while preserving original order from request.
            if ( isset( $unique[ $key ] ) ) {
                continue;
            }

            $unique[ $key ] = true;
            $result[] = $item;
        }

        return $result;
    }

    private function serve_zip_download_for_items( $current_user_id, $items, $download_name = 'download' ) {
        if ( ! class_exists( 'ZipArchive' ) ) {
            wp_die( esc_html__( 'ZIP download is not available on this server (ZipArchive missing).', 'nnax-frontend-user-files' ) );
        }

        $zip_files = array(); // archive_path => disk_path
        $zip_dirs  = array(); // archive_path => true

        foreach ( $items as $item ) {
            $type = isset( $item['type'] ) ? (string) $item['type'] : '';
            $id   = isset( $item['id'] ) ? (int) $item['id'] : 0;
            if ( $id <= 0 ) {
                continue;
            }

            $this->collect_zip_entries_for_item( $current_user_id, $type, $id, '', $zip_files, $zip_dirs );
        }

        if ( empty( $zip_files ) && empty( $zip_dirs ) ) {
            wp_die( esc_html__( 'No accessible files found for ZIP download.', 'nnax-frontend-user-files' ) );
        }

        $log_details = array(
            'zip_name'        => (string) $download_name,
            'requested_items' => count( $items ),
            'zip_files'       => count( $zip_files ),
            'zip_dirs'        => count( $zip_dirs ),
        );
        foreach ( $items as $item ) {
            $item_id = isset( $item['id'] ) ? (int) $item['id'] : 0;
            if ( $item_id <= 0 ) {
                continue;
            }
            if ( ! $this->can_user_access_item( $current_user_id, $item_id ) ) {
                continue;
            }
            NNAXFUF_Helpers::log_activity( $item_id, 'zip_download', $log_details );
        }

        $temp_zip = '';
        if ( function_exists( 'wp_tempnam' ) ) {
            $temp_zip = wp_tempnam( 'nnaxfuf-zip-' );
        } else {
            if ( defined( 'ABSPATH' ) ) {
                $wp_file_api = ABSPATH . 'wp-admin/includes/file.php';
                if ( file_exists( $wp_file_api ) ) {
                    require_once $wp_file_api;
                }
            }

            if ( function_exists( 'wp_tempnam' ) ) {
                $temp_zip = wp_tempnam( 'nnaxfuf-zip-' );
            } else {
                $tmp_dir = function_exists( 'sys_get_temp_dir' ) ? sys_get_temp_dir() : '';
                if ( ! empty( $tmp_dir ) ) {
                    $temp_zip = tempnam( $tmp_dir, 'nnaxfuf-zip-' );
                }
            }
        }

        if ( empty( $temp_zip ) ) {
            wp_die( esc_html__( 'Could not create temporary ZIP file.', 'nnax-frontend-user-files' ) );
        }

        $zip = new ZipArchive();
        if ( $zip->open( $temp_zip, ZipArchive::CREATE | ZipArchive::OVERWRITE ) !== true ) {
            wp_delete_file( $temp_zip );
            wp_die( esc_html__( 'ZIP file could not be created.', 'nnax-frontend-user-files' ) );
        }

        foreach ( array_keys( $zip_dirs ) as $dir_path ) {
            $zip->addEmptyDir( rtrim( $dir_path, '/' ) );
        }

        foreach ( $zip_files as $archive_path => $disk_path ) {
            $zip->addFile( $disk_path, $archive_path );
        }

        $zip->close();

        if ( ! file_exists( $temp_zip ) ) {
            wp_die( esc_html__( 'ZIP file could not be created.', 'nnax-frontend-user-files' ) );
        }

        $friendly_name = sanitize_file_name( $download_name );
        if ( $friendly_name === '' ) {
            $friendly_name = 'download';
        }
        if ( strtolower( substr( $friendly_name, -4 ) ) !== '.zip' ) {
            $friendly_name .= '.zip';
        }

        if ( ob_get_length() ) {
            ob_end_clean();
        }
        header( 'Content-Description: File Transfer' );
        header( 'Content-Type: application/zip' );
        header( 'Content-Disposition: attachment; filename="' . rawurlencode( $friendly_name ) . '"' );
        header( 'Content-Transfer-Encoding: binary' );
        header( 'Content-Length: ' . filesize( $temp_zip ) );
        header( 'Cache-Control: private' );
        header( 'Pragma: public' );
        header( 'Expires: 0' );
        global $wp_filesystem;
        if ( ! ( $wp_filesystem instanceof WP_Filesystem_Base ) ) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
            WP_Filesystem();
        }
        $zip_contents = ( $wp_filesystem instanceof WP_Filesystem_Base ) ? $wp_filesystem->get_contents( $temp_zip ) : false;
        if ( false !== $zip_contents ) {
            echo $zip_contents; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Binary ZIP response.
        }
        wp_delete_file( $temp_zip );
        exit;
    }

    private function collect_zip_entries_for_item( $current_user_id, $item_type, $item_id, $base_path, &$zip_files, &$zip_dirs ) {
        $post = get_post( $item_id );
        if ( ! $post ) {
            return;
        }

        if ( ! $this->can_user_access_item( $current_user_id, $item_id ) ) {
            return;
        }

        if ( $item_type === 'file' && $post->post_type !== NNAXFUF_Plugin::CPT ) {
            return;
        }
        if ( $item_type === 'folder' && $post->post_type !== NNAXFUF_Plugin::CPT_FOLDER ) {
            return;
        }

        if ( $post->post_type === NNAXFUF_Plugin::CPT ) {
            $file_path = get_post_meta( $post->ID, NNAXFUF_Plugin::META_FILEPATH, true );
            if ( empty( $file_path ) || ! file_exists( $file_path ) ) {
                return;
            }

            $archive_name = $this->build_archive_file_name( $post->ID, $file_path );
            $archive_path = $this->build_unique_archive_path( $base_path . $archive_name, $zip_files );
            $zip_files[ $archive_path ] = $file_path;
            return;
        }

        $folder_name = $this->sanitize_zip_segment( NNAXFUF_Helpers::get_raw_item_title( $post->ID ), 'folder-' . $post->ID );
        $folder_path = $base_path . $folder_name . '/';
        $zip_dirs[ $folder_path ] = true;

        $files = get_posts( array(
            'post_type'      => NNAXFUF_Plugin::CPT,
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'post_parent'    => $post->ID,
            'fields'         => 'ids',
        ) );

        foreach ( $files as $file_id ) {
            $this->collect_zip_entries_for_item( $current_user_id, 'file', (int) $file_id, $folder_path, $zip_files, $zip_dirs );
        }

        $folders = get_posts( array(
            'post_type'      => NNAXFUF_Plugin::CPT_FOLDER,
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'post_parent'    => $post->ID,
            'fields'         => 'ids',
        ) );

        foreach ( $folders as $folder_id ) {
            $this->collect_zip_entries_for_item( $current_user_id, 'folder', (int) $folder_id, $folder_path, $zip_files, $zip_dirs );
        }
    }

    private function can_user_access_item( $current_user_id, $item_id ) {
        return NNAXFUF_Helpers::user_can_access_item( $current_user_id, $item_id );
    }

    private function is_safe_upload_path( $path ) {
        $path = (string) $path;
        if ( $path === '' ) {
            return false;
        }

        $resolved_path = realpath( $path );
        if ( $resolved_path === false ) {
            return false;
        }
        $resolved_path = wp_normalize_path( $resolved_path );

        $allowed_roots = array();

        $uploads = wp_upload_dir();
        if ( ! empty( $uploads['basedir'] ) ) {
            $upload_root = realpath( (string) $uploads['basedir'] );
            if ( $upload_root !== false ) {
                $allowed_roots[] = trailingslashit( wp_normalize_path( $upload_root ) );
            }
        }

        if ( defined( 'NNAXFUF_PLUGIN_PATH' ) ) {
            $plugin_storage_paths = array(
                trailingslashit( NNAXFUF_PLUGIN_PATH ) . 'uploads',
                trailingslashit( NNAXFUF_PLUGIN_PATH ) . 'storage',
            );

            foreach ( $plugin_storage_paths as $candidate_path ) {
                $resolved_candidate = realpath( $candidate_path );
                if ( $resolved_candidate === false ) {
                    continue;
                }
                $allowed_roots[] = trailingslashit( wp_normalize_path( $resolved_candidate ) );
            }
        }

        foreach ( array_unique( $allowed_roots ) as $root ) {
            if ( strpos( $resolved_path, $root ) === 0 ) {
                return true;
            }
        }

        return false;
    }

    private function delete_physical_file_if_present( $post_id ) {
        $post_id   = (int) $post_id;
        $file_path = (string) get_post_meta( $post_id, NNAXFUF_Plugin::META_FILEPATH, true );

        if ( $file_path === '' ) {
            return true;
        }

        if ( ! file_exists( $file_path ) ) {
            return true;
        }

        if ( ! $this->is_safe_upload_path( $file_path ) ) {
            return new WP_Error(
                'nnaxfuf_delete_unsafe_path',
                __( 'Physical file could not be deleted because the path is outside the allowed storage area.', 'nnax-frontend-user-files' )
            );
        }

        if ( ! function_exists( 'wp_delete_file' ) ) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        wp_delete_file( $file_path );
        clearstatcache( true, $file_path );

        if ( file_exists( $file_path ) ) {
            return new WP_Error(
                'nnaxfuf_delete_failed',
                __( 'Physical file could not be deleted.', 'nnax-frontend-user-files' )
            );
        }

        return true;
    }

    private function build_archive_file_name( $file_id, $file_path ) {
        $title = sanitize_file_name( NNAXFUF_Helpers::get_raw_item_title( $file_id ) );
        $ext   = strtolower( (string) pathinfo( $file_path, PATHINFO_EXTENSION ) );

        if ( $title === '' ) {
            $title = 'file-' . (int) $file_id;
        }

        if ( $ext !== '' && strtolower( (string) pathinfo( $title, PATHINFO_EXTENSION ) ) !== $ext ) {
            $title .= '.' . $ext;
        }

        return $title;
    }

    private function build_unique_archive_path( $archive_path, $zip_files ) {
        $archive_path = ltrim( $archive_path, '/' );
        if ( ! isset( $zip_files[ $archive_path ] ) ) {
            return $archive_path;
        }

        $dirname  = pathinfo( $archive_path, PATHINFO_DIRNAME );
        $basename = pathinfo( $archive_path, PATHINFO_FILENAME );
        $ext      = pathinfo( $archive_path, PATHINFO_EXTENSION );

        if ( $dirname === '.' ) {
            $dirname = '';
        } else {
            $dirname .= '/';
        }

        $index = 2;
        do {
            $candidate = $dirname . $basename . '-' . $index;
            if ( $ext !== '' ) {
                $candidate .= '.' . $ext;
            }
            $index++;
        } while ( isset( $zip_files[ $candidate ] ) );

        return $candidate;
    }

    private function sanitize_zip_segment( $value, $fallback ) {
        $value = sanitize_file_name( (string) $value );
        if ( $value === '' ) {
            return sanitize_file_name( (string) $fallback );
        }
        return $value;
    }
    private function sanitize_share_id( $id ) {
        if ( is_numeric( $id ) ) {
            return (int) $id;
        }
        $id = (string) $id;
        if ( strpos( $id, 'role:' ) === 0 ) {
            return sanitize_text_field( $id );
        }
        return false;
    }
}
