<?php

declare(strict_types=1);

namespace ShieldX\Security;

defined('ABSPATH') || exit;

/**
 * Produces the byte-stable JSON used for definition hashes and signatures.
 */
final class VirtualPatchCanonicalizer
{
    /**
     * @param mixed $value
     */
    public static function json($value): string
    {
        $encoded = wp_json_encode(
            self::normalize($value),
            JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION
        );

        if (!is_string($encoded)) {
            throw new \InvalidArgumentException('Virtual patch data could not be canonicalized.');
        }

        return $encoded;
    }

    /**
     * @param array<string,mixed> $definition
     */
    public static function definitionHash(array $definition): string
    {
        unset($definition['definition_hash']);

        return hash('sha256', self::json($definition));
    }

    /**
     * @param mixed $value
     * @return mixed
     */
    private static function normalize($value)
    {
        if (!is_array($value)) {
            return $value;
        }

        if (self::isList($value)) {
            return array_map([self::class, 'normalize'], array_values($value));
        }

        ksort($value, SORT_STRING);
        foreach ($value as $key => $item) {
            $value[$key] = self::normalize($item);
        }

        return $value;
    }

    /**
     * PHP 7.4-compatible array_is_list().
     *
     * @param array<mixed> $value
     */
    private static function isList(array $value): bool
    {
        $index = 0;
        foreach ($value as $key => $_item) {
            if ($key !== $index) {
                return false;
            }
            $index++;
        }

        return true;
    }
}
