<?php

declare(strict_types=1);

namespace apexl\Io\modules\cron\Collection;

use apexl\Io\modules\cron\Interface\CronAction;
use Arrayy\Collection\AbstractCollection;
use Symfony\Component\Scheduler\RecurringMessage;
use Symfony\Component\Scheduler\Schedule;
use Symfony\Component\Scheduler\Trigger\StaticMessageProvider;
use Symfony\Component\Scheduler\Trigger\TriggerInterface;

class ScheduleCollection extends AbstractCollection
{
    public function getType(): string
    {
        return Schedule::class;
    }

    /**
     * Build and register a Schedule for the given CronAction.
     *
     * Wraps the action in a StaticMessageProvider so RecurringMessage::trigger()
     * skips its serialize($message) call — necessary because Symfony Scheduler
     * was designed around plain-DTO messages, but a CronAction with DI-injected
     * services transitively holds non-serialisable resources (e.g. PDOStatement).
     *
     * Returns the built Schedule so callers can chain further configuration like
     * ->stateful($cache) or ->processOnlyLastMissedRun(true).
     */
    public function addAction(TriggerInterface $trigger, CronAction $action): Schedule
    {
        $schedule = (new Schedule())->with(
            RecurringMessage::trigger(
                $trigger,
                new StaticMessageProvider(
                    [$action],
                    id: hash('xxh128', $action::class),
                    description: $action::class,
                ),
            ),
        );

        $this->add($schedule, $action::class);

        return $schedule;
    }

    /**
     * Detects the historic mis-registration shape and emits a clear
     * deprecation notice rather than letting the consumer silently
     * fatal at the first crashing hook.
     *
     * Background: every Town Centre `Register*CronTasks` hook was
     * calling
     *
     *   $collection->add(
     *       SomeCron::class,
     *       (new Schedule())->add(RecurringMessage::cron('expr', new SomeCron())),
     *   );
     *
     * which the inherited `voku/arrayy` `Arrayy::add($value, $key)`
     * reads as (value=class-string, key=Schedule-object) — then PHP
     * fatals trying to use the Schedule object as an array offset.
     * Net: the first hook to register that way takes the whole
     * `cron:run` process down, and ALL crons silently stop ticking.
     * Discovered 2026-05-29 in Town Centre — symptom was "every cron
     * silently dead for an unknown length of time"; manual button-
     * trigger paths were keeping individual features alive.
     *
     * The correct API is `addAction(TriggerInterface, CronAction)`
     * above — wraps the StaticMessageProvider boilerplate and
     * registers with the right (value=Schedule, key=class-string)
     * shape.
     *
     * This override calls parent::add() unconditionally (so legacy
     * paths keep working for anyone with a pre-built Schedule) but
     * emits an E_USER_DEPRECATED notice when the wrong-args shape
     * is detected — naming the offending CronAction class + pointing
     * at the correct API.
     */
    public function add($value, $key = null): static
    {
        if (
            is_string($value)
            && $key instanceof Schedule
            && is_subclass_of($value, CronAction::class)
        ) {
            trigger_error(
                sprintf(
                    'ScheduleCollection::add(%s::class, Schedule) is deprecated. '
                    . 'Use $collection->addAction(CronExpressionTrigger::fromSpec($expr), new %s()) instead. '
                    . 'Args have been auto-swapped to keep the registration alive — this path will be removed in a future major.',
                    $value,
                    $value,
                ),
                E_USER_DEPRECATED,
            );
            // Auto-swap so the registration actually lands (parent::add
            // with the broken arg-order would fatal in voku/arrayy).
            // Belt-and-braces — the deprecation tells the consumer to
            // migrate to addAction(); meanwhile their crons keep firing.
            return parent::add($key, $value);
        }
        return parent::add($value, $key);
    }
}
