<?php

declare(strict_types=1);

namespace Blax\WebRtc\Rooms;

use Blax\WebRtc\Contracts\RoomStore;
use Blax\WebRtc\Rooms\Stores\ArrayRoomStore;

/**
 * Tracks which peers are in which room for party-to-party / group calls, and
 * answers the mesh question the signaling layer needs: "who is a new joiner
 * already in a call with?" Empty rooms are dropped automatically.
 *
 * Membership lives in a pluggable {@see RoomStore}: the default {@see ArrayRoomStore}
 * keeps it in memory (bundled server), while a fork-per-message host binds a
 * Cache/Redis-backed store so state survives across workers. The public API is
 * unchanged either way.
 */
final class RoomManager
{
    private RoomStore $store;

    public function __construct(?RoomStore $store = null)
    {
        $this->store = $store ?? new ArrayRoomStore;
    }

    public function store(): RoomStore
    {
        return $this->store;
    }

    /**
     * Join $peerId to $roomId (with optional presence $info), returning the OTHER
     * peers already present so the caller can mesh / notify them. The room's type
     * is derived from its name (RoomType::fromName) by {@see room()}.
     *
     * @param  array<string,mixed>  $info
     * @return array<int,string>
     */
    public function join(string $roomId, string $peerId, array $info = []): array
    {
        // Moving between rooms: leave the old one first.
        $current = $this->store->roomOf($peerId);
        if ($current !== null && $current !== $roomId) {
            $this->leave($peerId);
        }

        $others = $this->store->members($roomId);
        $this->store->add($roomId, $peerId, $info);

        return $others;
    }

    /**
     * Remove $peerId from its room, returning the peers that REMAIN there (so the
     * caller can notify them). Drops the room when it empties.
     *
     * @return array<int,string>
     */
    public function leave(string $peerId): array
    {
        $roomId = $this->store->roomOf($peerId);
        if ($roomId === null) {
            return [];
        }

        $this->store->remove($roomId, $peerId);

        return $this->store->members($roomId);
    }

    public function roomOf(string $peerId): ?string
    {
        return $this->store->roomOf($peerId);
    }

    public function room(string $roomId): ?Room
    {
        $members = $this->store->members($roomId);
        if ($members === []) {
            return null;
        }

        $room = new Room($roomId, RoomType::fromName($roomId));
        foreach ($members as $peerId) {
            $room->add($peerId, $this->store->info($roomId, $peerId));
        }

        return $room;
    }

    /** @return array<int,string> */
    public function peersIn(string $roomId): array
    {
        return $this->store->members($roomId);
    }

    public function count(): int
    {
        return count($this->store->roomIds());
    }
}
