<?php

namespace App\Framework\ErrorHandling;

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

/**
 * Stacktrace-Klasse zum Aufbereiten und Darstellen von Exception-Traces
 * Implementiert ArrayAccess, IteratorAggregate und Countable für einfachen Zugriff in Templates
 */
final class StackTrace implements ArrayAccess, IteratorAggregate, Countable
{
    /** @var TraceItem[] */
    private(set) array $items = [];

    /**
     * Erstellt ein neues StackTrace-Objekt aus einer Exception
     */
    public function __construct(private readonly \Throwable $exception)
    {
        $this->processTrace($exception->getTrace());
    }

    /**
     * Verarbeitet die rohen Trace-Daten in strukturierte TraceItems
     */
    private function processTrace(array $trace): void
    {
        // Füge den Ursprung der Exception hinzu (file/line aus Exception selbst)
        $this->items[] = new TraceItem(
            file: $this->exception->getFile(),
            line: $this->exception->getLine(),
            function: null,
            class: null,
            type: null,
            args: [],
            isExceptionOrigin: true
        );

        // Verarbeite den Rest des Stacktraces
        foreach ($trace as $index => $frame) {
            $this->items[] = new TraceItem(
                file: $frame['file'] ?? '(internal function)',
                line: $frame['line'] ?? 0,
                function: $frame['function'] ?? null,
                class: $frame['class'] ?? null,
                type: $frame['type'] ?? null,
                args: $frame['args'] ?? [],
                isExceptionOrigin: false
            );
        }

        $this->items = array_reverse($this->items);
    }

    /**
     * Gibt alle Trace-Items zurück
     *
     * @return TraceItem[]
     */
    public function getItems(): array
    {
        return $this->items;
    }

    /**
     * Gibt den Ursprung der Exception zurück (erstes Item)
     */
    public function getOrigin(): ?TraceItem
    {
        return $this->items[0] ?? null;
    }

    /**
     * Gibt die ersten N Elemente des Stacktraces zurück
     */
    public function getFirst(int $count): array
    {
        return array_slice($this->items, 0, $count);
    }

    /**
     * Gibt die letzten N Elemente des Stacktraces zurück
     */
    public function getLast(int $count): array
    {
        return array_slice($this->items, -$count);
    }

    /**
     * Filtert den Stacktrace nach bestimmten Kriterien
     */
    public function filter(callable $callback): array
    {
        return array_filter($this->items, $callback);
    }

    /**
     * Implementierung von ArrayAccess
     */
    public function offsetExists(mixed $offset): bool
    {
        return isset($this->items[$offset]);
    }

    public function offsetGet(mixed $offset): mixed
    {
        return $this->items[$offset] ?? null;
    }

    public function offsetSet(mixed $offset, mixed $value): void
    {
        // Read-only
    }

    public function offsetUnset(mixed $offset): void
    {
        // Read-only
    }

    /**
     * Implementierung von IteratorAggregate
     */
    public function getIterator(): Traversable
    {
        return new ArrayIterator($this->items);
    }

    /**
     * Implementierung von Countable
     */
    public function count(): int
    {
        return count($this->items);
    }

    /**
     * Formatiert den gesamten Stacktrace als String
     */
    public function __toString(): string
    {
        $result = "";
        foreach ($this->items as $index => $item) {
            $result .= "#{$index} {$item}\n";
        }
        return $result;
    }
}
