<?php
/**
 * Multiple time windows per day for opening hours.
 *
 * @package Maca_Menu_List
 */

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

/**
 * Validate and normalize one daily time window (same-day only; no overnight).
 *
 * @param string $open_raw Open time.
 * @param string $close_raw Close time.
 * @return array{open: string, close: string}|null
 */
function maca_menulist_sanitize_hours_window($open_raw, $close_raw) {
    $open = maca_menulist_normalize_time($open_raw);
    $close = maca_menulist_normalize_time($close_raw);

    if ($open === null || $close === null) {
        return null;
    }

    $open = substr($open, 0, 5);
    $close = substr($close, 0, 5);

    if ($open >= $close) {
        return null;
    }

    return array(
        'open'  => $open,
        'close' => $close,
    );
}

/**
 * Sanitize a list of time windows and sort by opening time.
 *
 * @param mixed $windows_raw Raw windows from storage or POST.
 * @return array<int, array{open: string, close: string}>
 */
function maca_menulist_sanitize_hours_windows_list($windows_raw) {
    if (!is_array($windows_raw)) {
        return array();
    }

    $windows = array();

    foreach ($windows_raw as $window) {
        if (!is_array($window)) {
            continue;
        }

        $open_raw = isset($window['open']) ? $window['open'] : (isset($window['time_open']) ? $window['time_open'] : '');
        $close_raw = isset($window['close']) ? $window['close'] : (isset($window['time_close']) ? $window['time_close'] : '');
        $sanitized = maca_menulist_sanitize_hours_window($open_raw, $close_raw);

        if ($sanitized !== null) {
            $windows[] = $sanitized;
        }
    }

    usort(
        $windows,
        static function ($a, $b) {
            return strcmp($a['open'], $b['open']);
        }
    );

    return $windows;
}

/**
 * Extract windows from stored day hours (supports legacy open/close only).
 *
 * @param array<string, mixed>|null $day_hours Stored day config.
 * @return array<int, array{open: string, close: string}>
 */
function maca_menulist_day_hours_to_windows($day_hours) {
    if (!is_array($day_hours)) {
        return array();
    }

    if (!empty($day_hours['windows']) && is_array($day_hours['windows'])) {
        $windows = maca_menulist_sanitize_hours_windows_list($day_hours['windows']);
        if (!empty($windows)) {
            return $windows;
        }
    }

    $open = isset($day_hours['open']) ? (string) $day_hours['open'] : '11:00';
    $close = isset($day_hours['close']) ? (string) $day_hours['close'] : '22:00';
    $single = maca_menulist_sanitize_hours_window($open, $close);

    return $single !== null ? array($single) : array();
}

/**
 * Normalize stored day hours to closed + windows (+ legacy open/close span).
 *
 * @param array<string, mixed> $day_hours   Raw day config.
 * @param string               $default_open  Fallback open.
 * @param string               $default_close Fallback close.
 * @return array{closed: bool, windows: array<int, array{open: string, close: string}>, open: string, close: string}
 */
function maca_menulist_normalize_stored_day_hours($day_hours, $default_open = '11:00', $default_close = '22:00') {
    $closed = !empty($day_hours['closed']);
    $windows = maca_menulist_day_hours_to_windows($day_hours);

    if (empty($windows)) {
        $fallback = maca_menulist_sanitize_hours_window($default_open, $default_close);
        $windows = $fallback !== null ? array($fallback) : array();
    }

    return array(
        'closed'  => $closed,
        'windows' => $windows,
        'open'    => $windows[0]['open'],
        'close'   => $windows[ count($windows) - 1 ]['close'],
    );
}

/**
 * Build effective hours array for a date from time windows.
 *
 * @param array<int, array{open: string, close: string}> $windows Time windows.
 * @param string                                         $note    Optional note.
 * @return array{bookable: bool, closed: bool, open: string|null, close: string|null, windows: array<int, array{open: string, close: string}>, note: string}
 */
function maca_menulist_build_bookable_hours_result(array $windows, $note = '') {
    $windows = maca_menulist_sanitize_hours_windows_list($windows);

    $result = array(
        'bookable' => !empty($windows),
        'closed'   => empty($windows),
        'open'     => null,
        'close'    => null,
        'windows'  => $windows,
        'note'     => (string) $note,
    );

    if (!empty($windows)) {
        $result['open'] = $windows[0]['open'];
        $result['close'] = $windows[ count($windows) - 1 ]['close'];
    }

    return $result;
}

/**
 * Extract windows from an effective hours array.
 *
 * @param array<string, mixed>|null $hours Effective hours.
 * @return array<int, array{open: string, close: string}>
 */
function maca_menulist_get_hours_windows($hours) {
    if (!is_array($hours)) {
        return array();
    }

    if (!empty($hours['windows']) && is_array($hours['windows'])) {
        return maca_menulist_sanitize_hours_windows_list($hours['windows']);
    }

    if (!empty($hours['open']) && !empty($hours['close'])) {
        $single = maca_menulist_sanitize_hours_window($hours['open'], $hours['close']);

        return $single !== null ? array($single) : array();
    }

    return array();
}

/**
 * Whether effective hours allow bookings / service.
 *
 * @param array<string, mixed>|null $hours Effective hours.
 * @return bool
 */
function maca_menulist_hours_are_bookable($hours) {
    return is_array($hours)
        && !empty($hours['bookable'])
        && !empty(maca_menulist_get_hours_windows($hours));
}

/**
 * Guest-facing text for one or more windows.
 *
 * @param array<int, array{open: string, close: string}> $windows Time windows.
 * @return string
 */
function maca_menulist_format_hours_windows_text(array $windows) {
    $windows = maca_menulist_sanitize_hours_windows_list($windows);

    if (empty($windows)) {
        return '';
    }

    $parts = array();

    foreach ($windows as $window) {
        $parts[] = $window['open'] . '–' . $window['close'];
    }

    return implode(', ', $parts);
}

/**
 * Guest-facing opening hours lines (one line per time window).
 *
 * @param array<string, mixed> $hours         Weekly day hours or effective date hours.
 * @param string               $exception_type Optional exception type for that date.
 * @return array<int, string>
 */
function maca_menulist_get_opening_hours_schedule_lines($hours, $exception_type = '') {
    $exception_type = sanitize_key($exception_type);

    if ($exception_type === 'fully_booked') {
        return array(__('Fully booked', 'maca-restu-pro'));
    }

    $closed = !empty($hours['closed']);
    if (isset($hours['bookable'])) {
        $closed = $closed || empty($hours['bookable']);
    }

    if ($closed) {
        return array(__('Closed', 'maca-restu-pro'));
    }

    $windows = maca_menulist_sanitize_hours_windows_list(maca_menulist_get_hours_windows($hours));

    if (empty($windows)) {
        return array(__('Closed', 'maca-restu-pro'));
    }

    $lines = array();

    foreach ($windows as $window) {
        $lines[] = $window['open'] . '–' . $window['close'];
    }

    return $lines;
}

/**
 * Render stacked opening hours times for the public opening hours block.
 *
 * @param array<string, mixed> $hours         Weekly day hours or effective date hours.
 * @param string               $exception_type Optional exception type for that date.
 * @return string Safe HTML.
 */
function maca_menulist_render_opening_hours_schedule_time_html($hours, $exception_type = '') {
    $lines = maca_menulist_get_opening_hours_schedule_lines($hours, $exception_type);

    if (empty($lines)) {
        return '';
    }

    if (count($lines) === 1) {
        return '<span class="maca-opening-hours-time">' . esc_html($lines[0]) . '</span>';
    }

    $html = '<span class="maca-opening-hours-time maca-opening-hours-time--stacked">';

    foreach ($lines as $line) {
        $html .= '<span class="maca-opening-hours-window">' . esc_html($line) . '</span>';
    }

    $html .= '</span>';

    return $html;
}

/**
 * Whether a booking start time fits inside any window (respects booking duration).
 *
 * @param string                                              $date          Date Y-m-d.
 * @param string                                              $time_norm     Time HH:MM:SS.
 * @param array<int, array{open: string, close: string}>|null $windows       Time windows.
 * @param int|null                                            $slot_duration Booking duration minutes.
 * @return bool
 */
function maca_menulist_is_booking_start_in_hours_windows($date, $time_norm, $windows, $slot_duration = null) {
    $windows = maca_menulist_sanitize_hours_windows_list($windows);

    if ($date === '' || empty($windows)) {
        return false;
    }

    $slot_dt = maca_menulist_booking_make_datetime($date, $time_norm);
    if (!$slot_dt) {
        return false;
    }

    $slot_duration = $slot_duration === null ? maca_menulist_table_booking_slot_duration() : max(1, (int) $slot_duration);

    foreach ($windows as $window) {
        $open_dt = maca_menulist_booking_make_datetime($date, $window['open']);
        $close_dt = maca_menulist_booking_make_datetime($date, $window['close']);

        if (!$open_dt || !$close_dt) {
            continue;
        }

        $last_start = clone $close_dt;
        $last_start = $last_start->modify('-' . $slot_duration . ' minutes');

        if ($slot_dt >= $open_dt && $slot_dt <= $last_start) {
            return true;
        }
    }

    return false;
}

/**
 * Whether a clock time falls inside any service window (takeaway / open-now checks).
 *
 * @param string                                              $date      Date Y-m-d.
 * @param string                                              $time_norm Time HH:MM:SS.
 * @param array<int, array{open: string, close: string}>|null $windows   Time windows.
 * @return bool
 */
function maca_menulist_is_service_time_in_hours_windows($date, $time_norm, $windows) {
    $windows = maca_menulist_sanitize_hours_windows_list($windows);

    if ($date === '' || empty($windows)) {
        return false;
    }

    $current = substr((string) $time_norm, 0, 8);

    foreach ($windows as $window) {
        $open = strlen($window['open']) === 5 ? $window['open'] . ':00' : $window['open'];
        $close = strlen($window['close']) === 5 ? $window['close'] . ':00' : $window['close'];

        if (maca_menulist_time_in_range($current, $open, $close)) {
            return true;
        }
    }

    return false;
}

/**
 * Generate HH:MM booking start slots across multiple windows.
 *
 * @param string                                              $date    Date Y-m-d.
 * @param array<int, array{open: string, close: string}>|null $windows Time windows.
 * @param array<string, mixed>                                $args    {
 *     @type bool          $skip_past_today Skip past times when date is today.
 *     @type callable|null $filter          Optional filter callback ( slot H:i ) => bool.
 * }
 * @return array<int, string>
 */
function maca_menulist_generate_booking_time_slots_from_windows($date, $windows, $args = array()) {
    $args = wp_parse_args(
        is_array($args) ? $args : array(),
        array(
            'skip_past_today' => true,
            'filter'          => null,
        )
    );

    $windows = maca_menulist_sanitize_hours_windows_list($windows);
    if ($date === '' || empty($windows)) {
        return array();
    }

    $slot_duration = maca_menulist_table_booking_slot_duration();
    $interval = 30;
    $now = maca_menulist_booking_now();
    $is_today = ($date === $now->format('Y-m-d'));
    $slots = array();
    $seen = array();

    foreach ($windows as $window) {
        $open_dt = maca_menulist_booking_make_datetime($date, $window['open']);
        $close_dt = maca_menulist_booking_make_datetime($date, $window['close']);

        if (!$open_dt || !$close_dt) {
            continue;
        }

        $last_start = clone $close_dt;
        $last_start = $last_start->modify('-' . $slot_duration . ' minutes');

        if ($last_start < $open_dt) {
            continue;
        }

        for ($current = clone $open_dt; $current <= $last_start; $current = $current->modify('+' . $interval . ' minutes')) {
            if ($args['skip_past_today'] && $is_today && $current <= $now) {
                continue;
            }

            $slot = $current->format('H:i');

            if (isset($seen[ $slot ])) {
                continue;
            }

            if (is_callable($args['filter']) && !call_user_func($args['filter'], $slot)) {
                continue;
            }

            $seen[ $slot ] = true;
            $slots[] = $slot;
        }
    }

    sort($slots);

    return $slots;
}

/**
 * Count booking-duration sittings across all windows on a date.
 *
 * @param string                                              $date          Date Y-m-d.
 * @param array<int, array{open: string, close: string}>|null $windows       Time windows.
 * @param int|null                                            $slot_duration Booking duration minutes.
 * @return int
 */
function maca_menulist_count_booking_sittings_from_windows($date, $windows, $slot_duration = null) {
    $windows = maca_menulist_sanitize_hours_windows_list($windows);

    if ($date === '' || empty($windows)) {
        return 0;
    }

    $slot_duration = $slot_duration === null ? maca_menulist_table_booking_slot_duration() : max(1, (int) $slot_duration);
    $total_minutes = 0;

    foreach ($windows as $window) {
        $open_dt = maca_menulist_booking_make_datetime($date, $window['open']);
        $close_dt = maca_menulist_booking_make_datetime($date, $window['close']);

        if (!$open_dt || !$close_dt) {
            continue;
        }

        $minutes = (int) floor(($close_dt->getTimestamp() - $open_dt->getTimestamp()) / 60);
        if ($minutes > 0) {
            $total_minutes += $minutes;
        }
    }

    if ($total_minutes <= 0) {
        return 0;
    }

    $sittings = (int) floor($total_minutes / $slot_duration);

    if ($sittings >= 1) {
        return $sittings;
    }

    foreach ($windows as $window) {
        if (maca_menulist_generate_booking_time_slots_from_windows($date, array($window), array('skip_past_today' => false))) {
            return 1;
        }
    }

    return 0;
}

/**
 * Parse one weekday row from admin POST into stored day hours.
 *
 * @param array<string, mixed>|null $day_data POST row for one weekday.
 * @return array{closed: bool, windows: array<int, array{open: string, close: string}>, open: string, close: string}|null
 */
function maca_menulist_parse_day_hours_from_request($day_data) {
    if (!is_array($day_data)) {
        return null;
    }

    $closed = empty($day_data['is_open']);
    if (isset($day_data['closed'])) {
        $closed = !empty($day_data['closed']);
    }

    if (isset($day_data['windows']) && is_array($day_data['windows'])) {
        $windows = maca_menulist_sanitize_hours_windows_list($day_data['windows']);
    } else {
        $open_raw = isset($day_data['time_open']) ? $day_data['time_open'] : (isset($day_data['open']) ? $day_data['open'] : '11:00');
        $close_raw = isset($day_data['time_close']) ? $day_data['time_close'] : (isset($day_data['close']) ? $day_data['close'] : '22:00');
        $single = maca_menulist_sanitize_hours_window($open_raw, $close_raw);
        $windows = $single !== null ? array($single) : array();
    }

    if (!$closed && empty($windows)) {
        $fallback = maca_menulist_sanitize_hours_window('11:00', '22:00');
        $windows = $fallback !== null ? array($fallback) : array();
    }

    return array(
        'closed'  => $closed,
        'windows' => $windows,
        'open'    => !empty($windows) ? $windows[0]['open'] : '11:00',
        'close'   => !empty($windows) ? $windows[ count($windows) - 1 ]['close'] : '22:00',
    );
}
