<?php

declare(strict_types=1);

namespace App\Framework\Console;

class ProgressBar
{
    private ConsoleOutputInterface $output;

    private int $total;

    private int $current = 0;

    private int $width;

    private float $startTime;

    private string $format = '%bar% %percent%%';

    private string $barChar = '=';

    private string $emptyBarChar = '-';

    private string $progressChar = '>';

    private int $redrawFrequency = 1;

    private int $writeCount = 0;

    private bool $firstRun = true;

    public function __construct(ConsoleOutputInterface $output, int $total = 100, int $width = 50)
    {
        $this->output = $output;
        $this->total = max(1, $total);
        $this->width = $width;
        $this->startTime = microtime(true);
    }

    /**
     * Setzt das Format der Fortschrittsanzeige
     * Verfügbare Platzhalter:
     * - %bar%: Die Fortschrittsanzeige
     * - %current%: Aktueller Fortschritt
     * - %total%: Gesamtwert
     * - %percent%: Prozentsatz des Fortschritts
     * - %elapsed%: Verstrichene Zeit in Sekunden
     * - %remaining%: Geschätzte verbleibende Zeit in Sekunden
     */
    public function setFormat(string $format): self
    {
        $this->format = $format;

        return $this;
    }

    /**
     * Setzt die Zeichen für die Fortschrittsanzeige
     */
    public function setBarCharacters(string $barChar = '=', string $emptyBarChar = '-', string $progressChar = '>'): self
    {
        $this->barChar = $barChar;
        $this->emptyBarChar = $emptyBarChar;
        $this->progressChar = $progressChar;

        return $this;
    }

    /**
     * Setzt die Häufigkeit der Aktualisierung
     */
    public function setRedrawFrequency(int $frequency): self
    {
        $this->redrawFrequency = max(1, $frequency);

        return $this;
    }

    /**
     * Erhöht den Fortschritt um den angegebenen Schritt
     */
    public function advance(int $step = 1): self
    {
        $this->setCurrent($this->current + $step);

        return $this;
    }

    /**
     * Setzt den aktuellen Fortschritt
     */
    public function setCurrent(int $current): self
    {
        $this->current = min($this->total, max(0, $current));

        $this->writeCount++;
        if ($this->writeCount >= $this->redrawFrequency || $this->firstRun || $this->current >= $this->total) {
            $this->display();
            $this->writeCount = 0;
        }

        return $this;
    }

    /**
     * Startet die Fortschrittsanzeige
     */
    public function start(): self
    {
        $this->startTime = microtime(true);
        $this->current = 0;
        $this->firstRun = true;
        $this->display();

        return $this;
    }

    /**
     * Beendet die Fortschrittsanzeige
     */
    public function finish(): self
    {
        if ($this->current < $this->total) {
            $this->current = $this->total;
        }

        $this->display();
        $this->output->newLine(2);

        return $this;
    }

    /**
     * Zeigt die Fortschrittsanzeige an
     */
    private function display(): void
    {
        if (! $this->firstRun) {
            // Bewege den Cursor eine Zeile nach oben
            $this->output->write("\033[1A");
            // Lösche die aktuelle Zeile
            $this->output->write("\033[2K");
        }

        $this->firstRun = false;

        $percent = $this->current / $this->total;
        $bar = $this->getProgressBar($percent);

        $replacements = [
            '%bar%' => $bar,
            '%current%' => (string) $this->current,
            '%total%' => (string) $this->total,
            '%percent%' => number_format($percent * 100, 0),
            '%elapsed%' => $this->getElapsedTime(),
            '%remaining%' => $this->getRemaining($percent),
        ];

        $line = str_replace(array_keys($replacements), array_values($replacements), $this->format);

        $this->output->writeLine($line);
    }

    /**
     * Generiert die Fortschrittsanzeige
     */
    private function getProgressBar(float $percent): string
    {
        $completedWidth = (int) floor($percent * $this->width);
        $emptyWidth = $this->width - $completedWidth - ($completedWidth < $this->width ? 1 : 0);

        $bar = str_repeat($this->barChar, $completedWidth);
        if ($completedWidth < $this->width) {
            $bar .= $this->progressChar . str_repeat($this->emptyBarChar, $emptyWidth);
        }

        return $bar;
    }

    /**
     * Berechnet die verstrichene Zeit
     */
    private function getElapsedTime(): string
    {
        return number_format(microtime(true) - $this->startTime, 1);
    }

    /**
     * Berechnet die verbleibende Zeit
     */
    private function getRemaining(float $percent): string
    {
        if ($percent === 0) {
            return '--';
        }

        $elapsed = microtime(true) - $this->startTime;
        $remaining = $elapsed / $percent - $elapsed;

        return number_format($remaining, 1);
    }
}
