<?php
declare(strict_types=1);

/** Test evidence only. Never use this encoder to change the application's HTTP contract. */
final class DmReceiptJson
{
    public static function encode($value): string
    {
        self::validate($value);
        $previous = ini_get('serialize_precision');
        ini_set('serialize_precision', '-1');
        try {
            $json = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
                | JSON_PRESERVE_ZERO_FRACTION | JSON_THROW_ON_ERROR);
            $decoded = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
            if (!self::same($value, $decoded)) {
                throw new LogicException('Evidence JSON round-trip changed a value or type');
            }
            return $json;
        } finally {
            ini_set('serialize_precision', (string)$previous);
        }
    }

    private static function validate($value): void
    {
        if (is_array($value)) {
            foreach ($value as $child) self::validate($child);
        } elseif (is_float($value)) {
            if (!is_finite($value)) throw new InvalidArgumentException('Represent nonfinite evidence explicitly as tagged text');
        } elseif (!is_null($value) && !is_scalar($value)) {
            throw new InvalidArgumentException('Evidence must be explicit arrays and scalars, not models or resources');
        }
    }

    private static function same($left, $right): bool
    {
        if (gettype($left) !== gettype($right)) return false;
        if (is_float($left)) return pack('d', $left) === pack('d', $right);
        if (!is_array($left)) return $left === $right;
        if (array_keys($left) !== array_keys($right)) return false;
        foreach ($left as $key => $value) if (!self::same($value, $right[$key])) return false;
        return true;
    }
}
