<?php

namespace CouponsPlus\App\Conditions\BuiltIn\Conditions\Time\Factories;

use stdClass;
use JsonException;
use CouponsPlus\Original\Collections\Collection;
use CouponsPlus\App\Conditions\BuiltIn\Conditions\Time\HoursChecker;
use CouponsPlus\App\Conditions\BuiltIn\Conditions\Time\WeekDayChecker;
use CouponsPlus\App\Conditions\BuiltIn\Conditions\Time\FailingTimeChecker;
use CouponsPlus\App\Conditions\BuiltIn\Conditions\Time\TimeCheckerORComposite;
use CouponsPlus\App\Conditions\BuiltIn\Conditions\Time\TimeCheckerANDComposite;
use CouponsPlus\App\Conditions\BuiltIn\Conditions\Time\Abilities\CheckableTime;
use CouponsPlus\App\Conditions\BuiltIn\Conditions\Time\HoursAndMinutesRangeChecker;

class TimeCheckerFactory
{
    /**
     * Creates from offical Time slot format:
     * [
     *      'days' => [0 optionally through 6], // 0 = Sunday,
     *      'time' => [
     *          'type' => 'hours' | 'range',
     *          'value' => string[] | [string, string]; // e.g., ['00:00', '10:00'] for range or [0, 10, 18] for hours
     *      ]
     * ]
     * @param array|stdClass $timeSlot
     * @return CheckableTime
     */
    public function createTimeSlot($timeSlot): CheckableTime
    {
        try {
            $arrayedSlot = $this->ensureArray($timeSlot);
        } catch (JsonException $e) {
            return new FailingTimeChecker;
        }

        return new TimeCheckerANDComposite(
            // days checker
            $this->createWeekdaysChecker($arrayedSlot['days'] ?? new FailingTimeChecker),
            // time checker (hours and minutes)
            $this->createTimeChecker($arrayedSlot['time'] ?? new FailingTimeChecker)
        );
    }

    /**
     * @param int[] $days // 0 to 6
     */
    public function createWeekdaysChecker(array $days) : CheckableTime
    {
        return  new TimeCheckerORComposite(...Collection::create($days)->map(function (int $day) {
            if ($day < 0 || $day > 6) {
                return new FailingTimeChecker();
            }
            return new WeekDayChecker($day);
        })->asArray());
    }

    /**
     * @param array $time
     */
    public function createTimeChecker(array $time)
    {
        if ($time['type'] === 'hours') {
            return new TimeCheckerORComposite(...Collection::create($time['value'] ?? [])->map(function (int $value) {
                return new HoursChecker($value);
            })->asArray());
        }

        // this is a range
        return new HoursAndMinutesRangeChecker(...$time['value'] ?? []);
    }

    /**
     * @param array|object $timeSlot
     * @throws JsonException
     */
    private function ensureArray( $timeSlot)
    {
        // if object convert it to array recursively
        if (is_object($timeSlot)) {
            return json_decode(json_encode($timeSlot), true, 512, JSON_THROW_ON_ERROR);
        }
        return $timeSlot;
    }
}