<?php

declare(strict_types=1);

namespace WhmcsMigrate;

/**
 * Tracks progress of long-running operations and renders a progress bar to the console.
 */
final class ProgressTracker
{
    private int $total;

    private int $current = 0;

    private float $startTime;

    private string $label;

    private int $barWidth = 40;

    private int $lastRenderedPercent = -1;

    public function __construct(string $label, int $total)
    {
        $this->label = $label;
        $this->total = max($total, 1);
        $this->startTime = microtime(true);
    }

    /**
     * Advance progress by the given amount.
     */
    public function advance(int $amount = 1): void
    {
        $this->current += $amount;
        $this->render();
    }

    /**
     * Set the current progress value.
     */
    public function setCurrent(int $current): void
    {
        $this->current = $current;
        $this->render();
    }

    /**
     * Mark the progress as complete and print a final summary line.
     */
    public function finish(): void
    {
        $this->current = $this->total;
        $this->render(force: true);
        fprintf(STDOUT, "\n");
    }

    /**
     * Render the progress bar to STDOUT.
     *
     * Only re-renders when the percentage changes (to avoid excessive terminal writes).
     */
    private function render(bool $force = false): void
    {
        $percent = min(100, (int) round(($this->current / $this->total) * 100));

        // Only re-render when the percentage changes or forced
        if (! $force && $percent === $this->lastRenderedPercent) {
            return;
        }

        $this->lastRenderedPercent = $percent;

        $filled = (int) round(($percent / 100) * $this->barWidth);
        $empty = $this->barWidth - $filled;

        $bar = str_repeat('=', max(0, $filled - 1));

        if ($filled > 0) {
            $bar .= '>';
        }

        $bar .= str_repeat(' ', $empty);

        // Calculate ETA
        $elapsed = microtime(true) - $this->startTime;
        $eta = '';
        $rate = '';

        if ($this->current > 0 && $elapsed > 0) {
            $recordsPerSecond = $this->current / $elapsed;
            $remaining = $this->total - $this->current;
            $etaSeconds = $recordsPerSecond > 0 ? $remaining / $recordsPerSecond : 0;

            $rate = sprintf('%.0f/s', $recordsPerSecond);
            $eta = $this->formatDuration((int) $etaSeconds);
        }

        $line = sprintf(
            "\r  %s [%s] %3d%% %s/%s %s%s",
            str_pad($this->label, 20),
            $bar,
            $percent,
            number_format($this->current),
            number_format($this->total),
            $rate !== '' ? "({$rate})" : '',
            $eta !== '' ? " ETA: {$eta}" : '',
        );

        // Pad to clear previous line remnants
        $line = str_pad($line, 120);

        fprintf(STDOUT, "%s", $line);
    }

    /**
     * Format a duration in seconds to a human-readable string.
     */
    private function formatDuration(int $seconds): string
    {
        if ($seconds <= 0) {
            return '< 1s';
        }

        if ($seconds < 60) {
            return "{$seconds}s";
        }

        $minutes = (int) floor($seconds / 60);
        $remainingSeconds = $seconds % 60;

        if ($minutes < 60) {
            return "{$minutes}m {$remainingSeconds}s";
        }

        $hours = (int) floor($minutes / 60);
        $remainingMinutes = $minutes % 60;

        return "{$hours}h {$remainingMinutes}m";
    }

    /**
     * Get the elapsed time since the tracker was started.
     */
    public function getElapsed(): string
    {
        $elapsed = (int) (microtime(true) - $this->startTime);

        return $this->formatDuration($elapsed);
    }

    /**
     * Get the records per second rate.
     */
    public function getRate(): float
    {
        $elapsed = microtime(true) - $this->startTime;

        return $elapsed > 0 ? $this->current / $elapsed : 0;
    }
}
