<?php

declare(strict_types=1);

namespace App\Framework\Logging\Processors;

use App\Framework\Logging\LogProcessor;
use App\Framework\Logging\LogRecord;

/**
 * Fügt Code-Ortsinformationen (Datei, Zeile, Funktion) zu Log-Einträgen hinzu.
 */
final class IntrospectionProcessor implements LogProcessor
{
    /**
     * @var int Priorität des Processors
     */
    private const int PRIORITY = 7;

    /**
     * @var string[] Liste der Klassen, die bei der Introspection ignoriert werden sollen
     */
    private array $skipClassesPartials;

    /**
     * @var int Anzahl der Frames, die übersprungen werden sollen (Logger usw.)
     */
    private int $skipStackFrames;

    /**
     * Erstellt einen neuen IntrospectionProcessor
     *
     * @param string[] $skipClassesPartials Liste der Klassen, die ignoriert werden sollen
     * @param int $skipStackFrames Anzahl der Stack-Frames, die übersprungen werden sollen
     */
    public function __construct(
        array $skipClassesPartials = ['App\\Framework\\Logging\\'],
        int $skipStackFrames = 0
    ) {
        $this->skipClassesPartials = $skipClassesPartials;
        $this->skipStackFrames = $skipStackFrames;
    }

    /**
     * Verarbeitet einen Log-Record und fügt Introspection-Daten hinzu
     */
    public function processRecord(LogRecord $record): LogRecord
    {
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);

        // Überspringe Stack-Frames, die zur Logger-Infrastruktur gehören
        $i = $this->skipStackFrames;
        $framesCount = count($trace);

        // Suche nach dem ersten Frame, der nicht zu ignorieren ist
        while ($i < $framesCount) {
            if (isset($trace[$i]['class'])) {
                foreach ($this->skipClassesPartials as $partial) {
                    if (str_starts_with($trace[$i]['class'], $partial)) {
                        $i++;

                        continue 2; // Zum nächsten Frame wechseln
                    }
                }
            }

            break; // Frame gefunden, der nicht ignoriert werden soll
        }

        // Sicherstellen, dass wir einen gültigen Frame haben
        if ($i >= $framesCount) {
            return $record; // Kein passender Frame gefunden
        }

        // Introspection-Daten extrahieren
        $frame = $trace[$i];
        $introspection = [
            'file' => $frame['file'] ?? 'unknown',
            'line' => $frame['line'] ?? 0,
            'class' => $frame['class'] ?? '',
            'function' => $frame['function'] ?? '',
        ];

        // Zu den Extra-Daten hinzufügen
        return $record->addExtra('introspection', $introspection);
    }

    /**
     * Gibt die Priorität des Processors zurück
     */
    public function getPriority(): int
    {
        return self::PRIORITY;
    }

    /**
     * Gibt einen eindeutigen Namen für den Processor zurück
     */
    public function getName(): string
    {
        return 'introspection';
    }
}
