protected function plan(int $from, int $to)
    {
        $migrations = [];
        
        if ($from === $to)
            return $migrations;

        $is_downgrade = $to < $from;
        $reachable = $this->migrations[$from] ?? [];

        // For downgrades, we want the lowest reachable target that is above or equal to the final target
        // For upgrades, we want the highest reachable target that is below or equal to the final target
        // To always use the first encountered, the array needs to be reversed for upgrades, as it's sorted by key
        if (!$is_downgrade)
            $reachable = array_reverse($reachable, true);

        // Traverse the reachable migrations
        foreach ($reachable as $direct_to => $path)
        {
            if (
                ($direct_to === $to)                || // Bingo
                ($is_downgrade && $direct_to > $to) || // Proper downgrade
                (!$is_downgrade && $direct_to < $to)   // Proper upgrade
            )
            {
                // In the right direction, so add to the path
                $migrations[] = ['from' => $from, 'to' => $direct_to, 'file' => $path];
                break;
            }
        }

        if (count($migrations) === 0)
            throw new MigrationException("No migration path from version $from to $to for module {$this->module}");

        $last = end($migrations);
        if ($last['to'] !== $to)
        {
            $rest = $this->plan($last['to'], $to);
            foreach ($rest as $migration)
                $migrations[] = $migration;
        }

        return $migrations;
    }