<?php
/**
 * Calendario festivo de Colombia (Ley Emiliani y fechas fijas).
 *
 * @package Logistica
 */

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

/**
 * Festivos oficiales de Colombia para el checklist del panel.
 */
final class Logistica_Colombia_Holidays {

	/**
	 * Festivos de un año: fecha Y-m-d => etiqueta.
	 *
	 * @param int $year Año.
	 * @return array<string, string>
	 */
	public static function holidays_for_year( $year ) {
		$year = (int) $year;
		if ( $year < 1970 || $year > 2100 ) {
			return array();
		}

		$tz       = wp_timezone();
		$holidays = array();

		self::add_fixed( $holidays, $year, 1, 1, __( 'Año Nuevo', 'logistica' ), $tz );
		self::add_emiliani( $holidays, $year, 1, 6, __( 'Reyes Magos', 'logistica' ), $tz );
		self::add_emiliani( $holidays, $year, 3, 19, __( 'Día de San José', 'logistica' ), $tz );

		$easter = self::easter_sunday( $year, $tz );
		self::add_date( $holidays, $easter->modify( '-3 days' ), __( 'Jueves Santo', 'logistica' ) );
		self::add_date( $holidays, $easter->modify( '-2 days' ), __( 'Viernes Santo', 'logistica' ) );

		self::add_emiliani( $holidays, $year, 5, 1, __( 'Día del Trabajo', 'logistica' ), $tz );
		self::add_emiliani_date( $holidays, $easter->modify( '+43 days' ), __( 'Ascensión del Señor', 'logistica' ), $tz );
		self::add_emiliani_date( $holidays, $easter->modify( '+64 days' ), __( 'Corpus Christi', 'logistica' ), $tz );
		self::add_emiliani_date( $holidays, $easter->modify( '+71 days' ), __( 'Sagrado Corazón', 'logistica' ), $tz );
		self::add_emiliani( $holidays, $year, 6, 29, __( 'San Pedro y San Pablo', 'logistica' ), $tz );

		self::add_fixed( $holidays, $year, 7, 20, __( 'Día de la Independencia', 'logistica' ), $tz );
		self::add_fixed( $holidays, $year, 8, 7, __( 'Batalla de Boyacá', 'logistica' ), $tz );
		self::add_emiliani( $holidays, $year, 8, 15, __( 'Asunción de la Virgen', 'logistica' ), $tz );
		self::add_emiliani( $holidays, $year, 10, 12, __( 'Día de la Raza', 'logistica' ), $tz );
		self::add_emiliani( $holidays, $year, 11, 1, __( 'Todos los Santos', 'logistica' ), $tz );
		self::add_emiliani( $holidays, $year, 11, 11, __( 'Independencia de Cartagena', 'logistica' ), $tz );

		self::add_fixed( $holidays, $year, 12, 8, __( 'Inmaculada Concepción', 'logistica' ), $tz );
		self::add_fixed( $holidays, $year, 12, 25, __( 'Navidad', 'logistica' ), $tz );

		return $holidays;
	}

	/**
	 * @param int $from_year Primer año.
	 * @param int $to_year   Último año.
	 * @return array<string, string>
	 */
	public static function holidays_for_range( $from_year, $to_year ) {
		$from_year = (int) $from_year;
		$to_year   = (int) $to_year;
		if ( $from_year > $to_year ) {
			$swap      = $from_year;
			$from_year = $to_year;
			$to_year   = $swap;
		}

		$holidays = array();
		for ( $year = $from_year; $year <= $to_year; $year++ ) {
			foreach ( self::holidays_for_year( $year ) as $date => $label ) {
				$holidays[ $date ] = $label;
			}
		}

		ksort( $holidays );

		return $holidays;
	}

	/**
	 * Domingo de Pascua (algoritmo de Meeus/Jones/Butcher).
	 *
	 * @param int               $year Año.
	 * @param DateTimeZone|null $tz   Zona horaria.
	 * @return DateTimeImmutable
	 */
	private static function easter_sunday( $year, $tz ) {
		$a = $year % 19;
		$b = (int) floor( $year / 100 );
		$c = $year % 100;
		$d = (int) floor( $b / 4 );
		$e = $b % 4;
		$f = (int) floor( ( $b + 8 ) / 25 );
		$g = (int) floor( ( $b - $f + 1 ) / 3 );
		$h = ( 19 * $a + $b - $d - $g + 15 ) % 30;
		$i = (int) floor( $c / 4 );
		$k = $c % 4;
		$l = ( 32 + 2 * $e + 2 * $i - $h - $k ) % 7;
		$m = (int) floor( ( $a + 11 * $h + 22 * $l ) / 451 );
		$month = (int) floor( ( $h + $l - 7 * $m + 114 ) / 31 );
		$day   = ( ( $h + $l - 7 * $m + 114 ) % 31 ) + 1;

		return new DateTimeImmutable(
			sprintf( '%04d-%02d-%02d', $year, $month, $day ),
			$tz ? $tz : wp_timezone()
		);
	}

	/**
	 * Fecha fija (sin traslado Emiliani).
	 *
	 * @param array<string, string> $holidays Mapa destino.
	 * @param int                   $year     Año.
	 * @param int                   $month    Mes.
	 * @param int                   $day      Día.
	 * @param string                $label    Etiqueta.
	 * @param DateTimeZone          $tz       Zona horaria.
	 * @return void
	 */
	private static function add_fixed( array &$holidays, $year, $month, $day, $label, DateTimeZone $tz ) {
		$date = DateTimeImmutable::createFromFormat( 'Y-n-j', $year . '-' . $month . '-' . $day, $tz );
		if ( ! $date ) {
			return;
		}

		self::add_date( $holidays, $date, $label );
	}

	/**
	 * Festivo con Ley Emiliani (mes/día del año).
	 *
	 * @param array<string, string> $holidays Mapa destino.
	 * @param int                   $year     Año.
	 * @param int                   $month    Mes.
	 * @param int                   $day      Día.
	 * @param string                $label    Etiqueta.
	 * @param DateTimeZone          $tz       Zona horaria.
	 * @return void
	 */
	private static function add_emiliani( array &$holidays, $year, $month, $day, $label, DateTimeZone $tz ) {
		$date = DateTimeImmutable::createFromFormat( 'Y-n-j', $year . '-' . $month . '-' . $day, $tz );
		if ( ! $date ) {
			return;
		}

		self::add_emiliani_date( $holidays, $date, $label, $tz );
	}

	/**
	 * @param array<string, string> $holidays Mapa destino.
	 * @param DateTimeImmutable     $date     Fecha base.
	 * @param string                $label    Etiqueta.
	 * @param DateTimeZone          $tz       Zona horaria.
	 * @return void
	 */
	private static function add_emiliani_date( array &$holidays, DateTimeImmutable $date, $label, DateTimeZone $tz ) {
		$observed = self::emiliani_observance( $date, $tz );
		self::add_date( $holidays, $observed, $label );
	}

	/**
	 * Traslado al lunes anterior (martes) o siguiente (mié–dom).
	 *
	 * @param DateTimeImmutable $date Fecha original.
	 * @param DateTimeZone      $tz   Zona horaria.
	 * @return DateTimeImmutable
	 */
	private static function emiliani_observance( DateTimeImmutable $date, DateTimeZone $tz ) {
		$dow = (int) $date->format( 'N' );

		if ( 1 === $dow ) {
			return $date;
		}

		if ( 2 === $dow ) {
			return $date->modify( '-1 day' );
		}

		return $date->modify( '+' . ( 8 - $dow ) . ' days' );
	}

	/**
	 * @param array<string, string> $holidays Mapa destino.
	 * @param DateTimeImmutable     $date     Fecha.
	 * @param string                $label    Etiqueta.
	 * @return void
	 */
	private static function add_date( array &$holidays, DateTimeImmutable $date, $label ) {
		$holidays[ $date->format( 'Y-m-d' ) ] = $label;
	}
}
