<?php

namespace App\Provisioning;

use App\Provisioning\Contracts\ProvisioningStep;
use InvalidArgumentException;

/**
 * Maps a pipeline name (host | customer) to its ordered list of step classes.
 */
class PipelineRegistry
{
    /** @param array<string, array<int, class-string<ProvisioningStep>>> $pipelines */
    public function __construct(private array $pipelines) {}

    /** @return array<int, class-string<ProvisioningStep>> */
    public function stepsFor(string $pipeline): array
    {
        if (! isset($this->pipelines[$pipeline])) {
            throw new InvalidArgumentException("Unknown pipeline [{$pipeline}].");
        }

        return $this->pipelines[$pipeline];
    }

    public function count(string $pipeline): int
    {
        return count($this->stepsFor($pipeline));
    }

    /**
     * Will a run of `$pipeline` standing at `$fromStep` still carry out every
     * step of `$work`?
     *
     * The question a caller about to start a `$work` run has to ask about the run
     * already in flight, and the reason it is asked step by step rather than by
     * pipeline name: `plan-change` contains the whole of `address` and the whole
     * of `storage`, `quota` contains only the last third of `storage`, and
     * `restart` contains neither. A name comparison would have to be kept in step
     * with config/provisioning.php by hand; this reads the same lists the runner
     * executes.
     *
     * EVERY step, not any: the three steps of a storage change are ordered
     * because a quota Nextcloud enforces over a filesystem that never grew is a
     * promise of space the machine does not have. A run that would apply the new
     * quota and nothing else does not cover the work — it delivers half of it,
     * which is worse than delivering none.
     *
     * Steps at `$fromStep` itself count as still to come. The step there is
     * either about to run or running, and every step reads the machine's state
     * when it gets there rather than when the run was created.
     */
    public function stillCovers(string $pipeline, int $fromStep, string $work): bool
    {
        $remaining = array_slice($this->stepsFor($pipeline), max($fromStep, 0));

        foreach ($this->stepsFor($work) as $step) {
            if (! in_array($step, $remaining, true)) {
                return false;
            }
        }

        return true;
    }

    public function resolve(string $pipeline, int $index): ProvisioningStep
    {
        $steps = $this->stepsFor($pipeline);

        if (! isset($steps[$index])) {
            throw new InvalidArgumentException("No step at index {$index} for pipeline [{$pipeline}].");
        }

        return app($steps[$index]);
    }
}
