<?php
/**
 * @package   DPCalendar
 * @copyright Copyright (C) 2018 Digital Peak GmbH. <https://www.digital-peak.com>
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL
 */

namespace DigitalPeak\Component\DPCalendar\Administrator\Helper;

\defined('_JEXEC') or die();

use DigitalPeak\Component\DPCalendar\Administrator\Translator\Translator;
use Joomla\CMS\Application\CMSWebApplicationInterface;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Recurr\Rule;
use Recurr\Transformer\TextTransformer;
use Recurr\Transformer\Translator as TransformerTranslator;

class DateHelper
{
	private ?Translator $translator = null;

	public function transformRRuleToString(?string $rrule, ?string $startDate, ?array $exdates = []): string
	{
		if (\in_array($rrule, [null, '', '0'], true)) {
			return '';
		}

		$start = $this->getDate($startDate);
		$rule  = new Rule('', $start, null, $start->getTimezone()->getName());
		$rule->setExDates($exdates !== null && $exdates !== [] ? $exdates : []);

		$parts = $rule->parseString($rrule);

		// Parser can't handle both
		if (isset($parts['UNTIL']) && isset($parts['COUNT'])) {
			unset($parts['UNTIL']);
		}

		//Only add the date so we have no tz issues
		if (isset($parts['UNTIL'])) {
			$parts['UNTIL'] = substr((string)$parts['UNTIL'], 0, 8);
		}

		$translator = new TransformerTranslator();
		try {
			$translator->loadLocale(substr(DPCalendarHelper::getFrLanguage(), 0, 2));
		} catch (\InvalidArgumentException) {
			// Translation doesn't exist, ignore it
		}

		$buffer = '';
		try {
			$rule->loadFromArray($parts);
			$buffer = ucfirst((string)(new TextTransformer($translator))->transform($rule));

			if ($exdates !== null && $exdates !== [] && $this->translator instanceof Translator) {
				$buffer .= PHP_EOL;
				$buffer .= $this->translator->translate('COM_DPCALENDAR_EXDATES_EXCLUDE_LIST_TEXT');
				$buffer .= implode(', ', array_map(fn (Date|\DateTime|string|null $d): string => $this->getDate($d)->format('d.m.Y', true), $exdates));
			}
		} catch (\Exception) {
			// Do not crash as some rules are not parseable for the transformator
		}

		return $buffer;
	}

	public function getDate(Date|\DateTime|string|null $date = null, bool|int|null $allDay = null, ?string $tz = null): Date
	{
		// Load the application
		$app = Factory::getApplication();

		// Create the date object
		$dateObj = $date instanceof Date ? clone $date : Factory::getDate($date ?? '', $tz);

		// The global timezone
		$timezone = $app->get('offset');

		// The user object
		$user = $app->getIdentity();

		// Check if the user exists
		if ($user && $user->id !== 0) {
			// Load the timezone from the user
			$timezone = $user->getParam('timezone', $timezone);
		}

		// Set the flag if the timezone switcher is enabled
		static $timeZoneSwitcherEnabled = null;
		if ($timeZoneSwitcherEnabled === null) {
			$timeZoneSwitcherEnabled = (int)DPCalendarHelper::getComponentParameter('enable_tz_switcher', 0);
		}

		// Load the timezone from the session
		if ($app instanceof CMSWebApplicationInterface && $timeZoneSwitcherEnabled === 1) {
			$timezone = $app->getSession()->get('com_dpcalendar.user.timezone', $timezone);
		}

		// Set the timezone when not an ell day date
		if (\in_array($allDay, [false, 0, null], true)) {
			$dateObj->setTimezone(new \DateTimeZone($timezone));
		}

		// The date object
		return $dateObj;
	}

	public function getDateStringFromEvent(\stdClass $event, ?string $dateFormat = null, ?string $timeFormat = null, bool $noTags = false): string
	{
		$text = LayoutHelper::render(
			'event.datestring',
			['event' => $event, 'dateFormat' => $dateFormat, 'timeFormat' => $timeFormat],
			'',
			['component' => 'com_dpcalendar', 'client' => 0]
		);

		if (!$noTags) {
			return $text;
		}

		$text = strip_tags($text);
		$text = preg_replace("/\s\s+/", ' ', $text);

		return trim((string)$text);
	}

	public function getNames(): array
	{
		$options                    = [];
		$options['monthNames']      = [];
		$options['monthNamesShort'] = [];
		$options['dayNames']        = [];
		$options['dayNamesShort']   = [];
		$options['dayNamesMin']     = [];
		for ($i = 0; $i < 7; $i++) {
			$options['dayNames'][]      = DPCalendarHelper::dayToString($i, false);
			$options['dayNamesShort'][] = DPCalendarHelper::dayToString($i, true);

			if (\function_exists('mb_substr')) {
				$options['dayNamesMin'][] = mb_substr(DPCalendarHelper::dayToString($i, true), 0, 2);
			} else {
				$options['dayNamesMin'][] = substr(DPCalendarHelper::dayToString($i, true), 0, 2);
			}
		}
		for ($i = 1; $i <= 12; $i++) {
			$options['monthNames'][]      = DPCalendarHelper::monthToString($i, false);
			$options['monthNamesShort'][] = DPCalendarHelper::monthToString($i, true);
		}

		return $options;
	}

	public function convertPHPDateToJS(string $format): string
	{
		$replacements = [
			'A' => 'A',      // for the sake of escaping below
			'a' => 'a',      // for the sake of escaping below
			'B' => '',       // Swatch internet time (.beats), no equivalent
			'c' => 'YYYY-MM-DD[T]HH:mm:ssZ', // ISO 8601
			'D' => 'ddd',
			'd' => 'DD',
			'e' => 'zz',     // deprecated since version 1.6.0 of dayjs.js
			'F' => 'MMMM',
			'G' => 'H',
			'g' => 'h',
			'H' => 'HH',
			'h' => 'hh',
			'I' => '',       // Daylight Saving Time? => dayjs().isDST();
			'i' => 'mm',
			'j' => 'D',
			'L' => '',       // Leap year? => dayjs().isLeapYear();
			'l' => 'dddd',
			'M' => 'MMM',
			'm' => 'MM',
			'N' => 'E',
			'n' => 'M',
			'O' => 'ZZ',
			'o' => 'YYYY',
			'P' => 'Z',
			'r' => 'ddd, DD MMM YYYY HH:mm:ss ZZ', // RFC 2822
			'S' => 'o',
			's' => 'ss',
			'T' => 'z',      // deprecated since version 1.6.0 of dayjs.js
			't' => '',       // days in the month => dayjs().daysInMonth();
			'U' => 'X',
			'u' => 'SSSSSS', // microseconds
			'v' => 'SSS',    // milliseconds (from PHP 7.0.0)
			'W' => 'W',      // for the sake of escaping below
			'w' => 'e',
			'Y' => 'YYYY',
			'y' => 'YY',
			'Z' => '',       // time zone offset in minutes => dayjs().zone();
			'z' => 'DDD',
		];

		// Converts escaped characters
		foreach (array_keys($replacements) as $from) {
			$replacements['\\' . $from] = '[' . $from . ']';
		}

		return Text::_(strtr($format, $replacements));
	}

	public function minutesToDuration(int $minutes): string
	{
		if ($minutes === 0) {
			return '00:00:00';
		}

		$buffer = str_pad((string)floor($minutes / 60), 2, '0', STR_PAD_LEFT);
		$buffer .= ':';
		$buffer .= str_pad((string)($minutes % 60), 2, (string)STR_PAD_LEFT);

		return $buffer . ':00';
	}

	public function setTranslator(Translator $translator): void
	{
		$this->translator = $translator;
	}
}
