<?php

declare(strict_types=1);

namespace App\Framework\View;

use ArrayIterator;
use Countable;
use Dom\Element;
use IteratorAggregate;

/**
 * Funktionale Sammlung für DOM-Elemente mit moderner API
 */
final class ElementCollection implements IteratorAggregate, Countable
{
    public function __construct(
        private array $elements,
        private DomWrapper $wrapper
    ) {
    }

    /**
     * Führt eine Funktion für jedes Element aus
     */
    public function forEach(callable $callback): self
    {
        foreach ($this->elements as $index => $element) {
            $callback($element, $index);
        }

        return $this;
    }

    /**
     * Transformiert jedes Element und gibt ein Array zurück
     */
    public function map(callable $callback): array
    {
        return array_map($callback, $this->elements);
    }

    /**
     * Filtert Elemente basierend auf einer Bedingung
     */
    public function filter(callable $callback): self
    {
        return new self(
            array_values(array_filter($this->elements, $callback)),
            $this->wrapper
        );
    }

    /**
     * Reduziert die Sammlung zu einem einzelnen Wert
     */
    public function reduce(callable $callback, mixed $initial = null): mixed
    {
        return array_reduce($this->elements, $callback, $initial);
    }

    /**
     * Prüft ob mindestens ein Element die Bedingung erfüllt
     */
    public function some(callable $callback): bool
    {
        foreach ($this->elements as $element) {
            if ($callback($element)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Prüft ob alle Elemente die Bedingung erfüllen
     */
    public function every(callable $callback): bool
    {
        foreach ($this->elements as $element) {
            if (! $callback($element)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Findet das erste Element, das die Bedingung erfüllt
     */
    public function find(callable $callback): ?Element
    {
        foreach ($this->elements as $element) {
            if ($callback($element)) {
                return $element;
            }
        }

        return null;
    }

    /**
     * Gibt das erste Element zurück
     */
    public function first(): ?Element
    {
        return $this->elements[0] ?? null;
    }

    /**
     * Gibt das letzte Element zurück
     */
    public function last(): ?Element
    {
        return end($this->elements) ?: null;
    }

    /**
     * Gibt das Element an der angegebenen Position zurück
     */
    public function at(int $index): ?Element
    {
        return $this->elements[$index] ?? null;
    }

    /**
     * Nimmt die ersten n Elemente
     */
    public function take(int $count): self
    {
        return new self(
            array_slice($this->elements, 0, $count),
            $this->wrapper
        );
    }

    /**
     * Überspringt die ersten n Elemente
     */
    public function skip(int $count): self
    {
        return new self(
            array_slice($this->elements, $count),
            $this->wrapper
        );
    }

    /**
     * Fügt CSS-Klasse zu allen Elementen hinzu
     */
    public function addClass(string $className): self
    {
        return $this->forEach(fn ($el) => $this->wrapper->addClass($el, $className));
    }

    /**
     * Entfernt CSS-Klasse von allen Elementen
     */
    public function removeClass(string $className): self
    {
        return $this->forEach(fn ($el) => $this->wrapper->removeClass($el, $className));
    }

    /**
     * Setzt Attribut für alle Elemente
     */
    public function setAttribute(string $name, string $value): self
    {
        return $this->forEach(fn ($el) => $el->setAttribute($name, $value));
    }

    /**
     * Entfernt Attribut von allen Elementen
     */
    public function removeAttribute(string $name): self
    {
        return $this->forEach(fn ($el) => $el->removeAttribute($name));
    }

    /**
     * Setzt innerHTML für alle Elemente
     */
    public function setInnerHTML(string $html): self
    {
        return $this->forEach(fn ($el) => $el->innerHTML = $html);
    }

    /**
     * Setzt textContent für alle Elemente
     */
    public function setTextContent(string $text): self
    {
        return $this->forEach(fn ($el) => $el->textContent = $text);
    }

    /**
     * Ersetzt alle Elemente mit HTML-Content
     */
    public function replaceWith(string $html): void
    {
        $this->forEach(fn ($el) => $this->wrapper->replaceElementWithHtml($el, $html));
    }

    /**
     * Entfernt alle Elemente aus dem DOM
     */
    public function remove(): void
    {
        $this->forEach(fn ($el) => $el->parentNode?->removeChild($el));
    }

    /**
     * Kombiniert diese Sammlung mit einer anderen
     */
    public function merge(ElementCollection $other): self
    {
        return new self(
            array_merge($this->elements, $other->elements),
            $this->wrapper
        );
    }

    /**
     * Gibt eindeutige Elemente zurück
     */
    public function unique(): self
    {
        return new self(
            array_values(array_unique($this->elements, SORT_REGULAR)),
            $this->wrapper
        );
    }

    /**
     * Prüft ob die Sammlung leer ist
     */
    public function isEmpty(): bool
    {
        return empty($this->elements);
    }

    /**
     * Prüft ob die Sammlung nicht leer ist
     */
    public function isNotEmpty(): bool
    {
        return ! $this->isEmpty();
    }

    /**
     * Konvertiert zu Array
     */
    public function toArray(): array
    {
        return $this->elements;
    }

    /**
     * Gibt die Anzahl der Elemente zurück
     */
    public function count(): int
    {
        return count($this->elements);
    }

    /**
     * Iterator für foreach-Schleifen
     */
    public function getIterator(): ArrayIterator
    {
        return new ArrayIterator($this->elements);
    }

    /**
     * Debug-Information
     */
    public function __debugInfo(): array
    {
        return [
            'count' => $this->count(),
            'elements' => $this->elements,
        ];
    }
}
