<?php

declare(strict_types=1);

namespace Tournament\Bracket;

use InvalidArgumentException;

final class BracketSeeder
{
    public function __construct(
        private readonly BracketValidator $validator
    ) {
    }

    /**
     * Prepare participants for a Single Elimination bracket.
     *
     * @param array<int, array{id:int, seed?:int|null}> $participants
     *
     * @return array{
     *     participants: array<int, array>,
     *     bracket_size: int,
     *     bye_count: int,
     *     seed_order: array<int, int>,
     *     pairings: array<int, array{
     *         a: array|null,
     *         b: array|null
     *     }>
     * }
     */
    public function seed(array $participants): array
    {
        $this->validator->validateParticipants($participants);

        $participants = $this->assignSeeds($participants);

        $participantCount = count($participants);
        $bracketSize = $this->getNextPowerOfTwo($participantCount);
        $byeCount = $bracketSize - $participantCount;

        $seedMap = $this->buildSeedMap($participants);
        $seedOrder = $this->generateSeedOrder($bracketSize);

        $pairings = $this->buildPairings(
            $seedMap,
            $seedOrder
        );

        return [
            'participants' => $participants,
            'bracket_size' => $bracketSize,
            'bye_count' => $byeCount,
            'seed_order' => $seedOrder,
            'pairings' => $pairings,
        ];
    }

    /**
     * Assign sequential seeds.
     *
     * Existing seeds are preserved.
     *
     * @param array<int, array{id:int, seed?:int|null}> $participants
     *
     * @return array<int, array>
     */
    private function assignSeeds(array $participants): array
    {
        $usedSeeds = [];

        foreach ($participants as $participant) {
            if (!empty($participant['seed'])) {
                $seed = (int) $participant['seed'];

                if (isset($usedSeeds[$seed])) {
                    throw new InvalidArgumentException(
                        "Duplicate seed detected: {$seed}."
                    );
                }

                $usedSeeds[$seed] = true;
            }
        }

        $nextSeed = 1;

        foreach ($participants as &$participant) {
            if (!empty($participant['seed'])) {
                continue;
            }

            while (isset($usedSeeds[$nextSeed])) {
                $nextSeed++;
            }

            $participant['seed'] = $nextSeed;
            $usedSeeds[$nextSeed] = true;

            $nextSeed++;
        }

        unset($participant);

        usort(
            $participants,
            static fn(array $a, array $b): int =>
                $a['seed'] <=> $b['seed']
        );

        return $participants;
    }

    /**
     * Build a seed => participant map.
     *
     * @param array<int, array{id:int, seed:int}> $participants
     *
     * @return array<int, array>
     */
    private function buildSeedMap(array $participants): array
    {
        $map = [];

        foreach ($participants as $participant) {
            $map[(int) $participant['seed']] = $participant;
        }

        return $map;
    }

    /**
     * Generate standard tournament seed order.
     *
     * Examples:
     *
     * 2  => [1, 2]
     * 4  => [1, 4, 2, 3]
     * 8  => [1, 8, 4, 5, 2, 7, 3, 6]
     */
    private function generateSeedOrder(int $size): array
    {
        $this->validator->validateBracketSize($size);

        $order = [1, 2];

        while (count($order) < $size) {
            $nextSize = count($order) * 2;
            $nextOrder = [];

            foreach ($order as $seed) {
                $nextOrder[] = $seed;
                $nextOrder[] = $nextSize + 1 - $seed;
            }

            $order = $nextOrder;
        }

        return $order;
    }

    /**
     * Build first-round pairings.
     *
     * @param array<int, array> $seedMap
     * @param array<int, int> $seedOrder
     *
     * @return array<int, array{
     *     a: array|null,
     *     b: array|null
     * }>
     */
    private function buildPairings(
        array $seedMap,
        array $seedOrder
    ): array {
        $pairings = [];

        for ($i = 0, $count = count($seedOrder); $i < $count; $i += 2) {
            $seedA = $seedOrder[$i];
            $seedB = $seedOrder[$i + 1];

            $pairings[] = [
                'a' => $seedMap[$seedA] ?? null,
                'b' => $seedMap[$seedB] ?? null,
            ];
        }

        return $pairings;
    }

    /**
     * Get the next power of two.
     */
    private function getNextPowerOfTwo(int $number): int
    {
        $power = 1;

        while ($power < $number) {
            $power *= 2;
        }

        return $power;
    }
}