<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\Log;

/**
 * The one place the shop's two incompatible log scales are translated into a single vocabulary.
 *
 * `tjtllog.nLevel` is a Monolog level — 100 debug, 200 info, 300 warning, 400+ error, and the
 * backend's own filter uses exactly those breakpoints
 * (`Router/Controller/Backend/SystemLogController.php:96-107`).
 *
 * `tzahlungslog.nLevel` runs the OTHER WAY: its column comment is `'1=Error, 2=Notice, 3=Debug'`
 * (`initial_schema.sql:5158`). A "level >= warning" filter is therefore `nLevel >= 300` on one
 * table and `nLevel <= 1` on the other. Getting that backwards is the single most likely bug in
 * the log layer, so it lives here, once, with a test — and callers never see a raw int except as
 * `rawLevel`.
 *
 * Pure: no DB, no shop container.
 */
final class LogLevel
{
    public const DEBUG   = 'debug';
    public const INFO    = 'info';
    public const WARNING = 'warning';
    public const ERROR   = 'error';

    /** Least severe first — the order every "at least this severe" comparison uses. */
    public const ORDER = [self::DEBUG, self::INFO, self::WARNING, self::ERROR];

    /** Monolog breakpoints, most severe first. */
    private const MONOLOG = [400 => self::ERROR, 300 => self::WARNING, 200 => self::INFO];

    /** tzahlungslog's own scale, most severe first. Note there is no warning in it. */
    private const PAYMENT = [1 => self::ERROR, 2 => self::INFO, 3 => self::DEBUG];

    public static function isValid(string $level): bool
    {
        return \in_array($level, self::ORDER, true);
    }

    /** Severity rank, 0 = debug. */
    public static function rank(string $level): int
    {
        $rank = \array_search($level, self::ORDER, true);

        return $rank === false ? 0 : $rank;
    }

    /** `tjtllog.nLevel` -> normalised level. Anything under 200 is debug, anything over 400 error. */
    public static function fromMonolog(int $raw): string
    {
        foreach (self::MONOLOG as $threshold => $level) {
            if ($raw >= $threshold) {
                return $level;
            }
        }

        return self::DEBUG;
    }

    /**
     * `tzahlungslog.nLevel` -> normalised level. Out-of-range values are treated as debug rather
     * than guessed: the scale has exactly three members and a fourth would be data corruption.
     */
    public static function fromPayment(int $raw): string
    {
        return self::PAYMENT[$raw] ?? self::DEBUG;
    }

    /**
     * SQL bound for "at least this severe" on a Monolog column: `nLevel >= N`.
     * `error` -> 400, `warning` -> 300, `info` -> 200, `debug` -> 0.
     */
    public static function monologAtLeast(string $level): int
    {
        return match ($level) {
            self::ERROR   => 400,
            self::WARNING => 300,
            self::INFO    => 200,
            default       => 0,
        };
    }

    /**
     * SQL bound for "at least this severe" on `tzahlungslog`: `nLevel <= N`, because the scale is
     * inverted. `warning` collapses onto `error` (1) — the payment scale has no warning, and
     * silently returning notices for a warning filter would be a lie.
     */
    public static function paymentAtMost(string $level): int
    {
        return match ($level) {
            self::ERROR, self::WARNING => 1,
            self::INFO                 => 2,
            default                    => 3,
        };
    }
}
