<?php
/**
 * BookiFlex Core
 *
 * @package    BookiFlex
 * @subpackage Core
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

namespace BookiFlex\Core\Repository;

use InvalidArgumentException;

defined( 'ABSPATH' ) || exit;

/**
 * Data Transfer Object for reservation list filtering and pagination.
 *
 * Encapsulates all filter parameters for querying reservations in admin table list.
 * Immutable (readonly) to prevent accidental mutations during query building.
 *
 * @package BookiFlex\Core\Repository
 */
class ReservationQueryFilter
{
    /**
     * Allowed orderBy fields for sorting
     */
    private const ALLOWED_ORDER_BY = [
        'created_at',
        'updated_at',
        'status',
        'check_in_date',
        'check_out_date',
        'total_amount',
        'number',
	    'customer',
//	    'payment_type',
    ];

    /**
     * Create reservation query filter.
     *
     * @param array|null $statuses Filter by reservation statuses (e.g., ['confirmed', 'waiting_payment'])
     * @param int|null $customerId Filter by customer ID
     * @param int|null $groupId Filter by reservation group ID
     * @param string|null $searchTerm Search term for customer name, email, or reservation number
     * @param string|null $reservationNumber Filter by reservation number (partial match)
     * @param string $orderBy Field to sort by (must be in ALLOWED_ORDER_BY)
     * @param string $order Sort direction (ASC or DESC)
     * @param int $limit Number of results per page
     * @param int $offset Offset for pagination
     *
     * @throws InvalidArgumentException If validation fails
     */
    public function __construct(
        public ?array $statuses = null,
        public ?int $customerId = null,
        public ?int $groupId = null,
        public ?string $searchTerm = null,
        public ?string $reservationNumber = null,
        public string $orderBy = 'created_at',
        public string $order = 'DESC',
        public int $limit = 5,
        public int $offset = 0,
    ) {
        // Validate order direction
        $this->order = strtoupper($this->order);
        if (!in_array($this->order, ['ASC', 'DESC'], true)) {
            throw new InvalidArgumentException(
                sprintf('Order must be ASC or DESC, got: %s', $order)
            );
        }

        // Validate orderBy field
        if (!in_array($this->orderBy, self::ALLOWED_ORDER_BY, true)) {
            throw new InvalidArgumentException(
                sprintf(
                    'Invalid orderBy field: %s. Allowed: %s',
                    $this->orderBy,
                    implode(', ', self::ALLOWED_ORDER_BY)
                )
            );
        }

        // Validate numeric constraints
        if ($this->limit < 0) {
            throw new InvalidArgumentException('Limit must be non-negative');
        }

        if ($this->offset < 0) {
            throw new InvalidArgumentException('Offset must be non-negative');
        }

        // Trim string values
        if ($this->searchTerm !== null) {
            $this->searchTerm = trim($this->searchTerm);
            if ($this->searchTerm === '') {
                $this->searchTerm = null;
            }
        }

        if ($this->reservationNumber !== null) {
            $this->reservationNumber = trim($this->reservationNumber);
            if ($this->reservationNumber === '') {
                $this->reservationNumber = null;
            }
        }

        if ($this->statuses !== null) {
            $this->statuses = array_values(array_filter(
                array_map('trim', $this->statuses),
                fn(string $s) => $s !== ''
            ));
            if (empty($this->statuses)) {
                $this->statuses = null;
            }
        }
    }

    /**
     * Create filter from HTTP request parameters.
     *
     * Factory method for creating filter from WordPress admin request.
     * Handles sanitization and type conversion of request parameters.
     *
     * @param array $request Request parameters (typically $_GET + $_REQUEST merged)
     * @param int $perPage Number of items per page
     * @param int $currentPage Current page number (1-indexed)
     *
     * @return self
     *
     * @example
     * ```php
     * $filter = ReservationQueryFilter::fromRequest(
     *     array_merge($_GET, $_REQUEST),
     *     20,  // per page
     *     2    // page 2
     * );
     * ```
     */
    public static function fromRequest(array $request, int $perPage, int $currentPage): self
    {
        return new self(
            statuses: !empty($request['status']) ? [(string) $request['status']] : null,
            customerId: isset($request['customer_id']) && is_numeric($request['customer_id'])
                ? (int) $request['customer_id']
                : null,
            groupId: isset($request['group_id']) && is_numeric($request['group_id'])
                ? (int) $request['group_id']
                : null,
            searchTerm: !empty($request['s']) ? (string) $request['s'] : null,
            reservationNumber: !empty($request['reservation_number'])
                ? (string) $request['reservation_number']
                : null,
            orderBy: !empty($request['orderby']) ? (string) $request['orderby'] : 'created_at',
            order: !empty($request['order']) ? strtoupper((string) $request['order']) : 'DESC',
            limit: $perPage,
            offset: max(0, ($currentPage - 1) * $perPage),
        );
    }

    /**
     * Check if any filters are active (excluding pagination/sorting).
     *
     * @return bool True if at least one filter is set
     */
    public function hasFilters(): bool
    {
        return $this->statuses !== null
            || $this->customerId !== null
            || $this->groupId !== null
            || $this->searchTerm !== null
            || $this->reservationNumber !== null;
    }

    /**
     * Create a copy of this filter without statuses filter.
     *
     * Useful for getting status counts across all statuses.
     *
     * @return self New filter instance without statuses
     */
    public function withoutStatuses(): self
    {
        return new self(
            statuses: null,
            customerId: $this->customerId,
            groupId: $this->groupId,
            searchTerm: $this->searchTerm,
            reservationNumber: $this->reservationNumber,
            orderBy: $this->orderBy,
            order: $this->order,
            limit: $this->limit,
            offset: $this->offset,
        );
    }

    /**
     * Create a copy of this filter without pagination.
     *
     * Useful for counting total results or exporting all data.
     *
     * @return self New filter instance with limit=0, offset=0
     */
    public function withoutPagination(): self
    {
        return new self(
            statuses: $this->statuses,
            customerId: $this->customerId,
            groupId: $this->groupId,
            searchTerm: $this->searchTerm,
            reservationNumber: $this->reservationNumber,
            orderBy: $this->orderBy,
            order: $this->order,
            limit: 0,
            offset: 0,
        );
    }
}
