<?php

declare(strict_types=1);

namespace PrettyLinks\Pro\Health;

use PrettyLinks\Pro\Options\Store as OptionsStore;

/**
 * Schedules the `prli_link_health_check` WP-Cron event according to the
 * configured Pro `link_health_frequency` option.
 */
class Scheduler
{
    public const HOOK = 'prli_link_health_check';

    /**
     * Hook for ad-hoc, one-off runs — batch self-chaining, the 24h pending
     * retry, and the REST "Force check now". Deliberately separate from the
     * recurring {@see self::HOOK} so this class's reschedule/clear logic only
     * ever touches the recurring event: a pending single event can't mask the
     * recurrence in `wp_get_schedule()`/`wp_next_scheduled()`, and a
     * frequency-change `wp_clear_scheduled_hook(HOOK)` can't wipe an in-flight
     * chain or retry. Both hooks are bound to `HealthChecker::runCheck`.
     */
    public const ADHOC_HOOK = 'prli_link_health_check_now';

    /**
     * Pro options store.
     *
     * @var OptionsStore
     */
    private OptionsStore $options;

    /**
     * Constructor.
     *
     * @param OptionsStore $options Pro options store.
     */
    public function __construct(OptionsStore $options)
    {
        $this->options = $options;
    }

    /**
     * Schedules or clears the link-health WP-Cron event based on the current options.
     *
     * @return void
     */
    public function boot(): void
    {
        if (!(bool) $this->options->getPro('enable_link_health', false)) {
            if (wp_next_scheduled(self::HOOK)) {
                wp_clear_scheduled_hook(self::HOOK);
            }
            // Also drop any queued one-off runs (chain / retry / force) so the
            // feature goes fully quiet when disabled.
            if (wp_next_scheduled(self::ADHOC_HOOK)) {
                wp_clear_scheduled_hook(self::ADHOC_HOOK);
            }
            // And discard any in-flight force cutoff so re-enabling within its
            // TTL doesn't resume a stranded forced full-rescan.
            delete_transient(HealthChecker::FORCE_CUTOFF_KEY);
            return;
        }

        $frequency = (string) $this->options->getPro('link_health_frequency', 'weekly');
        // Valid values are 'weekly' and 'monthly'. Legacy 'daily' (and any
        // unexpected value) rewrites to 'weekly' — v4 dropped Daily because
        // the batched runner self-chains for as long as a full pass takes,
        // and an overlap window of less than a week is untenable on big
        // sites (see HealthChecker::runCheck). Writing the normalized
        // value back keeps the UI in sync with what the scheduler does.
        if ($frequency !== 'weekly' && $frequency !== 'monthly') {
            $frequency = 'weekly';
            $this->options->setPro('link_health_frequency', 'weekly');
        }
        $recurrence = $frequency === 'monthly' ? 'prli_monthly' : 'weekly';

        // If the user changed frequency since the last schedule, clear the
        // existing event so we don't keep firing on the old cadence. Only
        // the recurring event needs resetting — self-chained single events
        // use the same hook and will fall in line on the next boot.
        $current = wp_get_schedule(self::HOOK);
        if ($current !== false && $current !== $recurrence) {
            wp_clear_scheduled_hook(self::HOOK);
        }

        if (!wp_next_scheduled(self::HOOK)) {
            wp_schedule_event(time() + HOUR_IN_SECONDS, $recurrence, self::HOOK);
        }
    }
}
