<?php

namespace Planaday\Utility\DataGrouping;

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

/**
 * @template T
 *
 * @implements ArrayAccess<int, T>
 * @implements IteratorAggregate<int, T>
 */
class Collection implements ArrayAccess, Countable, IteratorAggregate {
	/** @var T[] */
	protected array $contents = [];

	/**
	 * @param T ...$contents
	 */
	public function __construct( ...$contents ) {
		$this->contents = $contents;
	}

	/**
	 * @return Traversable<int, T>
	 */
	public function getIterator(): Traversable {
		return new ArrayIterator( $this->contents );
	}

	/**
	 * @param int $offset
	 */
	public function offsetExists( mixed $offset ): bool {
		return isset( $this->contents[ $offset] );
	}

	/**
	 * @param int $offset
	 *
	 * @return T|null
	 */
	public function offsetGet( mixed $offset ): mixed {
		return $this->contents[ $offset ] ?? null;
	}

	/**
	 * @param int $offset
	 * @param T $value
	 */
	public function offsetSet( mixed $offset, mixed $value ): void {
		if ( $offset === null ) {
			$this->contents[] = $value;
			return;
		}

		$this->contents[ $offset ] = $value;
	}

	/**
	 * @param int $offset
	 */
	public function offsetUnset( mixed $offset ): void {
		unset( $this->contents[ $offset ] );
	}

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

	/**
	 * @template K
	 * @template TFrom
	 * @param TFrom[]|array<K, TFrom> $array
	 *
	 * @return Collection<TFrom>
	 */
	public static function fromArray( array $array ): self {
		return self::fromIterable($array);
	}

	/**
	 * @template K
	 * @template TFrom
	 * @param TFrom[]|array<K, TFrom>|Traversable<K, TFrom> $array
	 *
	 * @return Collection<TFrom>
	 */
	public static function fromIterable( iterable $array ): self {
		return new self(...$array);
	}

	/**
	 * @return array<int, T>
	 */
	public function toArray(): array {
		return $this->contents;
	}

	/**
	 * @param T $value
	 */
	public function contains($value): bool{
		foreach ($this->contents as $content){
			if ($value === $content) return true;
		}
		return false;
	}

	/**
	 * @param T ...$values
	 */
	public function append(...$values): static{
		foreach ($values as $value){
			$this->contents[] = $value;
		}
		return $this;
	}

	/**
	 * @template Return
	 * @param callable(T):Return $callback
	 *
	 * @return iterable<int, Return>
	 */
	public function select(callable $callback): iterable{
		foreach ($this->contents as $content){
			yield $callback($content);
		}
	}
}