<?php

namespace ZenoChatbot\Services\Scheduler;

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

class PendingSchedule
{
    /** @var string */
    private $job;

    /** @var mixed */
    private $action;

    /** @var array<string,mixed> */
    private $args = [];

    /** @var string */
    private $group = '';

    /** @var bool */
    private $unique = false;

    /** @var bool */
    private $sync = false;

    /** @var bool|callable */
    private $condition = true;

    /** @var int */
    private $delaySeconds = 0;

    /** @var int|null */
    private $runAtTimestamp;

    /** @var string|null */
    private $recurrenceType;

    /** @var mixed */
    private $recurrenceValue;

    /** @var int */
    private $priority = 10;

    public static function create($job, $action): PendingSchedule
    {
        $instance = new static();
        $instance->job = trim((string) $job);
        $instance->action = $action;

        return $instance;
    }

    public function args($args): PendingSchedule
    {
        $this->args = is_array($args) ? $args : [];
        return $this;
    }

    public function group($group): PendingSchedule
    {
        $this->group = sanitize_text_field((string) $group);
        return $this;
    }

    public function unique($state = true): PendingSchedule
    {
        $this->unique = (bool) $state;
        return $this;
    }

    public function when($condition): PendingSchedule
    {
        if (is_bool($condition) || is_callable($condition)) {
            $this->condition = $condition;
        }

        return $this;
    }

    public function after($delay): PendingSchedule
    {
        $this->delaySeconds = Scheduler::toSeconds($delay);
        return $this;
    }

    public function at($time, $timezone = null): PendingSchedule
    {
        if ($time instanceof \DateTimeInterface) {
            $this->runAtTimestamp = (int) $time->getTimestamp();
            return $this;
        }

        if (is_numeric($time)) {
            $this->runAtTimestamp = (int) $time;
            return $this;
        }

        if (!is_string($time)) {
            return $this;
        }

        $time = trim($time);
        if ($time === '') {
            return $this;
        }

        $tz = null;
        if (is_string($timezone) && $timezone !== '') {
            try {
                $tz = new \DateTimeZone($timezone);
            } catch (\Throwable $e) {
                $tz = null;
            }
        }

        try {
            $date = $tz ? new \DateTimeImmutable($time, $tz) : new \DateTimeImmutable($time);
            $this->runAtTimestamp = (int) $date->getTimestamp();
        } catch (\Throwable $e) {
            // ignore invalid date
        }

        return $this;
    }

    public function repeat(): RepeatSchedule
    {
        return RepeatSchedule::create($this);
    }

    public function sync($state = true): PendingSchedule
    {
        $this->sync = (bool) $state;
        return $this;
    }

    public function dispatch(): array
    {
        Scheduler::boot();

        if ($this->job === '') {
            return [
                'success' => false,
                'scheduled' => false,
                'reason' => 'invalid_job',
                'job' => $this->job,
            ];
        }

        if (!Scheduler::rememberAction($this->job, $this->action)) {
            return [
                'success' => false,
                'scheduled' => false,
                'reason' => 'invalid_action',
                'job' => $this->job,
            ];
        }

        if (!$this->passesCondition()) {
            $cleared = 0;
            if ($this->sync) {
                $cleared = Scheduler::cancel($this->job, null, $this->resolveGroup());
            }

            return [
                'success' => true,
                'scheduled' => false,
                'reason' => 'condition_false',
                'job' => $this->job,
                'cleared' => $cleared,
            ];
        }

        $group = $this->resolveGroup();
        $hookArgs = [$this->args];

        if ($this->sync) {
            if (Scheduler::hasScheduled($this->job, $hookArgs, $group)) {
                return [
                    'success' => true,
                    'scheduled' => false,
                    'reason' => 'already_scheduled',
                    'job' => $this->job,
                    'group' => $group,
                    'sync' => true,
                ];
            }

            Scheduler::cancel($this->job, null, $group);
        } elseif ($this->unique && Scheduler::hasScheduled($this->job, $hookArgs, $group)) {
            return [
                'success' => true,
                'scheduled' => false,
                'reason' => 'already_scheduled',
                'job' => $this->job,
                'group' => $group,
            ];
        }

        $timestamp = $this->resolveTimestamp();
        $actionId = 0;

        if ($this->recurrenceType === null) {
            if ($this->runAtTimestamp === null && $this->delaySeconds <= 0) {
                $actionId = Scheduler::enqueueAsync($this->job, $hookArgs, $group, $this->unique, $this->priority);
            } else {
                $actionId = Scheduler::scheduleSingle($this->job, $timestamp, $hookArgs, $group, $this->unique, $this->priority);
            }
        } elseif ($this->recurrenceType === 'interval') {
            $interval = max(1, (int) $this->recurrenceValue);
            $actionId = Scheduler::scheduleRecurring($this->job, $timestamp, $interval, $hookArgs, $group, $this->unique, $this->priority);
        } elseif ($this->recurrenceType === 'cron') {
            $pattern = trim((string) $this->recurrenceValue);
            if ($pattern === '') {
                return [
                    'success' => false,
                    'scheduled' => false,
                    'reason' => 'invalid_recurrence_pattern',
                    'job' => $this->job,
                    'group' => $group,
                ];
            }

            $actionId = Scheduler::scheduleCron($this->job, $timestamp, $pattern, $hookArgs, $group, $this->unique, $this->priority);
        }

        return [
            'success' => $actionId > 0,
            'scheduled' => $actionId > 0,
            'reason' => $actionId > 0 ? 'scheduled' : 'schedule_failed',
            'job' => $this->job,
            'hook' => Scheduler::jobHook($this->job),
            'group' => $group,
            'action_id' => (int) $actionId,
            'recurrence' => $this->recurrenceType,
            'timestamp' => $timestamp,
            'unique' => $this->unique,
            'sync' => $this->sync,
        ];
    }

    public function setRecurrence($type, $value): PendingSchedule
    {
        $type = trim((string) $type);
        if (!in_array($type, ['interval', 'cron'], true)) {
            return $this;
        }

        $this->recurrenceType = $type;
        $this->recurrenceValue = $value;

        return $this;
    }

    public function previewTimestamp(): int
    {
        return $this->resolveTimestamp();
    }

    private function resolveGroup(): string
    {
        return $this->group !== '' ? $this->group : Scheduler::defaultGroup();
    }

    private function resolveTimestamp(): int
    {
        if (is_int($this->runAtTimestamp)) {
            return max(time(), $this->runAtTimestamp);
        }

        if ($this->delaySeconds > 0) {
            return time() + $this->delaySeconds;
        }

        return time() + 1;
    }

    private function passesCondition(): bool
    {
        if (is_bool($this->condition)) {
            return $this->condition;
        }

        if (is_callable($this->condition)) {
            try {
                return (bool) call_user_func($this->condition);
            } catch (\Throwable $e) {
                return false;
            }
        }

        return true;
    }
}
