<?php
// phpcs:ignoreFile WordPress.Security.EscapeOutput.OutputNotEscaped
use SLB_API_Mobile\Helper\UserRoleHelper;

class SLN_PostType_Booking extends SLN_PostType_Abstract
{
    public $hide_phone;
    public $hide_email;

    /**
     * Per-request set of "Y-m-d" days whose persisted salon_cache entry must be
     * rebuilt, populated when a booking's date/time meta is written outside the
     * normal Builder flow (CSV importer, external migrators, programmatic
     * wp_insert_post / update_post_meta). The actual rebuild is deferred to
     * shutdown so it runs once per request, after ALL meta is written.
     *
     * @var array
     */
    private static $dirtyCacheDates = array();

    /** @var bool guard so the shutdown flush is registered only once per request */
    private static $cacheShutdownHooked = false;

    /** @var bool prevents recursive meta correction for misaligned booking times */
    private static $aligningBookingTimeMeta = false;

    public function init()
    {
        parent::init();

        if (is_admin()) {
            add_action('manage_'.$this->getPostType().'_posts_custom_column', array($this, 'manage_column'), 10, 2);
            add_filter('manage_'.$this->getPostType().'_posts_columns', array($this, 'manage_columns'));
            add_action('admin_footer-post.php', array($this, 'bulkAdminFooterEdit'));
            add_action('admin_footer-post-new.php', array($this, 'bulkAdminFooterNew'));
            add_filter('display_post_states', array($this, 'bulkPostStates'), 10, 2);
            add_action('admin_head-post-new.php', array($this, 'posttype_admin_css'));
            add_action('admin_head-post.php', array($this, 'posttype_admin_css'));
            add_action('restrict_manage_posts', array($this, 'restrict_manage_posts'), 10, 2);
            add_filter('parse_query', array($this, 'parse_query'));
            add_filter('pre_get_posts', array($this, 'pre_get_posts'));
            add_filter('post_row_actions', array($this, 'post_row_actions'), 10, 2);
            add_filter( 'months_dropdown_results', array($this,'months_dropdown_results'),10,2 );
            add_filter( 'posts_join', array($this, 'posts_join'),10,2 );
            add_filter( 'posts_where', array($this, 'posts_where') );
            add_filter( 'posts_distinct', array($this,'posts_distinct') );
            add_action('admin_enqueue_scripts',array($this,'admin_enqueue_scripts')   );
            add_filter( 'posts_search', [$this,'posts_search'], 10, 2 );
            add_filter('redirect_post_location', [$this, 'redirect_post_location'], 10, 2);
            // PERFORMANCE: Pre-load metadata for admin list (Issue #7)
            add_filter('posts_results', array($this, 'optimize_admin_list_metadata'), 10, 2);
        }
        $user_role_helper = new UserRoleHelper();
        $this->hide_email = $user_role_helper->is_hide_customer_phone();
        $this->hide_phone = $user_role_helper->is_hide_customer_email();

        // CRITICAL FIX: Clear cache on booking meta updates (PWA drag-resize, backend edits)
        // Not just status changes - any booking update should invalidate availability cache
        add_action('updated_post_meta', array($this, 'onBookingMetaUpdate'), 10, 4);
        add_action('added_post_meta',   array($this, 'onBookingMetaUpdate'), 10, 4);

        $this->registerPostStatus();
    }

    public function redirect_post_location($link, $post_id){
        $post_id = (int) $post_id;
        if ( $post_id < 1 || get_post_type( $post_id ) !== $this->getPostType() ) {
            return $link;
        }
        if ( isset( $_POST['_wp_http_referer'] ) ) {
            $qstring = parse_url( (string) wp_unslash( $_POST['_wp_http_referer'] ), PHP_URL_QUERY );
            $query   = array();
            if ( is_string( $qstring ) && $qstring !== '' ) {
                parse_str( $qstring, $query );
            }
            if ( isset( $query['mode'] ) && $query['mode'] === 'sln_editor' ) {
                $link = add_query_arg( array( 'mode' => 'sln_editor' ), $link );
            }
            if ( isset( $query['sln_editor_popup'] ) ) {
                $link = add_query_arg( array( 'sln_editor_popup' => $query['sln_editor_popup'] ), $link );
            }
            if ( isset( $_POST['post_status'] ) && $_POST['post_status'] === SLN_Enum_BookingStatus::PENDING ) {
                if ( isset( $_POST['_sln_booking_status'] ) ) {
                    if ( $_POST['_sln_booking_status'] === SLN_Enum_BookingStatus::CONFIRMED ) {
                        $link = add_query_arg( array( 'message' => '2' ), $link );
                    } elseif ( $_POST['_sln_booking_status'] === SLN_Enum_BookingStatus::CANCELED ) {
                        $link = add_query_arg( array( 'message' => '3' ), $link );
                    }
                }
            }
        }
        return $link;
    }

    public function admin_enqueue_scripts(){
        $screen = get_current_screen();
        if( $screen->id === 'edit-sln_booking' ){
			wp_enqueue_style('salon-admin-css', SLN_PLUGIN_URL.'/css/admin.css', array(), SLN_Action_InitScripts::ASSETS_VERSION, 'all');

	    //Rtl support
	    wp_style_add_data( 'salon-admin-css', 'rtl', 'replace' );

            SLN_Action_InitScripts::preloadScripts();

            SLN_Action_InitScripts::enqueueSelect2();
            SLN_Action_InitScripts::enqueueTwitterBootstrap(true);
            SLN_Action_InitScripts::enqueueAdmin();
        }
    }


    function posts_search( $search, $wp_query ) {
        // Bail if we are not in the admin area
        if ( ! is_admin() ) {
            return $search;
        }

        // Bail if this is not the search query.
        if ( ! $wp_query->is_main_query() && ! $wp_query->is_search() ) {
            return $search;
        }

        // Get the value that is being searched.
        $search_string = get_query_var( 's' );

        // Bail if the search string is not an integer.
        if ( ! filter_var( $search_string, FILTER_VALIDATE_INT ) ) {
            return $search;
        }

	global $wpdb;

        // This appears to be a search using a post ID.
        // Return modified posts_search clause.
        return "AND " . $wpdb->posts . ".ID = '" . intval( $search_string )  . "'";
    }

    public function pre_get_posts($query){
        global $pagenow;

        if (
            isset($_GET['post_type']) && $_GET['post_type'] === $this->getPostType()
            && is_admin()
            && $pagenow=='edit.php'
            && (is_array($query->get('post_type')) ? in_array($this->getPostType(), $query->get('post_type')) : $query->get('post_type') === $this->getPostType())
        ) {
            $query->set('m', null);

    	    if ( in_array(SLN_Plugin::USER_ROLE_STAFF,  wp_get_current_user()->roles) || in_array(SLN_Plugin::USER_ROLE_WORKER,  wp_get_current_user()->roles) ) {

        		$assistantsIDs = array();

        		$repo	    = $this->getPlugin()->getRepository(SLN_Plugin::POST_TYPE_ATTENDANT);
        		$attendants = $repo->getAll();

        		foreach ($attendants as $attendant) {
        		    if ($attendant->getMeta('staff_member_id') == get_current_user_id() && $attendant->getIsStaffMemberAssignedToBookingsOnly()) {
        			$assistantsIDs[] = $attendant->getId();
        		    }
        		}

        		if ( ! empty( $assistantsIDs ) ) {
                            $meta_query   = $query->get('meta_query') ? $query->get('meta_query') : array();
                            $meta_query[] = array(
                                'key'   => '_sln_booking_services',
                                'value' => implode('|', array_map(function ($v) {
                                    return sprintf('"attendant";i:%s;', $v);
                                }, $assistantsIDs)),
                                'compare' => 'REGEXP',
                            );
        		    $query->set('meta_query', $meta_query);
        		}
    	    }
        }
        return $query;
    }
    
    /**
     * Optimize admin list performance by pre-loading all metadata
     * 
     * With 36,000+ bookings, loading metadata individually for each row
     * causes 100-200 queries per page. This pre-loads all metadata in
     * a single query, making subsequent get_post_meta() calls instant.
     * 
     * @param array $posts
     * @param WP_Query $query
     * @return array
     */
    public function optimize_admin_list_metadata($posts, $query) {
        // Only optimize admin booking list page
        if (!is_admin() || !$query->is_main_query()) {
            return $posts;
        }
        
        global $pagenow;
        if ($pagenow !== 'edit.php' || !isset($_GET['post_type']) || $_GET['post_type'] !== $this->getPostType()) {
            return $posts;
        }
        
        if (empty($posts)) {
            return $posts;
        }
        
        // Pre-load ALL metadata for these posts in a single query
        $post_ids = wp_list_pluck($posts, 'ID');
        update_meta_cache('post', $post_ids);
        
        if (SLN_Plugin::isDebugEnabled()) {
            SLN_Plugin::addLog(sprintf(
                '[Admin Performance] Pre-loaded metadata for %d bookings in admin list',
                count($post_ids)
            ));
        }
        
        return $posts;
    }
    public function posts_join ( $join, $wp_query ) {
        global $pagenow, $wpdb;
        // I want the filter only when performing a search on edit page of Custom Post Type named "segnalazioni".

        if ( is_admin() && 'edit.php' === $pagenow  && ! empty($_GET['post_type'] )  && $this->getPostType() === $_GET['post_type'] && ! empty( $_GET['s'] ) && ( isset($wp_query->query_vars['post_type'] )&& $wp_query->query_vars['post_type'] === $this->getPostType() ) ) {
            $join .= ' LEFT JOIN ' . $wpdb->postmeta . ' sln_mt1 ON ' . $wpdb->posts . '.ID = sln_mt1.post_id AND sln_mt1.meta_key = "_sln_booking_firstname" ';
            $join .= " LEFT JOIN {$wpdb->postmeta} sln_mt2 ON {$wpdb->posts}.ID = sln_mt2.post_id AND sln_mt2.meta_key = '_sln_booking_lastname' ";
            $join .= " LEFT JOIN {$wpdb->postmeta} sln_mt3 ON {$wpdb->posts}.ID = sln_mt3.post_id ";
        }
        return $join;
    }

    public function posts_where( $where ) {
        global $pagenow, $wpdb;

        // I want the filter only when performing a search on edit page of Custom Post Type named "segnalazioni".
        if ( is_admin() && 'edit.php' === $pagenow && ! empty($_GET['post_type'] ) && $this->getPostType() === $_GET['post_type'] && ! empty( $_GET['s'] ) ) {
            $where = preg_replace(
                "/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
                " (" . $wpdb->posts . ".post_title LIKE $1) OR (sln_mt3.meta_value LIKE $1) OR (CONCAT(sln_mt1.meta_value, sln_mt2.meta_value) LIKE $1) ", $where );
        }
        return $where;
    }
    public function posts_distinct( $where ){
        global $pagenow, $wpdb;

        if ( is_admin() && $pagenow=='edit.php' && ! empty($_GET['post_type'] ) && $_GET['post_type']==$this->getPostType() && !empty($_GET['s'])) {
        return "DISTINCT";

        }
        return $where;
    }

    public  function months_dropdown_results($months, $post_type){

        if($post_type !== $this->getPostType() ) return $months;
        global $wpdb;
        $extra_checks = "AND post_status != 'auto-draft'";
            if ( ! isset( $_GET['post_status'] ) || 'trash' !== $_GET['post_status'] ) {
                $extra_checks .= " AND post_status != 'trash'";
            } elseif ( isset( $_GET['post_status'] ) ) {
                $extra_checks = $wpdb->prepare( ' AND post_status = %s', sanitize_text_field(wp_unslash($_GET['post_status'])) );
            }
                $months = $wpdb->get_results(
                $wpdb->prepare(
                    "
                SELECT DISTINCT YEAR( meta_value ) AS year, MONTH( meta_value) AS month

                FROM $wpdb->postmeta as pt
                LEFT JOIN $wpdb->posts as p
                ON pt.post_id = p.ID
                WHERE pt.meta_key = '_sln_booking_date' AND
                post_type = %s
                $extra_checks
                ORDER BY meta_value DESC
            ", $post_type
                )
            );

            return $months;
    }

    public function post_row_actions($actions, $post) {
        if ($post && $post->post_type === SLN_Plugin::POST_TYPE_BOOKING) {
            unset($actions['inline hide-if-no-js']);
            if(defined('SLN_VERSION_PAY') && SLN_VERSION_PAY){
                if (current_user_can('edit_' . SLN_Plugin::POST_TYPE_BOOKING. 's')) {

                    $actions['clone'] = '<a href="'.$this->getDuplicateActionPostLink($post->ID).'" title="'
                            . esc_attr__("Duplicate this item", 'salon-booking-system')
                            . '">' .  esc_html__('Duplicate', 'salon-booking-system') . '</a>';

                    if (class_exists('\SalonSOAP\Addon') && get_current_screen()->base == 'salon_page_salon-customers') {
                        $actions['soap_edit'] = '<a href="' . $this->getSOAPNotesActionPostLink($post->ID) . '" title="'
                            . esc_attr__("Edit SOAP notes", 'sln-soap')
                            . '">' . esc_html__('SOAP&nbsp;notes', 'sln-soap') . '</a>';
                    }
                }
            }
        }
        return $actions;
    }

    public function getSOAPNotesActionPostLink($id = 0)
    {
        return admin_url("post.php?action=edit&mode=sln_editor&post=$id");
    }

    public function getDuplicateActionPostLink( $id = 0, $context = 'display') {

        $action_name = 'duplicate';

        if ( 'display' == $context ) {
            $action = '?post_type='. $this->getpostType().  '&action='.$action_name.'&amp;post='.$id;
        } else {
            $action = '?post_type='. $this->getpostType(). '&action='.$action_name.'&post='.$id;
        }

        return wp_nonce_url(admin_url( "post-new.php". $action ), 'sln_duplicate-post_' . $id);
    }

    public function manage_columns($columns)
    {
        $ret = array(
            'cb' => $columns['cb'],
            'ID' => __('Booking ID'),
            'booking_date' => __('Booking Date', 'salon-booking-system'),
            'booking_status' => __('Status', 'salon-booking-system'),
            'myauthor' => __('User name', 'salon-booking-system'),
            'booking_attendant' => __('Attendant', 'salon-booking-system'),
            'booking_duration' => __('Duration', 'salon-booking-system'),
            'booking_price' => __('Booking Price', 'salon-booking-system'),
            'booking_services' => __('Booking Services', 'salon-booking-system'),
            'origin_source' => __('Origin', 'salon-booking-system'),
            'booking_actions' => __('Actions', 'salon-booking-system'),
        );

        if ($this->getPlugin()->getSettings()->get('sms_remind') || $this->getPlugin()->getSettings()->get('email_remind')) {
            $ret['booking_reminder'] = __('Reminders', 'salon-booking-system');
        }

        return $ret;
    }

    public function manage_column($column, $post_id)
    {
        $obj = $this->getPlugin()->createBooking($post_id);
        if($obj->getStatus() == SLN_Enum_BookingStatus::DRAFT){
            return;
        }

	    $customer = $obj->getCustomer();

        switch ($column) {
            case 'ID' :
                echo edit_post_link($post_id, '<p>', '</p>', $post_id);
                break;
            case 'myauthor':
		        echo '<a href="'.esc_url(add_query_arg(array('page' => SLN_Admin_Customers::PAGE, 'id' => $customer ? $customer->getId() : null), admin_url('admin.php'))).'">'. esc_html($obj->getDisplayName()) .'</a>';
                if($obj->getPhone()){
                    if(!$this->hide_phone){
                        echo '<div><a href="' . esc_html('https://wa.me/'. $obj->getSmsPrefix() . $obj->getPhone()) .'" target="_blank">Tel. '. esc_html($obj->getSmsPrefix() . $obj->getPhone()) .'</a></div>';
                    }
                }
                break;
            case 'booking_status' :
                $status = SLN_Enum_BookingStatus::getLabel(get_post_status($post_id));
                $color  = SLN_Enum_BookingStatus::getRealColor(get_post_status($post_id));
                $weight = 'normal';
                if (get_post_status($post_id) == SLN_Enum_BookingStatus::CONFIRMED || get_post_status($post_id) == SLN_Enum_BookingStatus::PAID) $weight = 'bold';
                if (get_post_status($post_id) == SLN_Enum_BookingStatus::TRASH) {
                    $status = __('Trash', 'salon-booking-system');
                    $color  = '';
                }
                echo '<div style="width:14px !important; height:14px; border-radius:14px; border:2px solid '.$color.'; float:left; margin-top:2px;"></div> &nbsp;<span style="color:'.$color.'; font-weight:'.$weight.';">' . esc_html($status) . '</span>';
                break;
            case 'booking_duration':
                $duration = SLN_Func::convertToHoursMins(SLN_Func::getMinutesFromDuration($obj->getDuration()));
                echo esc_html($duration);
                break;
            case 'booking_date':
                $startsAt = $obj->getStartsAt();
                if (!SLN_Func::isTimeAlignedToInterval($startsAt->format('H:i'))) {
                    $startsAt = SLN_Func::alignDateTimeToInterval(
                        $startsAt,
                        (int) $this->getPlugin()->getSettings()->getInterval()
                    );
                }
                echo esc_html($this->getPlugin()->format()->datetime($startsAt));
                echo '<span style="color:#969494;font-size: 10px;"><br>'.get_the_date('j F Y');
                echo '<br>'.get_the_time('G:i').'</span>';
                break;
            case 'booking_price' :
                echo $this->getPlugin()->format()->money(get_post_meta($post_id, '_sln_booking_amount', true)); # not use esc_xxxx. When free money return html
                if (get_post_status($post_id) == SLN_Enum_BookingStatus::PAID && $deposit = get_post_meta(
                        $post_id,
                        '_sln_booking_deposit',
                        true
                    )
                ) {
                    echo '(deposit '. esc_html($this->getPlugin()->format()->money($deposit)) .')';
                }
                break;
            case 'booking_services' :
                $name_services = array();
                $countServices = $obj->getCountServices();
                foreach ($obj->getServices() as $helper) {
                    $serviceName = esc_html($helper->getName());
                    $serviceId = $helper->getId();
                    
                    // Add quantity in parentheses if > 1
                    if (isset($countServices[$serviceId]) && $countServices[$serviceId] > 1) {
                        $serviceName .= ' (' . $countServices[$serviceId] . ')';
                    }
                    
                    $name_services[] = $serviceName;
                }
                echo implode(', ', $name_services);
                break;
            case 'booking_attendant' :
                $theId = $obj->getAttendantsIds(true);
                if (count($theId) > 0):
                    $attendantId = array_values($theId)[0];
                    echo get_the_post_thumbnail($attendantId, array(20, 20), array('valign' => 'middle'));
                    echo ' &nbsp;';
                endif;
                echo esc_html($obj->getAttendantsString());
                break;
            case 'booking_review' :
                $comments = get_comments("post_id=$post_id&type=sln_review");
                $comment = isset($comments[0]) ? $comments[0] : null;

                echo '<input type="hidden" name="sln-rating" value="'. esc_attr($obj->getRating()) .'">
                        <div class="rating" style="display: none;"></div>';

                if (!empty($comment)) {
                    echo '<a href="'.esc_url(add_query_arg(array('p' => $post_id), admin_url('edit-comments.php'))).'#salon-review"
                            class="overflow-dots">'. esc_html($comment->comment_content) .'</a>';
                }

                break;
            case 'booking_reminder' :

                $statuses = array(SLN_Enum_BookingStatus::PAID, SLN_Enum_BookingStatus::CONFIRMED, SLN_Enum_BookingStatus::PAY_LATER);

                if (in_array($obj->getStatus(), $statuses)) {

                    echo '<div>';

                    if ($this->getPlugin()->getSettings()->get('email_remind')) {

                        $title = '';
                        $class = '';

                        $email_remind = $obj->getMeta('email_remind');
                        if(!empty($email_remind) && $email_remind){
                            $email_remind_utc_time = $obj->getMeta('email_remind_utc_time');

                            if ($email_remind_utc_time) {
                                $datetime = (new SLN_DateTime($obj->getMeta('email_remind_utc_time'), new DateTimeZone('UTC')))->setTimezone(SLN_DateTime::getWpTimezone());
                            } else {
                                $interval = $this->getPlugin()->getSettings()->get('email_remind_interval');
                                $datetime = $obj->getStartsAt()->modify('-' . str_replace('+', '', $interval));
                            }
                            $title = sprintf(
                                // translators: %s will be replaced by the datetime
                                __('Email correctly sent on %s', 'salon-booking-system'),
                                $this->getPlugin()->format()->date($datetime) . '/' . $this->getPlugin()->format()->time($datetime)
                            );
                            $class = 'sln-booking-reminder-success';
                        }else{
                            $remind_error = $obj->getMeta('email_remind_error');

                            if (empty ($remind_error)) {
                                $interval = $this->getPlugin()->getSettings()->get('email_remind_interval');
                                $datetime = $obj->getStartsAt()->modify('-' . str_replace('+', '', $interval));
	                            $now = new SLN_DateTime();
                                // don't show if time of remind was passed, and we have no info
                                
                                $title = sprintf(
                                    // translators: %s will be replaced by the datetime
                                    __('Email will be sent at %s', 'salon-booking-system'),
                                    $this->getPlugin()->format()->date($datetime) . '/' . $this->getPlugin()->format()->time($datetime)
                                );
                                $class = 'sln-booking-reminder-await';
                            } else {
                                $title = __('Email failed', 'salon-booking-system') . ' ' .$remind_error;
                                $class = 'sln-booking-reminder-error';
                            }
                        } 

                        echo sprintf(
                            '<span class="sln-booking-reminder-email sln-booking-reminder %s" title="%s"></span>',
                            $class,
                            $title
                        );
                    }

                    if ($this->getPlugin()->getSettings()->get('sms_remind')) {

                        $title = '';
                        $class = '';

                        $sms_remind = $obj->getMeta('sms_remind');

                        if (!empty($sms_remind)) {
                            $sms_remind_utc_time = $obj->getMeta('sms_remind_utc_time');

                            if ($sms_remind_utc_time) {
                                $datetime = (new SLN_DateTime($obj->getMeta('sms_remind_utc_time'), new DateTimeZone('UTC')))->setTimezone(SLN_DateTime::getWpTimezone());
                            } else {
                                $interval = $this->getPlugin()->getSettings()->get('sms_remind_interval');
                                $datetime = $obj->getStartsAt()->modify('-' . str_replace('+', '', $interval));
                            }
                            $title = sprintf(
                                // translators: %s will be replaced by the datetime
                                __('Sms correctly sent on %s', 'salon-booking-system'),
                                $this->getPlugin()->format()->date($datetime) . '/' . $this->getPlugin()->format()->time($datetime)
                            );
                            $class = 'sln-booking-reminder-success';

                            
                        } else {
                            $remind_error = $obj->getMeta('sms_remind_error');

                            if (empty ($remind_error)) {
                                $interval = $this->getPlugin()->getSettings()->get('sms_remind_interval');
                                $datetime = $obj->getStartsAt()->modify('-' . str_replace('+', '', $interval));
	                            $now = new SLN_DateTime();
	                            // don't show if time of remind was passed, and we have no info
                                if ($now > $datetime) {
                                    $title = __('Sms failed', 'salon-booking-system') . ': ' . 'time has passed';
                                    $class = 'sln-booking-reminder-error';
                                } else {
                                    $title = sprintf(
                                        // translators: %s will be replaced by the datetime
                                        __('Sms will be sent at %s', 'salon-booking-system'),
                                        $this->getPlugin()->format()->date($datetime) . '/' . $this->getPlugin()->format()->time($datetime)
                                    );
                                    $class = 'sln-booking-reminder-await';
                                }

                            } else {
                                $title = __('Sms failed', 'salon-booking-system') . ' ' . $remind_error;
                                $class = 'sln-booking-reminder-error';
                            }
                        }

                        echo sprintf(
                            '<span class="sln-booking-reminder-sms sln-booking-reminder %s" title="%s"></span>',
                            $class,
                            $title
                        );
                    }

                    echo '</div>';

                }

                break;

                case 'booking_actions' :
                    if ( $this->getPlugin()->getSettings()->get('confirmation') && $obj->getStatus(
) == SLN_Enum_BookingStatus::PENDING) {
                        $title = !defined("SLN_VERSION_PAY") ? __('Switch to PRO to unlock the "Quick approval"', 'salon-booking-system') : '';
                        echo '<div class="sln-booking-confirmation '. ( !defined("SLN_VERSION_PAY") ? 'sln-booking-confirmation-disabled' : '') .'">';
                            echo '<div class="sln-booking-confirmation-alert-loading"></div>';
                            echo '<div class="sln-booking-confirmation-tooltip"><a href="' . esc_url( defined('SLN_PRICING_URL') ? SLN_PRICING_URL : 'https://www.salonbookingsystem.com/plugin-pricing-2/' ) . '" target="_blank">'. esc_html($title) .'</a></div>';
                            echo '<div class="sln-booking-confirmation-success" data-status="' . SLN_Enum_BookingStatus::CONFIRMED . '" data-booking-id="' . $obj->getId() . '" title="' . ( esc_attr($title) ? '' : esc_html__('Accept', 'salon-booking-system') ) . '" data-class="success"></div>';
                            echo '<div class="sln-booking-confirmation-error" data-status="' . SLN_Enum_BookingStatus::CANCELED . '" data-booking-id="' . $obj->getId() . '" title="' . ( esc_attr($title) ? '' : esc_html__('Refuse', 'salon-booking-system') ) . '" data-class="danger"></div>';
                        echo '</div>';
				    }

                break;
                case 'origin_source':
                    $origin = $obj->getOrigin() ? $obj->getOrigin() : 'Direct';
                    $displayLabel = SLN_Enum_BookingOrigin::getLabel($origin);
                    
                    // Add WordPress username for Back-end and Web app origins
                    // Use created_by_user_id (admin who created) not post_author (customer)
                    if ($displayLabel === 'Back-end' || $displayLabel === 'Web app') {
                        $creator_user_id = $obj->getMeta('created_by_user_id');
                        
                        // Fallback: If no creator ID, check if post_author is an admin/staff user
                        if (!$creator_user_id) {
                            $post = get_post($post_id);
                            if ($post && $post->post_author) {
                                $post_author_user = get_userdata($post->post_author);
                                if ($post_author_user) {
                                    // Check if post_author is admin/staff (not just customer)
                                    $user_roles = $post_author_user->roles;
                                    if (array_intersect($user_roles, ['administrator', 'shop_manager', 'sln_staff', 'sln_worker'])) {
                                        $creator_user_id = $post->post_author;
                                    }
                                }
                            }
                        }
                        
                        // Display username if we found a creator
                        if ($creator_user_id) {
                            $user = get_userdata($creator_user_id);
                            if ($user) {
                                $username = $user->display_name ?: $user->user_login;
                                $displayLabel .= ' <span style="font-size: 0.85em; color: #637491;">(' . esc_html($username) . ')</span>';
                            }
                        }
                    }
                    
                    echo $displayLabel;
                    
                    // Show last edit information if booking was edited (same pattern as booking_date column)
                    $last_editor_id = $obj->getLastEditedByUserId();
                    $last_edited_at = $obj->getLastEditedAt();
                    
                    if ($last_editor_id && $last_edited_at) {
                        $editor = get_userdata($last_editor_id);
                        if ($editor) {
                            $editor_name = $editor->display_name ?: $editor->user_login;
                            $time_ago = human_time_diff(strtotime($last_edited_at), current_time('timestamp'));
                            echo '<span style="color:#999;font-size:11px;font-style:italic;"><br>';
                            echo sprintf(__('Edited by %s (%s ago)', 'salon-booking-system'), esc_html($editor_name), $time_ago);
                            echo '</span>';
                        }
                    }

                break;
        }
    }

    public function enter_title_here($title, $post)
    {
        if ($post && $this->getPostType() === $post->post_type) {
            $title = __('Enter booking name', 'salon-booking-system');
        }

        return $title;
    }

    public function updated_messages($messages)
    {
        global $post, $post_ID;

        $messages[$this->getPostType()] = array(
            0 => '', // Unused. Messages start at index 1.
            1 => sprintf(
                __('Booking updated.', 'salon-booking-system')
            ),
            2 => __('Booking confirmed', 'salon-booking-system'),
            3 => __('Booking cancelled', 'salon-booking-system'),
            4 => __('Booking updated.', 'salon-booking-system'),
            5 => isset($_GET['revision']) ? sprintf(
                // translators: %s will be replaced by the revision title
                __('Booking restored to revision from %s', 'salon-booking-system'),
                wp_post_revision_title((int)$_GET['revision'], false)
            ) : false,
            6 => sprintf(
                __('Booking published.', 'salon-booking-system')
            ),
            7 => __('Booking saved.', 'salon-booking-system'),
            8 => sprintf(
                __('Booking submitted.', 'salon-booking-system')
            ),
            9 => sprintf(
                // translators: %1$s will be replaced by the date
                __(
                    'Booking scheduled for: <strong>%1$s</strong>.',
                    'salon-booking-system'
                ),
                SLN_TimeFunc::translateDate(__('M j, Y @ G:i', 'salon-booking-system'), SLN_TimeFunc::getPostTimestamp($post))
            ),
            10 => sprintf(
                __('Booking draft updated.', 'salon-booking-system')
            ),
        );


        return $messages;
    }

    protected function getPostTypeArgs()
    {
        return array(
            'description' => __('This is where bookings are stored.', 'salon-booking-system'),
            'public' => true,
            'show_ui' => true,
            'map_meta_cap' => true,
            'publicly_queryable' => false,
            'exclude_from_search' => true,
            'show_in_menu' => 'salon',
            'hierarchical' => false,
            'show_in_nav_menus' => true,
            'rewrite' => false,
            'query_var' => false,
            'supports' => array('title', 'comments', 'custom-fields'),
            'has_archive' => false,
            'rewrite' => false,
            'supports' => array(
                'revisions',
            ),
            'labels' => array(
                'name' => __('Bookings', 'salon-booking-system'),
                'singular_name' => __('Booking', 'salon-booking-system'),
                'menu_name' => __('Salon', 'salon-booking-system'),
                'name_admin_bar' => __('Salon Booking', 'salon-booking-system'),
                'all_items' => __('Bookings', 'salon-booking-system'),
                'add_new' => __('Add Booking', 'salon-booking-system'),
                'add_new_item' => __('Add New Booking', 'salon-booking-system'),
                'edit_item' => __('Edit Booking', 'salon-booking-system'),
                'new_item' => __('New Booking', 'salon-booking-system'),
                'view_item' => __('View Booking', 'salon-booking-system'),
                'search_items' => __('Search Bookings', 'salon-booking-system'),
                'not_found' => __('No bookings found', 'salon-booking-system'),
                'not_found_in_trash' => __('No bookings found in trash', 'salon-booking-system'),
                'archive_title' => __('Booking Archive', 'salon-booking-system'),
            ),
            'capability_type' => array($this->getPostType(), $this->getPostType().'s'),
            'map_meta_cap' => true,
            'capabilities' => array(
                'create_posts' => 'create_' . $this->getPostType().'s',
            ),
        );
    }

    private function registerPostStatus()
    {
        foreach (SLN_Enum_BookingStatus::toArray() as $k => $v) {
            register_post_status(
                $k,
                array(
                    'label' => $v,
                    'public' => true,
                    'exclude_from_search' => false,
                    'show_in_admin_all_list' => true,
                    'show_in_admin_status_list' => true,
                    'label_count' => _n_noop(
                        $v.' <span class="count">(%s)</span>',
                        $v.' <span class="count">(%s)</span>'
                    ),
                )
            );
        }
        add_action('transition_post_status', array($this, 'transitionPostStatus'), 10, 3);
        add_action('sln.booking_builder.new_booking_ready', array($this, 'clearNewBookingCaches'), 10, 1);
        add_action('sln.booking.setStatus', array($this, 'onBookingSetStatus'), 10, 3);
    }

    public function transitionPostStatus($new_status, $old_status, $post)
    {
        if (
	    $post
	    && $post->post_type == SLN_Plugin::POST_TYPE_BOOKING
            && $old_status != $new_status
        ) {
            // CRITICAL FIX: For NEW posts, transition_post_status fires during wp_insert_post,
            // BEFORE Builder has saved services/date/time/amount meta. Sending email here would
            // produce notifications with blank services, wrong date, and "free" price.
            // Builder::create() handles send/action/cache for new posts after meta is saved.
            // Only skip for brand-new post creation (new → auto-draft). Do NOT skip when
            // transitioning FROM auto-draft (e.g. user confirming front-end booking).
            $is_new_post = ($old_status === 'new');
            if ($is_new_post) {
                return;
            }

            $p = $this->getPlugin();
            $booking = $p->createBooking($post);
            $p->messages()->sendByStatus($booking, $new_status);
            do_action('sln.booking_builder.create.booking_created', $booking);
            
            if ($booking && $booking->getDate()) {
                SLN_Helper_Availability_Cache::clearDateCache($booking->getDate());
                $this->clearIntervalsCache($booking);

                // When a booking is cancelled/errored via any WP-native path (Quick Edit,
                // wp_update_post, WP-CLI, etc.) the per-request booking cache and the
                // persisted salon_cache must be refreshed so the freed slot is immediately
                // visible. Clear the repo cache first so processBooking() re-queries the DB.
                if (in_array($new_status, SLN_Enum_BookingStatus::$noTimeStatuses)) {
                    $repo = $p->getRepository(SLN_Plugin::POST_TYPE_BOOKING);
                    $repo->clearBookingCache();
                    $p->getBookingCache()->processBooking($booking);
                }
            }
        }
    }

    /**
     * Clear availability and intervals cache for new bookings.
     * Called from Builder after meta is saved (new posts skip transitionPostStatus).
     */
    public function clearNewBookingCaches($booking)
    {
        if ($booking && $booking->getDate()) {
            SLN_Helper_Availability_Cache::clearDateCache($booking->getDate());
            $this->clearIntervalsCache($booking);

            // Invalidate the one-click forecast cache for this customer so
            // the next forecast page load rebuilds suggestions from scratch.
            $user_id = $booking->getUserId();
            if ($user_id) {
                $shop_id = (int) $booking->getMeta('shop');
                SLN_Helper_BookingForecaster::bustCache($user_id, $shop_id);
            }
        }
    }

    /**
     * Fired on sln.booking.setStatus for every status change, including the
     * direct-DB fallback path that bypasses transition_post_status.
     * Guarantees caches are always flushed when a booking is cancelled/errored
     * so the freed slot is immediately visible to the next availability check.
     *
     * @param SLN_Wrapper_Booking $booking
     * @param string              $oldStatus
     * @param string              $newStatus
     */
    public function onBookingSetStatus($booking, $oldStatus, $newStatus)
    {
        if (!in_array($newStatus, SLN_Enum_BookingStatus::$noTimeStatuses)) {
            return;
        }

        if (!($booking instanceof SLN_Wrapper_Booking) || !$booking->getDate()) {
            return;
        }

        // Clear per-date in-memory availability cache.
        SLN_Helper_Availability_Cache::clearDateCache($booking->getDate());

        // Clear interval step transients (_transient_sln_*).
        $this->clearIntervalsCache($booking);

        // MUST clear the per-request booking list BEFORE rebuilding salon_cache.
        // processBooking() calls getForAvailabilityBookings() internally; if $bookingCache
        // was populated earlier in this request (before the cancellation), it still holds
        // the pre-cancel post objects and processBooking() would re-mark the day as 'full'.
        $repo = $this->getPlugin()->getRepository(SLN_Plugin::POST_TYPE_BOOKING);
        $repo->clearBookingCache();

        // Rebuild the persisted day cache (salon_cache option) against fresh DB data.
        $this->getPlugin()->getBookingCache()->processBooking($booking);
    }

    /**
     * Clear intervals cache when bookings change
     * 
     * The intervals cache stores pre-calculated availability data for the date step.
     * When a booking is created/updated/deleted, this cache must be cleared to show
     * accurate availability.
     * 
     * @param SLN_Wrapper_Booking $booking
     */
    private function clearIntervalsCache($booking) {
        global $wpdb;
        
        // Clear all intervals transients
        // These are cached in DateStep::getViewData() with keys like 'sln_[hash]'
        $deleted = $wpdb->query(
            "DELETE FROM {$wpdb->options} 
             WHERE option_name LIKE '_transient_sln_%'"
        );
        
        if (SLN_Plugin::isDebugEnabled()) {
            SLN_Plugin::addLog(sprintf(
                '[DateStep Performance] Cleared %d intervals cache entries due to booking #%d status change',
                $deleted,
                $booking->getId()
            ));
        }
    }

    /**
     * Clear availability cache when booking metadata is updated
     *
     * CRITICAL FIX: PWA drag-to-resize and other booking updates don't change status,
     * but DO change services/duration. We must clear cache on ANY booking update,
     * not just status changes.
     *
     * This fixes the issue where:
     * - PWA user drags to resize booking (30 min → 90 min)
     * - Booking saved correctly in database
     * - But availability cache NOT cleared (status unchanged: confirmed → confirmed)
     * - Front-end users see stale availability and get booking errors
     *
     * Reference: PWA_DRAG_RESIZE_CACHE_INVALIDATION_FIX.md
     *
     * @param int    $meta_id    ID of updated metadata entry
     * @param int    $post_id    Post ID (booking ID)
     * @param string $meta_key   Meta key being updated
     * @param mixed  $meta_value New meta value
     */
    public function onBookingMetaUpdate($meta_id, $post_id, $meta_key, $meta_value) {
        // Only process booking post type
        if (get_post_type($post_id) !== SLN_Plugin::POST_TYPE_BOOKING) {
            return;
        }

        if ($meta_key === '_sln_booking_time' && !self::$aligningBookingTimeMeta) {
            $aligned = SLN_Func::alignTimeToInterval($meta_value);
            if ($aligned && $aligned !== $meta_value) {
                self::$aligningBookingTimeMeta = true;
                update_post_meta($post_id, '_sln_booking_time', $aligned);
                self::$aligningBookingTimeMeta = false;

                if (SLN_Plugin::isDebugEnabled()) {
                    SLN_Plugin::addLog(sprintf(
                        '[Booking Time] Corrected misaligned time for booking #%d: %s -> %s',
                        $post_id,
                        $meta_value,
                        $aligned
                    ));
                }

                return;
            }
        }

        // Sanitize shop ID: the Multi-Shops addon can write the value with a leading/trailing
        // space (e.g. ' 62111'), which causes MySQL '=' comparisons to silently fail.
        // Fix it immediately after write using a direct DB update to avoid recursion.
        if ($meta_key === '_sln_booking_shop' && is_string($meta_value) && $meta_value !== trim($meta_value)) {
            global $wpdb;
            $wpdb->update(
                $wpdb->postmeta,
                array('meta_value' => trim($meta_value)),
                array('meta_id'    => $meta_id),
                array('%s'),
                array('%d')
            );
            wp_cache_delete($post_id, 'post_meta');
        }

        // Clear cache when services are updated (includes duration changes from PWA drag-resize)
        if ($meta_key === '_sln_booking_services') {
            $booking = $this->getPlugin()->createBooking($post_id);

            if ($booking && $booking->getDate()) {
                if (SLN_Plugin::isDebugEnabled()) {
                    SLN_Plugin::addLog(sprintf(
                        '[Cache Invalidation] Clearing availability cache due to services update (booking #%d, date: %s)',
                        $post_id,
                        $booking->getDate()->format('Y-m-d')
                    ));
                }

                SLN_Helper_Availability_Cache::clearDateCache($booking->getDate());
                $this->clearIntervalsCache($booking);
            }
        }

        // Also clear when date or time changes (booking rescheduled)
        if (in_array($meta_key, array('_sln_booking_date', '_sln_booking_time'))) {
            $booking = $this->getPlugin()->createBooking($post_id);

            if ($booking && $booking->getDate()) {
                if (SLN_Plugin::isDebugEnabled()) {
                    SLN_Plugin::addLog(sprintf(
                        '[Cache Invalidation] Clearing availability cache due to date/time update (booking #%d, date: %s)',
                        $post_id,
                        $booking->getDate()->format('Y-m-d')
                    ));
                }

                SLN_Helper_Availability_Cache::clearDateCache($booking->getDate());
                $this->clearIntervalsCache($booking);

                // The clears above only drop the transient/in-memory availability
                // caches; they do NOT rebuild the persisted salon_cache option that
                // getCachedDays() uses to decide which calendar days are selectable.
                // The normal booking flow rebuilds it via Builder::processBooking(),
                // but bookings created with raw wp_insert_post + update_post_meta
                // (CSV importer, external migrators, WP-CLI, programmatic inserts)
                // bypass the Builder entirely, leaving salon_cache stale — fully booked
                // days keep showing as available until Settings are re-saved. Queue a
                // deferred rebuild of the affected day so those paths stay consistent.
                $this->queueSalonCacheRefresh($booking->getDate());
            }
        }
    }

    /**
     * Mark a day for a persisted salon_cache rebuild and register a single
     * shutdown flush. Deferring to shutdown guarantees every meta field has been
     * written before we recompute (avoids the importer's "date set after
     * wp_insert_post" timing gap) and collapses a bulk insert that touches many
     * days into one option write per request.
     *
     * @param DateTime $date
     */
    private function queueSalonCacheRefresh($date)
    {
        $ymd = $date->format('Y-m-d');
        self::$dirtyCacheDates[$ymd] = $ymd;

        if (!self::$cacheShutdownHooked) {
            self::$cacheShutdownHooked = true;
            add_action('shutdown', array($this, 'flushSalonCacheRefresh'));
        }
    }

    /**
     * Rebuild the persisted salon_cache for every day queued during this request.
     * Runs once on shutdown (after the HTTP response work is done) so importing or
     * migrating bookings stays responsive while availability is kept correct.
     */
    public function flushSalonCacheRefresh()
    {
        if (empty(self::$dirtyCacheDates)) {
            return;
        }

        $dates = self::$dirtyCacheDates;
        self::$dirtyCacheDates = array();

        try {
            $plugin = $this->getPlugin();

            // processDate() recomputes the day from live bookings via
            // getForAvailabilityBookings(), which is memoised per request. Clear
            // that cache first so the booking just written is actually counted
            // (mirrors the cancellation path in onBookingSetStatus()).
            $plugin->getRepository(SLN_Plugin::POST_TYPE_BOOKING)->clearBookingCache();

            $bc = $plugin->getBookingCache();
            foreach ($dates as $ymd) {
                $bc->processDate(\Salon\Util\Date::create($ymd));
            }
            $bc->save();
        } catch (Exception $e) {
            SLN_Plugin::addLog('[Cache Invalidation] flushSalonCacheRefresh failed: ' . $e->getMessage());
        }
    }


    public function bulkAdminFooterNew()
    {
        $this->bulkAdminFooter(true);
    }

    public function bulkAdminFooterEdit()
    {
        $this->bulkAdminFooter(false);
    }

    public function bulkAdminFooter($isNew = false)
    {
        global $post;
        if ($post && $post->post_type == SLN_Plugin::POST_TYPE_BOOKING) {
            ?>
            <script type="text/javascript">
                jQuery(function ($) {
                    $('#save-post').attr('value', '<?php echo esc_html__(
                        $isNew ? "Add booking" : 'Update booking',
                        'salon-booking-system'
                    ) ?>').addClass('sln-btn sln-btn--main');
                    $('#submitdiv h3 span').text('<?php echo esc_html__('Booking', 'salon-booking-system') ?>');
                    <?php
                    foreach (SLN_Enum_BookingStatus::toArray() as $k => $v) {
                    $complete = '';
                    $label = '';
                    if ($post->post_status == $k) {
                        $complete = ' selected=\"selected\"';
                        $label = '<span id=\"post-status-display\">'.$v.'</span>';
                    }
                    ?>
                    $("select#post_status").append("<option value=\"<?php echo $k ?>\" <?php echo $complete ?>><?php echo $v ?></option>");
                    $(".misc-pub-section label").append("<?php echo $label ?>");
                    <?php
                    }
                    ?>
                });
            </script>
            <?php
        }
    }

    public function bulkPostStates($post_states, $post)
    {
        global $post;
        $arg = get_query_var('post_status');
        if ($post && $post->post_type == SLN_Plugin::POST_TYPE_BOOKING) {
            foreach (SLN_Enum_BookingStatus::toArray() as $k => $v) {

                if ($arg != $k) {
                    if ($post->post_status == $k) {
                        return array($v);
                    }
                }
            }
        }

        return $post_states;
    }

    public function posttype_admin_css()
    {
        global $post_type;
        if ($post_type == SLN_Plugin::POST_TYPE_BOOKING) {
            echo $this->getPlugin()->loadView('metabox/_booking_head');
        }
    }

    /**
     * @param WP_Query $query
     */
    public function parse_query($query) {
        global $pagenow;

        if (isset($_GET['post_type']) && $_GET['post_type'] === $this->getPostType() && is_admin() && $pagenow=='edit.php' && $query->get('post_type') === $this->getPostType()) {
            $meta_queries = array();
            if (isset($_GET['m']) && !empty($_GET['m'])) {
                $m =  sanitize_text_field(wp_unslash($_GET['m']));
                $y =  substr ( $m , 0 ,4 );
                $m =  substr ( $m , 4, 2 );
                $meta_queries[] = array(
                    'key'     => '_sln_booking_date',
                    'value'   => array($y."-".$m."-01",$y."-".$m."-31"),
                    'compare' => 'BETWEEN',
                );
            }

	    if ( ! empty( $_GET['service'] ) ) {
                $service = sanitize_text_field(wp_unslash($_GET['service']));
                $meta_queries[] = array(
                    'key'     => '_sln_booking_services',
                    'value'   => "\"service\";i:{$service};",
                    'compare' => 'LIKE',
                );
            }

            if (isset($_GET['attendant']) && !empty($_GET['attendant'])) {
                $attendant = sanitize_text_field(wp_unslash($_GET['attendant']));
                $meta_queries[] = array(
                    'key'     => '_sln_booking_services',
                    'value'   => "i:{$attendant};",
                    'compare' => 'LIKE',
                );
            }

            if (isset($_GET['username']) && !empty($_GET['username'])) {
                $username_parts = explode('|', sanitize_text_field(wp_unslash($_GET['username'])));
                if (!empty($username_parts[0])) {
                    $meta_queries[] = array(
                        'key'   => '_sln_booking_firstname',
                        'value' => $username_parts[0],
                    );
                }
                if (!empty($username_parts[1])) {
                    $meta_queries[] = array(
                        'key'   => '_sln_booking_lastname',
                        'value' => $username_parts[1],
                    );
                }
            }

            if(has_filter('sln_booking_meta_multi_param')) {
                $meta_queries = apply_filters('sln_booking_meta_multi_param', $meta_queries);
            }
            
            // Only set post_status to all statuses if user hasn't selected a specific status
            // This allows the status filter dropdown to work correctly
            if (empty($_GET['post_status']) || $_GET['post_status'] === 'all') {
                $statuses_keys = array_keys(SLN_Enum_BookingStatus::toArray());
                // Don't include auto-draft posts in the bookings list
                // These are temporary posts created by WordPress for autosave functionality
                $query->set('post_status',$statuses_keys);
            }
            // If post_status is set in GET, WordPress will handle it automatically
            
            if (!empty($meta_queries)) {
                $meta_queries['relation'] = 'AND';

                $meta_query = $query->get('meta_query');

                $meta_query = array_merge(!empty($meta_query) ? $meta_query : array(), $meta_queries);
                $query->set('meta_query', $meta_query);
            }
        }
    }

    public function restrict_manage_posts($post_type, $which = null) {
        global $wpdb;
        if ($post_type === $this->getPostType() && $which === 'top') {
            $statuses = SLN_Enum_BookingStatus::toArray();

            $rows = $wpdb->get_results("SELECT mv1.meta_value as firstname, {$wpdb->prefix}postmeta.meta_value as lastname FROM {$wpdb->prefix}postmeta INNER JOIN {$wpdb->prefix}postmeta AS mv1 ON {$wpdb->prefix}postmeta.post_id = mv1.post_id AND mv1.meta_key = '_sln_booking_firstname' WHERE {$wpdb->prefix}postmeta.meta_key='_sln_booking_lastname';");

            $users = array();
            foreach($rows as $row) {
                $key = ($row->firstname ? $row->firstname : '') . '|' . ($row->lastname ? $row->lastname : '');
                $users[$key] = $row->firstname . ' ' . $row->lastname;
            }
            asort($users);

            $repo       = $this->getPlugin()->getRepository(SLN_Plugin::POST_TYPE_SERVICE);
            $services   = $repo->getAll();

            $repo       = $this->getPlugin()->getRepository(SLN_Plugin::POST_TYPE_ATTENDANT);
            $attendants = $repo->getAll();
            ?>
            <?php $current = isset($_GET['post_status']) ? sanitize_text_field(wp_unslash($_GET['post_status'])) : ''; ?>
	    <?php if ($current !== 'trash'): ?>
		<select name="post_status" id="filter-by-post_status">
		    <option value=""><?php esc_html_e('All Statuses', 'salon-booking-system') ?></option>
		    <?php foreach($statuses as $k => $v): ?>
			<option value="<?php echo $k; ?>" <?php echo ($current === $k ? 'selected' : ''); ?>><?php echo $v; ?></option>
		    <?php endforeach ?>
		</select>
	    <?php endif ?>

            <?php $current = isset($_GET['username']) ? sanitize_text_field(wp_unslash($_GET['username'])) : ''; ?>
            <select name="username" id="filter-by-username">
                <option value=""><?php esc_html_e('All users name', 'salon-booking-system') ?></option>
                <?php foreach($users as $k => $v): ?>
                    <option value="<?php echo $k; ?>" <?php echo ($current === $k ? 'selected' : ''); ?>><?php echo $v; ?></option>
                <?php endforeach ?>
            </select>

            <?php $current = isset($_GET['service']) ? (int) $_GET['service'] : ''; ?>
            <select name="service" id="filter-by-service">
                <option value=""><?php esc_html_e('All services', 'salon-booking-system') ?></option>
                <?php foreach($services as $v): ?>
                    <option value="<?php echo $v->getId(); ?>" <?php echo ($current === $v->getId() ? 'selected' : ''); ?>><?php echo $v->getTitle(); ?></option>
                <?php endforeach ?>
            </select>

            <?php $current = isset($_GET['attendant']) ? (int) $_GET['attendant'] : ''; ?>
            <select name="attendant" id="filter-by-attendant">
                <option value=""><?php esc_html_e('All attendants', 'salon-booking-system') ?></option>
                <?php foreach($attendants as $v): ?>
                    <option value="<?php echo $v->getId(); ?>" <?php echo ($current === $v->getId() ? 'selected' : ''); ?>><?php echo $v->getTitle(); ?></option>
                <?php endforeach ?>
            </select>
            <?php
        }
    }

}
