<?php

declare(strict_types=1);

namespace FleekDash\API;

/**
 * Single place for menu payload content hashing (change detection, not cryptographic security).
 * Uses the same JSON flags everywhere so hashes stay stable across endpoints.
 */
final class MenuPayloadHash
{
    private const JSON_FLAGS = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;

    /** md5('empty') — used for empty / error payloads */
    public const EMPTY = 'a2e4822a98337283e39f7b60acf85ec9';

    /**
     * @param array<mixed> $items
     */
    public static function hashItems(array $items): string
    {
        $json = \json_encode($items, self::JSON_FLAGS);
        if ($json === false) {
            return self::EMPTY;
        }

        return \md5($json);
    }

    /**
     * @param array<int, array<string, mixed>> $items
     */
    public static function hashNormalizedMenu(array $items): string
    {
        return self::hashItems(self::normalizeMenuForHash($items));
    }

    /**
     * Normalize menu items for hash generation.
     * Must stay aligned with frontend menu fingerprint semantics.
     *
     * @param array<int, array<string, mixed>> $items
     * @return array<int, array<string, mixed>>
     */
    public static function normalizeMenuForHash(array $items): array
    {
        $normalized = [];
        foreach ($items as $item) {
            $normalizedItem = [
                'unique_id' => $item['unique_id'] ?? '',
                'text' => $item['name'] ?? '',
                'href' => self::normalizeHrefForHash((string) ($item['href'] ?? '')),
                'url' => self::normalizeHrefForHash((string) ($item['url'] ?? '')),
                'icon' => self::normalizeIconForHash((string) ($item['icon'] ?? '')),
                'target' => $item['target'] ?? '_self',
                'route_type' => $item['route_type'] ?? 'wordpress_iframe',
                'is_wp_native' => $item['is_wp_native'] ?? true,
                'order' => isset($item['order']) ? (float) $item['order'] : 0.0,
                'position' => isset($item['position']) ? (float) $item['position'] : 0.0,
            ];
            if (isset($item['submenu']) && \is_array($item['submenu']) && !empty($item['submenu'])) {
                /** @var array<int, array<string, mixed>> $submenu */
                $submenu = $item['submenu'];
                $normalizedItem['submenu'] = self::normalizeMenuForHash($submenu);
            }
            $normalized[] = $normalizedItem;
        }
        return $normalized;
    }

    private static function normalizeHrefForHash(string $href): string
    {
        $normalized = '';
        if ($href !== '') {
            try {
                $parsed = \parse_url($href);
                if (!$parsed) {
                    $normalized = $href;
                } else {
                    $path = $parsed['path'] ?? '';
                    $query = $parsed['query'] ?? '';
                    $path = \ltrim($path, '/');
                    $path = \preg_replace('#^wp-admin/#', '', $path);
                    if (!empty($query)) {
                        \parse_str($query, $params);
                        \ksort($params);
                        $query = \http_build_query($params);
                    }
                    $result = $path;
                    if (!empty($query)) {
                        $result .= '?' . $query;
                    }
                    $normalized = (string) \preg_replace('/\?\?+/', '?', $result);
                }
            } catch (\Throwable $e) {
                $normalized = $href;
            }
        }
        return $normalized;
    }

    private static function normalizeIconForHash(string $iconClass): string
    {
        $out = '';
        if ($iconClass !== '') {
            $tokens = \preg_split('/\s+/', \trim($iconClass));
            $tokens = \array_filter(\array_map('trim', $tokens ?: []));
            if (!empty($tokens)) {
                $out = self::resolveIconTokenForHash($tokens);
            }
        }
        return $out;
    }

    /**
     * @param array<int, string> $tokens
     */
    private static function resolveIconTokenForHash(array $tokens): string
    {
        foreach ($tokens as $token) {
            if (self::isPreferredIconTokenForHash($token)) {
                return $token;
            }
        }
        $stableTokens = \array_filter($tokens, [self::class, 'isStableIconTokenForHash']);
        return !empty($stableTokens) ? (string) \reset($stableTokens) : '';
    }

    private static function isPreferredIconTokenForHash(string $token): bool
    {
        return \strpos($token, 'dashicons-') === 0
            || \strpos($token, 'menu-icon-') === 0
            || \strpos($token, 'toplevel_page_') === 0;
    }

    private static function isStableIconTokenForHash(string $token): bool
    {
        return $token !== 'wp-has-current-submenu'
            && $token !== 'wp-not-current-submenu'
            && $token !== 'wp-menu-open'
            && $token !== 'wp-menu-close'
            && $token !== 'current'
            && $token !== 'menu-top'
            && $token !== 'menu-top-last'
            && \strpos($token, 'opensub') !== 0
            && \strpos($token, 'menu-top-first') !== 0;
    }
}
