<?php

/**
 * User Weekday Data Service
 */

namespace RadiusTheme\RadiusBooking\Services\EmployeeServices;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}
use RadiusTheme\RadiusBooking\Helpers\SettingsHelper;
use RadiusTheme\RadiusBooking\Models\EmployeeWeekday;
use RadiusTheme\RadiusBooking\Models\EmployeeWeekdayTimePeriod;
use RadiusTheme\RadiusBooking\Models\User;
use RadiusTheme\RadiusBooking\Repositories\UserRepository;

/**
 * User Weekday Data Service
 *
 * This service provides methods to manage and retrieve data related to user-assigned weekdays,
 * time periods, break times, locations, and services. It includes functionality to format and
 * extract detailed structured data associated with user schedules.
 */
class UserWeekdayDataService {

	/**
	 * Retrieve the weekdays associated with a specific user.
	 *
	 * @param int $user_id The ID of the user for whom to retrieve weekdays.
	 *
	 * @return array An array of formatted weekday data for the specified user. Returns an empty array if the user or weekdays are not found.
	 */
	public static function getUserWeekdays( $user_id ) {
		$user = User::find( $user_id );
		if ( ! $user ) {
			return array();
		}

		$weekdays = $user->weekdays()->get();
		if ( empty( $weekdays ) ) {
			return array();
		}
		$static = new static();
		return $static->formatWeekdayData( $user_id, $weekdays );
	}

	/**
	 * Retrieve the effective weekdays for an employee, merging custom schedule with global schedule fallback.
	 *
	 * Used for availability calculations. If the employee has no custom schedule, the global
	 * schedule is used entirely. If partially defined, missing days fallback to global.
	 *
	 * @param int $user_id The ID of the user.
	 *
	 * @return array Merged weekday data with global schedule fallback.
	 */
	public static function getEffectiveWeekdays( $user_id ) {
		$employeeWeekdays = self::getUserWeekdays( $user_id );

		// If employee has custom weekdays, use them exclusively.
		// Only enabled days are saved to DB, so missing days are intentionally disabled.
		if ( ! empty( $employeeWeekdays ) ) {
			return $employeeWeekdays;
		}

		// Sub-staff inherit the creator's effective schedule when they haven't
		// set their own — so updates to the main staff's schedule flow through
		// live instead of being frozen at creation time.
		$user = User::find( $user_id );
		if ( $user && ! empty( $user->created_by ) && (int) $user->created_by !== (int) $user_id ) {
			return self::getEffectiveWeekdays( (int) $user->created_by );
		}

		// No custom schedule and no parent — fall back to global.
		return self::getGlobalScheduleAsWeekdays( $user_id );
	}

	/**
	 * Format weekday data for a user
	 *
	 * @param int $user_id The ID of the user
	 * @param array $weekdays A collection of weekday objects to be formatted
	 *
	 * @return array An array of formatted weekday data
	 */
	public function formatWeekdayData( $user_id, $weekdays ) {
		$weekdayData = array();

		foreach ( $weekdays as $weekday ) {
			$weekdayData[] = array(
				'id'           => $weekday->id,
				'user_id'      => $user_id,
				'day_index'    => $weekday->day_index,
				'start_time'   => $weekday->start_time,
				'end_time'     => $weekday->end_time,
				'time_breaks'  => $this->getTimeBreaks( $weekday->id ),
				'time_periods' => $this->getTimePeriods( $weekday->id ),
			);
		}

		return $weekdayData;
	}

	/**
	 * Retrieve the break times for a specific weekday.
	 *
	 * @param int $weekday_id The ID of the weekday to retrieve breaks for.
	 *
	 * @return array An array of formatted break data for the specified weekday.
	 */
	private function getTimeBreaks( $weekday_id ) {
		$timeWeekDay = EmployeeWeekday::find( $weekday_id );
		if ( ! $timeWeekDay ) {
			return array();
		}

		$breaks = $timeWeekDay->employeeBreakTimes()->get();
		if ( empty( $breaks ) ) {
			return array();
		}

		return $this->formatBreakData( $breaks );
	}

	/**
	 * Format break data into an array structure
	 *
	 * @param array $breaks Array of break objects to be formatted
	 *
	 * @return array Array of formatted break data
	 */
	private function formatBreakData( $breaks ) {
		$breakData = array();

		foreach ( $breaks as $break ) {
			$breakData[] = array(
				'id'         => $break->id,
				'weekday_id' => $break->weekday_id,
				'start_time' => $break->start_time,
				'end_time'   => $break->end_time,
				'note'       => $break->note ?? '',
			);
		}

		return $breakData;
	}

	/**
	 * Get time periods for a specific weekday
	 *
	 * @param int $weekday_id The ID of the weekday to retrieve time periods for
	 *
	 * @return array The list of formatted time periods associated with the weekday
	 */
	private function getTimePeriods( $weekday_id ) {
		$timeWeekDay = EmployeeWeekday::find( $weekday_id );
		if ( ! $timeWeekDay ) {
			return array();
		}

		$timePeriods = $timeWeekDay->employeeTimePeriods()->get();
		if ( empty( $timePeriods ) ) {
			return array();
		}

		return $this->formatTimePeriodData( $timePeriods );
	}

	/**
	 * Format time period data with relevant details
	 *
	 * @param array $timePeriods Array of time period objects to format
	 *
	 * @return array Formatted time period data with associated details
	 */
	private function formatTimePeriodData( $timePeriods ) {
		$periodData = array();

		foreach ( $timePeriods as $period ) {
			$periodData[] = array(
				'id'         => $period->id,
				'weekday_id' => $period->weekday_id,
				'start_time' => $period->start_time,
				'end_time'   => $period->end_time,
				'locations'  => $this->getTimePeriodLocations( $period->id ),
				'services'   => $this->getTimePeriodServices( $period->id ),
			);
		}

		return $periodData;
	}

	/**
	 * Get locations for a specified time period.
	 *
	 * @param int $period_id The ID of the time period.
	 *
	 * @return array An array of formatted location data for the given time period, or an empty array if no locations are found.
	 */
	private function getTimePeriodLocations( $period_id ) {
		$timePeriod = EmployeeWeekdayTimePeriod::find( $period_id );
		if ( ! $timePeriod ) {
			return array();
		}

		$locations = $timePeriod->locations()->get();
		if ( empty( $locations ) ) {
			return array();
		}

		return $this->formatLocationData( $locations );
	}

	/**
	 * Format location data into an array structure
	 *
	 * @param array $locations An array of location objects to be formatted
	 *
	 * @return array An array of formatted location data
	 */
	private function formatLocationData( $locations ) {
		$locationData = array();

		foreach ( $locations as $location ) {
			$locationData[] = array(
				'id'                     => $location->id,
				'weekday_time_period_id' => $location->weekday_time_period_id,
				'location_id'            => $location->location_id,
			);
		}

		return $locationData;
	}

	/**
	 * Retrieves and formats the services associated with a specified time period.
	 *
	 * @param int $period_id The ID of the time period for which services are to be retrieved.
	 *
	 * @return array An array of formatted service data. Returns an empty array if the time period or services are not found.
	 */
	private function getTimePeriodServices( $period_id ) {
		$timePeriod = EmployeeWeekdayTimePeriod::find( $period_id );
		if ( ! $timePeriod ) {
			return array();
		}

		$services = $timePeriod->services()->get();
		if ( empty( $services ) ) {
			return array();
		}

		return $this->formatServiceData( $services );
	}

	/**
	 * Formats the provided service data into a structured array.
	 *
	 * @param array $services An array of service objects to be formatted.
	 *
	 * @return array An array of formatted service data, including IDs, time period IDs, and service IDs.
	 */
	private function formatServiceData( $services ) {
		$serviceData = array();

		foreach ( $services as $service ) {
			$serviceData[] = array(
				'id'                     => $service->id,
				'weekday_time_period_id' => $service->weekday_time_period_id,
				'service_id'             => $service->service_id,
			);
		}

		return $serviceData;
	}

	/**
	 * Extracts and compiles unique location IDs from a structured array of weekdays and their associated time periods.
	 *
	 * @param array $weekdays An array of weekdays, each containing time periods and locations data.
	 *
	 * @return array A reindexed array of unique location IDs. Returns an empty array if no location IDs are found.
	 */
	public function extractLocationIds( array $weekdays ): array {
		$locationIds = array();

		foreach ( $weekdays as $weekday ) {
			if ( empty( $weekday['time_periods'] ) ) {
				continue;
			}

			foreach ( $weekday['time_periods'] as $period ) {
				if ( empty( $period['locations'] ) ) {
					continue;
				}

				foreach ( $period['locations'] as $loc ) {
					if ( ! empty( $loc['location_id'] ) ) {
						$locationIds[] = $loc['location_id'];
					}
				}
			}
		}

		// Remove duplicates and reindex for clean output
		return array_values( array_unique( $locationIds ) );
	}

	/**
	 * Converts the Global Schedule settings into the Employee Weekday format.
	 *
	 * Used as fallback when an employee has no custom schedule (or for days
	 * not covered by the employee's custom schedule).
	 *
	 * @param int $user_id The employee user ID.
	 *
	 * @return array Array of weekday data in the same format as formatWeekdayData().
	 */
	public static function getGlobalScheduleAsWeekdays( $user_id ) {
		$globalSchedule = SettingsHelper::get_setting( 'weeklySchedule' );

		$dayNameToIndex = array(
			'monday'    => 1,
			'tuesday'   => 2,
			'wednesday' => 3,
			'thursday'  => 4,
			'friday'    => 5,
			'saturday'  => 6,
			'sunday'    => 7,
		);

		$weekdayData = array();

		foreach ( $globalSchedule as $dayName => $schedule ) {
			if ( empty( $schedule['enabled'] ) ) {
				continue;
			}

			$dayIndex = $dayNameToIndex[ $dayName ] ?? null;
			if ( $dayIndex === null ) {
				continue;
			}

			$startTime = $schedule['start'] ?? '09:00';
			$endTime   = $schedule['end'] ?? '17:00';

			$weekdayData[] = array(
				'id'           => null,
				'user_id'      => $user_id,
				'day_index'    => $dayIndex,
				'start_time'   => $startTime,
				'end_time'     => $endTime,
				'time_breaks'  => array(),
				'time_periods' => array(
					array(
						'id'         => null,
						'weekday_id' => null,
						'start_time' => $startTime,
						'end_time'   => $endTime,
						'locations'  => array(),
						'services'   => array(),
					),
				),
			);
		}

		return $weekdayData;
	}

	/**
	 * Merges employee weekday data with global schedule fallback on a per-day basis.
	 *
	 * Employee custom days take priority. For any day_index not present in the
	 * employee data, the global schedule entry (if enabled) is used.
	 *
	 * @param array $employeeWeekdays Employee's custom weekday data.
	 * @param array $globalWeekdays   Global schedule converted to weekday format.
	 *
	 * @return array Merged weekday data array.
	 */
	private static function mergeWithGlobalSchedule( $employeeWeekdays, $globalWeekdays ) {
		$employeeDayMap = array();
		foreach ( $employeeWeekdays as $weekday ) {
			$employeeDayMap[ $weekday['day_index'] ] = $weekday;
		}

		$globalDayMap = array();
		foreach ( $globalWeekdays as $weekday ) {
			$globalDayMap[ $weekday['day_index'] ] = $weekday;
		}

		$merged = array();

		for ( $dayIndex = 1; $dayIndex <= 7; $dayIndex++ ) {
			if ( isset( $employeeDayMap[ $dayIndex ] ) ) {
				$merged[] = $employeeDayMap[ $dayIndex ];
			} elseif ( isset( $globalDayMap[ $dayIndex ] ) ) {
				$merged[] = $globalDayMap[ $dayIndex ];
			}
		}

		return $merged;
	}

	/**
	 * Retrieve the effective special days for an employee, falling back to global custom schedules.
	 *
	 * If the employee has custom special days saved, those are returned.
	 * Otherwise, the global customSchedules setting is converted and returned.
	 *
	 * @param int $user_id The ID of the user.
	 *
	 * @return array Special days data in the standard format.
	 */
	public static function getEffectiveSpecialDays( $user_id ) {
		$userRepo         = new UserRepository();
		$employeeSpecials = $userRepo->getUserSpecialDays( $user_id );

		if ( ! empty( $employeeSpecials ) ) {
			return $employeeSpecials;
		}

		$user = User::find( $user_id );
		if ( $user && ! empty( $user->created_by ) && (int) $user->created_by !== (int) $user_id ) {
			return self::getEffectiveSpecialDays( (int) $user->created_by );
		}

		return self::getGlobalCustomSchedulesAsSpecialDays();
	}

	/**
	 * Retrieve the effective days off for an employee, falling back to global holidays.
	 *
	 * If the employee has custom days off saved, those are returned.
	 * Otherwise, the global holidays setting is converted and returned.
	 *
	 * @param int $user_id The ID of the user.
	 *
	 * @return array Days off data in the standard format.
	 */
	public static function getEffectiveDaysOff( $user_id ) {
		$userRepo       = new UserRepository();
		$employeeDaysOff = $userRepo->getUserDaysOff( $user_id );

		if ( ! empty( $employeeDaysOff ) ) {
			return $employeeDaysOff;
		}

		$user = User::find( $user_id );
		if ( $user && ! empty( $user->created_by ) && (int) $user->created_by !== (int) $user_id ) {
			return self::getEffectiveDaysOff( (int) $user->created_by );
		}

		return self::getGlobalHolidaysAsDaysOff();
	}

	/**
	 * Converts the global customSchedules setting into the employee special days format.
	 *
	 * Global format: { id, date, start, end, label }
	 * Employee format: { id, user_id, name, start_date, end_date, time_periods: [...] }
	 *
	 * @return array Array of special days data in employee format.
	 */
	public static function getGlobalCustomSchedulesAsSpecialDays() {
		$globalCustomSchedules = get_option( 'rtrb_customSchedules_settings', array() );

		if ( empty( $globalCustomSchedules ) || ! is_array( $globalCustomSchedules ) ) {
			return array();
		}

		$specialDays = array();

		foreach ( $globalCustomSchedules as $schedule ) {
			if ( empty( $schedule['date'] ) ) {
				continue;
			}

			$specialDays[] = array(
				'id'           => null,
				'user_id'      => null,
				'name'         => ! empty( $schedule['label'] ) ? $schedule['label'] : 'Custom Schedule',
				'start_date'   => $schedule['date'],
				'end_date'     => $schedule['date'],
				'time_periods' => array(
					array(
						'id'               => null,
						'special_day_id'   => null,
						'start_time'       => $schedule['start'] ?? '09:00',
						'end_time'         => $schedule['end'] ?? '17:00',
						'locations'        => array(),
						'services'         => array(),
					),
				),
			);
		}

		return $specialDays;
	}

	/**
	 * Converts the global holidays setting into the employee days off format.
	 *
	 * Global format: { id, date, label }
	 * Employee format: { id, user_id, name, start_date, end_date, type, recurring }
	 *
	 * @return array Array of days off data in employee format.
	 */
	public static function getGlobalHolidaysAsDaysOff() {
		$globalHolidays = get_option( 'rtrb_holidays_settings', array() );

		if ( empty( $globalHolidays ) || ! is_array( $globalHolidays ) ) {
			return array();
		}

		$daysOff = array();

		foreach ( $globalHolidays as $holiday ) {
			if ( empty( $holiday['date'] ) ) {
				continue;
			}

			$daysOff[] = array(
				'id'         => null,
				'user_id'    => null,
				'name'       => ! empty( $holiday['label'] ) ? $holiday['label'] : 'Holiday',
				'start_date' => $holiday['date'],
				'end_date'   => $holiday['date'],
				'type'       => 'holiday',
				'recurring'  => '0',
			);
		}

		return $daysOff;
	}
}
