<?php
namespace app\common\service;

use think\db\Expression;

/** Plain Nutrition.use_weight only; never FLOAT(M,D) or computed aggregates. */
final class NutritionPlainFloatWeight
{
    public static function forModel(array $row, $model): array
    {
        return self::mapFields($row, $model->db(false)->getConnection() instanceof \app\cores\database\Dm);
    }

    public static function mapFields(array $row, bool $dm): array
    {
        if (!$dm || !array_key_exists('use_weight', $row)) {
            return $row;
        }
        $original = $row['use_weight'];
        if ((!is_string($original) && !is_int($original) && !is_float($original))
            || !is_numeric($original) || !is_finite((float)$original)) {
            return $row;
        }
        // Plain FLOAT has binary32 capacity, without the FLOAT(8,2) decimal operation.
        $cap = 3.4028234663852886e38;
        $value = max(-$cap, min($cap, (float)$original));
        $promoted = unpack('g', pack('g', $value))[1];
        $text = self::decimal(sprintf('%.17g', $promoted));
        if (preg_match('/^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/D', $text) === 1) {
            $row['use_weight'] = new Expression('CAST(' . $text . ' AS REAL)');
        }
        return $row;
    }

    public static function physicalForModel(array $row, $model): array
    {
        return self::physicalRow($row, $model->db(false)->getConnection() instanceof \app\cores\database\Dm);
    }

    /** Call only with audited physical Nutrition.use_weight projections. */
    public static function physicalRow(array $row, bool $dm): array
    {
        if ($dm && array_key_exists('use_weight', $row)
            && is_float($row['use_weight']) && is_finite($row['use_weight'])) {
            // PHP 7.3 user sprintf %g and mysqlnd both invoke php_gcvt with FLT_DIG=6.
            $row['use_weight'] = (float)self::decimal(sprintf('%.6g', $row['use_weight']));
        }
        return $row;
    }

    private static function decimal(string $text): string
    {
        $decimal = localeconv()['decimal_point'];
        return $decimal === '.' ? $text : str_replace($decimal, '.', $text);
    }
}

