<?php

namespace Planaday\Utility\DataGrouping;

use ArrayAccess;
use Countable;
use IteratorAggregate;
use Traversable;

/**
 * @template TKey
 * @template TValue
 */
class LazyCollection implements ArrayAccess, IteratorAggregate, Countable {
	/** @var ArrayAccess<TKey, TValue>&Traversable<TKey, TValue>&Countable */
	protected ArrayAccess&Traversable&Countable $data;

	/** @var callable():(ArrayAccess<TKey, TValue>&Traversable<TKey, TValue>&Countable) */
	protected $loader;

	protected bool $isLoaded = false;

	/**
	 * @param callable():(ArrayAccess<TKey, TValue>&Traversable<TKey, TValue>&Countable)$loader
	 */
	public function __construct(
		callable $loader
	) {
		$this->loader = $loader;
	}

	protected function load(): void {
		if( $this->isLoaded ) return;

		$this->data = ($this->loader)();
		$this->isLoaded = true;
	}

	/**
	 * @param TKey $offset
	 *
	 * @return bool
	 */
	public function offsetExists( mixed $offset ): bool {
		$this->load();
		return $this->data->offsetExists($offset);
	}

	/**
	 * @param TKey $offset
	 *
	 * @return TValue|null
	 */
	public function offsetGet( mixed $offset ): mixed {
		$this->load();
		return $this->data->offsetGet($offset);
	}

	/**
	 * @param TKey $offset
	 * @param TValue $value
	 *
	 * @return void
	 */
	public function offsetSet( mixed $offset, mixed $value ): void {
		$this->load();
		$this->data->offsetSet($offset, $value);
	}

	/**
	 * @param TKey $offset
	 *
	 * @return void
	 */
	public function offsetUnset( mixed $offset ): void {
		$this->load();
		$this->data->offsetUnset($offset);
	}

	public function count(): int {
		$this->load();
		return count($this->data);
	}

	public function getIterator(): Traversable {
		$this->load();
		foreach( $this->data as $key => $value ) {
			yield $key => $value;
		}
	}
}