<?php

declare(strict_types=1);

namespace Duplicator\Core\Exceptions;

use Exception;
use Throwable;

/**
 * Single domain exception for Duplicator. No subclasses: the failure type is the
 * error code (CODE_*), not a class. `message` stays raw (log + telemetry hash);
 * `userMessage` is the optional translated/formatted text for display.
 */
class DupliException extends Exception
{
    const SEVERITY_ERROR = 'error';

    const CODE_ERROR          = 0;
    const CODE_MAX_BUILD_TIME = 1;
    const CODE_STUCK          = 2;

    const CODE_SCAN_READ_FAILED    = 100;
    const CODE_SCAN_INVALID_REPORT = 101;
    const CODE_SCAN_FAILED         = 102;
    const CODE_SCAN_INDEX_INVALID  = 103;
    const CODE_SCAN_WRITE_FAILED   = 104;

    const CODE_ZIP_NOT_AVAILABLE   = 109;
    const CODE_ZIP_OPEN_FAILED     = 110;
    const CODE_ZIP_CLOSE_FAILED    = 111;
    const CODE_ZIP_FILE_OVERFLOW   = 112;
    const CODE_ZIP_RETRY_EXHAUSTED = 113;

    const CODE_SHELL_ZIP_FAILED            = 120;
    const CODE_SHELL_ZIP_QUOTA             = 121;
    const CODE_SHELL_ZIP_FILE_NOT_FOUND    = 122;
    const CODE_SHELL_ZIP_FILE_COUNT_FAILED = 123;
    const CODE_SHELL_ZIP_RETRY_EXHAUSTED   = 124;

    const CODE_DB_VALIDATION_FAILED    = 130;
    const CODE_DB_CREATE_QUERY_FAILED  = 131;
    const CODE_DB_FILE_OPEN_FAILED     = 132;
    const CODE_DB_FILE_TRUNCATE_FAILED = 133;
    const CODE_DB_EMPTY_FILE           = 134;
    const CODE_DB_RETRY_EXHAUSTED      = 135;
    const CODE_DB_PROGRESS_FILE_FAILED = 136;
    const CODE_DB_PHP_DUMP_INTERRUPTED = 137;

    const CODE_MYSQLDUMP_FAILED            = 140;
    const CODE_MYSQLDUMP_FILE_WRITE_FAILED = 141;
    const CODE_MYSQLDUMP_UNAVAILABLE       = 142;
    const CODE_MYSQLDUMP_INTERRUPTED       = 143;

    const CODE_DUP_ARCHIVE_ADD_FAILED        = 150;
    const CODE_DUP_ARCHIVE_VALIDATION_FAILED = 151;
    const CODE_DUP_ARCHIVE_32BIT_LIMIT       = 152;
    const CODE_DUP_ARCHIVE_RETRY_EXHAUSTED   = 153;
    const CODE_DUP_ARCHIVE_TRUNCATE_FAILED   = 154;

    const CODE_INTEGRITY_DB_INCOMPLETE          = 160;
    const CODE_INTEGRITY_INSTALLER_INCOMPLETE   = 161;
    const CODE_INTEGRITY_ARCHIVE_EMPTY          = 162;
    const CODE_INTEGRITY_SCANFILE_MISSING       = 163;
    const CODE_INTEGRITY_FILE_COUNT_MISMATCH    = 164;
    const CODE_INTEGRITY_DB_TOO_SMALL           = 165;
    const CODE_INTEGRITY_DB_FILE_MISSING        = 166;
    const CODE_INTEGRITY_INSTALLER_FILE_MISSING = 167;

    const CODE_ENCRYPTION_UNAVAILABLE       = 170;
    const CODE_STORAGE_INVALID              = 171;
    const CODE_INSTALLER_BUILD_FAILED       = 172;
    const CODE_INSTALLER_ADD_FAILED         = 173;
    const CODE_INSTALLER_CONSISTENCY_FAILED = 174;

    /** @var string */
    protected $userMessage = '';

    /**
     * @param string         $message     Raw English text for log/telemetry hash, never localized
     * @param int            $code        One of the CODE_* constants
     * @param string         $userMessage Optional translated/formatted message for the end user
     * @param Throwable|null $previous    Previous throwable for chaining
     */
    public function __construct(
        string $message = '',
        int $code = self::CODE_ERROR,
        string $userMessage = '',
        ?Throwable $previous = null
    ) {
        parent::__construct($message, $code, $previous);
        $this->userMessage = $userMessage;
    }

    /**
     * @return string one of the SEVERITY_* constants
     */
    public function getSeverity(): string
    {
        return self::SEVERITY_ERROR;
    }

    /**
     * Message for display: the translated/formatted userMessage when set,
     * otherwise the raw message as a fallback.
     *
     * @return string
     */
    public function getUserMessage(): string
    {
        return $this->userMessage !== '' ? $this->userMessage : $this->getMessage();
    }
}
