<?php

declare(strict_types=1);

namespace PHPNative\Core;

use ArrayIterator;
use IteratorAggregate;
use Traversable;

abstract class Collection implements IteratorAggregate
{
    public function __construct(
        private array $elements,
    ) {}

    public static function createEmpty(): static
    {
        return new static([]);
    }

    public static function fromMap(array $items, callable $fn): static
    {
        return new static(array_map($fn, $items));
    }

    public function reduce(callable $fn, mixed $initial): mixed
    {
        return array_reduce($this->elements, $fn, $initial);
    }

    public function map(callable $fn): array
    {
        return array_map($fn, $this->elements);
    }

    public function each(callable $fn): void
    {
        array_walk($this->elements, $fn);
    }

    public function some(callable $fn): bool
    {
        foreach ($this->elements as $index => $element) {
            if ($fn($element, $index, $this->elements)) {
                return true;
            }
        }

        return false;
    }

    public function setElements($elements): void
    {
        $this->elements = $elements;
    }

    public function filter(callable $fn): static
    {
        return new static(array_filter($this->elements, $fn, ARRAY_FILTER_USE_BOTH));
    }

    public function first(): mixed
    {
        return reset($this->elements);
    }

    public function last(): mixed
    {
        return end($this->elements);
    }

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

    public function isEmpty(): bool
    {
        return empty($this->elements);
    }

    public function add(mixed $element): void
    {
        $this->elements[] = $element;
    }

    public function values(): array
    {
        return array_values($this->elements);
    }

    public function items(): array
    {
        return $this->elements;
    }

    #[\Override]
    public function getIterator(): Traversable
    {
        return new ArrayIterator($this->elements);
    }

    public function removeFirstItem(): void
    {
        if (count($this->elements) > 0) {
            array_shift($this->elements);
        }
    }

    public function sort(callable $callback): void
    {
        usort($this->elements, $callback);
    }

    public function removeItem($item): void
    {
        $temp = $this->filter(function ($value, $key) use ($item) {
            return $value::class != $item::class;
        });

        $this->elements = $temp->items();
    }

    public function clear(): void
    {
        $this->elements = [];
    }
}
