<?php

/**
 * Trait for package progress tracking
 *
 * @package   Duplicator
 * @copyright (c) 2025, Snap Creek LLC
 */

declare(strict_types=1);

namespace Duplicator\Package;

use Duplicator\Libs\Snap\SnapString;
use Duplicator\Utils\Logging\DupLog;
use Exception;

/**
 * Trait for tracking backup progress through independent phases
 *
 * This trait provides runtime progress calculation based on build state data
 * rather than storing progress values. Each phase reports 0-100% completion
 * with descriptive messages.
 *
 * @phpstan-require-extends AbstractPackage
 */
trait TraitPackageProgress
{
    /**
     * Get current progress information
     *
     * Returns progress data for the current phase of backup creation.
     * Each phase tracks its own 0-100% completion independently.
     *
     * @return array{percent: float, message: string, phase: string, phaseName: string} Progress information
     *               - percent: 0-100 completion percentage of current phase
     *               - message: Detailed localized description of current operation
     *               - phase: Machine-readable phase identifier
     *               - phaseName: Human-readable phase name for display
     *
     * @throws Exception If not called on AbstractPackage instance
     */
    public function getProgress(): array
    {
        if (!$this instanceof AbstractPackage) {
            throw new Exception('TraitPackageProgress can only be used on AbstractPackage instances');
        }

        $status = $this->getStatus();

        if ($status >= AbstractPackage::STATUS_PRE_PROCESS && $status < AbstractPackage::STATUS_DBSTART) {
            return $this->getInitProgress();
        }

        if ($status >= AbstractPackage::STATUS_DBSTART && $status < AbstractPackage::STATUS_ARCSTART) {
            return $this->getDatabaseProgress();
        }

        if ($status >= AbstractPackage::STATUS_ARCSTART && $status < AbstractPackage::STATUS_STORAGE_PROCESSING) {
            return $this->getArchiveProgress();
        }

        if ($status === AbstractPackage::STATUS_STORAGE_PROCESSING) {
            return $this->getStorageProgress();
        }

        // Complete state
        if ($status === AbstractPackage::STATUS_COMPLETE) {
            return [
                'percent'   => 100.0,
                'message'   => __('Backup package has been successfully created and is ready for use', 'duplicator-pro'),
                'phase'     => 'complete',
                'phaseName' => __('Backup Complete', 'duplicator-pro'),
            ];
        }

        // Error/cancelled states
        if ($status < 0) {
            return [
                'percent'   => 0.0,
                'message'   => __('An error occurred during backup creation. Please check the logs for details', 'duplicator-pro'),
                'phase'     => 'error',
                'phaseName' => __('Backup Failed', 'duplicator-pro'),
            ];
        }

        // Fallback for unknown status
        return [
            'percent'   => 0.0,
            'message'   => __('Processing backup operation', 'duplicator-pro'),
            'phase'     => 'unknown',
            'phaseName' => __('Processing', 'duplicator-pro'),
        ];
    }

    /**
     * Get initialization phase progress
     *
     * Initialization is nearly instantaneous, so progress remains at 0%
     * until database export begins.
     *
     * @return array{percent: float, message: string, phase: string, phaseName: string}
     */
    protected function getInitProgress(): array
    {
        return [
            'percent'   => 0.0,
            'message'   => __('Initializing backup configuration and preparing for database export', 'duplicator-pro'),
            'phase'     => 'init',
            'phaseName' => __('Initializing Backup', 'duplicator-pro'),
        ];
    }

    /**
     * Get database export phase progress
     *
     * Calculates progress based on row counters in db_build_progress.
     * Progress = (rows dumped / total rows) × 100
     *
     * @return array{percent: float, message: string, phase: string, phaseName: string}
     */
    protected function getDatabaseProgress(): array
    {
        $percent = 0.0;

        $totalRows  = $this->db_build_progress->countCheckData['impreciseTotalRows'];
        $dumpedRows = $this->db_build_progress->countCheckData['countTotal'];

        if ($totalRows > 0) {
            $percent = ($dumpedRows / $totalRows) * 100.0;
            // Ensure percentage stays within 0-100 range
            $percent = max(0.0, min(100.0, $percent));
        }


        return [
            'percent'   => $percent,
            'message'   => __('Exporting database tables and content to SQL file', 'duplicator-pro'),
            'phase'     => 'database',
            'phaseName' => __('Database Export', 'duplicator-pro'),
        ];
    }

    /**
     * Get archive creation phase progress
     *
     * Calculates progress based on bytes processed vs expected total size.
     * Progress = (bytes processed / expected size) × 100
     *
     * @return array{percent: float, message: string, phase: string, phaseName: string}
     */
    protected function getArchiveProgress(): array
    {
        $expectedSize  = (int) $this->build_progress->expected_archive_size;
        $processedSize = (int) $this->build_progress->processed_archive_size;

        $percent = 0.0;
        if ($expectedSize > 0) {
            $percent = ($processedSize / $expectedSize) * 100.0;
            $percent = max(0.0, min(100.0, $percent));
        }

        DupLog::infoTrace(
            'Archive progress - Size: ' .
            SnapString::byteSize($processedSize) . '/' . SnapString::byteSize($expectedSize) .
            ' (' . round($percent, 1) . '%)'
        );

        return [
            'percent'   => $percent,
            'message'   => __('Compressing files and folders into backup archive', 'duplicator-pro'),
            'phase'     => 'archive',
            'phaseName' => __('Backup Creation', 'duplicator-pro'),
        ];
    }

    /**
     * Get storage transfer phase progress
     *
     * Calculates progress for current active storage upload.
     * For multiple storages, shows individual progress per storage
     * with position in queue: "Upload [Name] [%]% [Storage X of Y]"
     *
     * @return array{percent: float, message: string, phase: string, phaseName: string}
     */
    protected function getStorageProgress(): array
    {
        $result =  [
            'percent'   => 0.0,
            'message'   => __('Transferring backup to storage locations', 'duplicator-pro'),
            'phase'     => 'storage',
            'phaseName' => __('Storage Transfer', 'duplicator-pro'),
        ];

        if (empty($this->upload_infos)) {
            return $result;
        }

        // Find active upload: hasStarted() && !hasCompleted()
        $activeUploadInfo = null;
        $activeIndex      = 0;

        foreach ($this->upload_infos as $index => $uploadInfo) {
            if ($uploadInfo->hasStarted() && !$uploadInfo->hasCompleted()) {
                $activeUploadInfo = $uploadInfo;
                $activeIndex      = $index;
                break;
            }
        }

        // No active storage found - return 0%
        if ($activeUploadInfo === null) {
            return $result;
        }

        $percent     = (float) $activeUploadInfo->progress;
        $percent     = max(0.0, min(100.0, $percent)); // Clamp to 0-100
        $storageName = $activeUploadInfo->getStorage()->getName();
        $position    = $activeIndex + 1;
        $total       = count($this->upload_infos);

        if ($total > 1) {
            $message = sprintf(
                __('Uploading backup to <b>%1$s</b> [Storage %2$d of %3$d]', 'duplicator-pro'),
                $storageName,
                $position,
                $total
            );
        } else {
            $message = sprintf(
                __('Uploading backup to <b>%1$s</b>', 'duplicator-pro'),
                $storageName
            );
        }
        $result['message'] = $message;
        $result['percent'] = $percent;
        return $result;
    }

    /**
     * Get status progress percentage (deprecated wrapper)
     *
     * @return float Progress percentage (0-100)
     *
     * @deprecated Use getProgress()['percent'] instead. This method is maintained for backward compatibility.
     */
    public function getStatusProgress(): float
    {
        return $this->getProgress()['percent'];
    }

    /**
     * Get status progress text message (deprecated wrapper)
     *
     * @return string Localized progress message
     *
     * @deprecated Use getProgress()['message'] instead. This method is maintained for backward compatibility.
     */
    public function getStatusProgressText(): string
    {
        return $this->getProgress()['message'];
    }

    /**
     * Get progress percentage
     *
     * @return float Progress percentage (0-100)
     *
     * @deprecated Use getProgress()['percent'] instead. This method is maintained for backward compatibility.
     */
    public function getProgressPercent(): float
    {
        return $this->getProgress()['percent'];
    }

    /**
     * Get build size for display
     *
     * @return string Formatted size or "Building..." when file doesn't exist yet
     */
    public function getBuildSize(): string
    {
        if (!$this instanceof AbstractPackage) {
            throw new Exception('TraitPackageProgress can only be used on AbstractPackage instances');
        }

        $status = $this->getStatus();

        // If error, return 0
        if ($status < 0) {
            return SnapString::byteSize(0);
        }

        // If complete, use final stored size
        if ($status == AbstractPackage::STATUS_COMPLETE || $this->transferWasInterrupted()) {
            return SnapString::byteSize($this->Archive->Size);
        }

        // For all other states, read current file size from disk
        $size              = 0;
        $temp_archive_path = DUPLICATOR_SSDIR_PATH_TMP . '/' . $this->getArchiveFilename();
        $archive_path      = DUPLICATOR_SSDIR_PATH . '/' . $this->getArchiveFilename();

        if (file_exists($archive_path)) {
            $size = @filesize($archive_path);
        } elseif (file_exists($temp_archive_path)) {
            $size = @filesize($temp_archive_path);
        }

        // If size is 0, archive doesn't exist or isn't accessible yet (e.g., Shell Zip creating)
        if ($size === 0) {
            return __('Building...', 'duplicator-pro');
        }

        return SnapString::byteSize($size);
    }

    /**
     * Get display size (deprecated wrapper)
     *
     * @return string Formatted size string
     *
     * @deprecated Use getBuildSize() instead. This method is maintained for backward compatibility.
     */
    public function getDisplaySize(): string
    {
        return $this->getBuildSize();
    }
}
