
/**
 * Brand name reduced to what tells brands apart (2.10.2026): lower case, no ®/™, no legal
 * form, no spaces or punctuation. "BigBoy" = "Big Boy", "SULOV®" = "Sulov", "INVENTO
 * Products  Services" (the "&" lost in an import) = "INVENTO Products & Services GmbH",
 * "Hi Tec Nutrition" = "HiTec Nutrition". Used by the import (no duplicate brand is
 * created) and by the brand merge in product_params_fix.php.
 *
 * @param string $name
 * @return string
 */
function brandNormalizedKey($name)
{
    $key = mb_strtolower(trim((string)$name), 'UTF-8');
    $key = str_replace(['®', '™', '©'], '', $key);
    $key = preg_replace('/(?<!\p{L})(s\.?\s?r\.?\s?o\.?|a\.\s?s\.|s\.p\.a\.|spa|srl|gmbh|e\.\s?k\.|co\.?\s?kg|ltd\.?|inc\.?)(?!\p{L})/u', ' ', $key);
    $key = preg_replace('/(?<!\p{L})(and|&|und)(?!\p{L})/u', ' ', $key);
    // A trailing "Co." / "Co" is a legal form as well ("Günther Co." = "Günther Co KG")
    $key = preg_replace('/(?<!\p{L})co\.?\s*$/u', '', trim($key));
    return preg_replace('/[^\p{L}\p{N}]+/u', '', $key);
}

/**
 * Nightly anomaly report of parameter values (2.10.2026). Counts what the repairs of
 * October 2026 cleaned up, so a new mass corruption (an import, an AI run, a migration)
 * is seen the next morning instead of months later. Saved as JSON next to the logs and
 * shown by admin/ajax/getAdminMessages.php when anything is found.
 *
 * @param string $file Where to save the JSON
 * @return array ['time' => int, 'checks' => [key => ['label', 'count', 'samples']]]
 */
function parameterAnomalyReport($file)
{
    $checks = [];
    $add = function ($key, $label, $count, array $samples = []) use (&$checks) {
        $checks[$key] = ['label' => $label, 'count' => (int)$count, 'samples' => array_slice($samples, 0, 5)];
    };
    $sample = function ($sql) {
        $out = [];
        $res = web_Mysql::gi()->DBquery($sql);
        while ($row = web_Mysql::gi()->DBarray($res)) {
            $out[] = (int)$row['id_produkt'] . ' ' . mb_substr((string)$row['nazev'], 0, 40, 'UTF-8') . ': ' . mb_substr((string)$row['hodnota'], 0, 40, 'UTF-8');
        }
        return $out;
    };
    $visible = " AND p.show_eshop = 1";

    // 1. Junk and whole descriptions
    $row = mfa("SELECT COUNT(*) AS pocet FROM market_parametry_zaznamy z JOIN market_produkty p ON p.id = z.id_produkt
        WHERE (z.hodnota IN ('', 'Array', 'neuvedeno', 'neuvádí se') OR CHAR_LENGTH(z.hodnota) > 255)" . $visible);
    $add('junk', 'Balast nebo celý popis v hodnotě parametru', $row['pocet'] ?? 0,
        $sample("SELECT z.id_produkt, p.nazev, z.hodnota FROM market_parametry_zaznamy z JOIN market_produkty p ON p.id = z.id_produkt
            WHERE (z.hodnota IN ('', 'Array', 'neuvedeno', 'neuvádí se') OR CHAR_LENGTH(z.hodnota) > 255)" . $visible . " LIMIT 5"));

    // 2. Pojem ids that do not exist or belong to another parameter
    $row = mfa("SELECT COUNT(*) AS pocet FROM market_parametry_zaznamy z
        JOIN (SELECT DISTINCT id_parametr FROM market_pojmy) pp ON pp.id_parametr = z.id_parametr
        LEFT JOIN market_pojmy po ON po.id = z.hodnota AND po.id_parametr = z.id_parametr
        WHERE po.id IS NULL");
    $add('pojem', 'Neexistující pojem nebo pojem jiného parametru', $row['pocet'] ?? 0);

    // 3. + 4. Slider parameters: text in a number, numbers off the axis
    $rangeParams = [];
    $res = web_Mysql::gi()->DBquery("SELECT DISTINCT f.parametr_definice_id FROM market_kategorie_filtry f
        WHERE f.typ = 'range' AND f.active = 1 AND f.target = 'produkt'
          AND NOT EXISTS (SELECT 1 FROM market_pojmy po WHERE po.id_parametr = f.parametr_definice_id)");
    while ($r = web_Mysql::gi()->DBarray($res)) {
        $rangeParams[] = (int)$r['parametr_definice_id'];
    }
    $notNumber = 0;
    $offAxis = 0;
    $notNumberSamples = [];
    $offAxisSamples = [];
    if ($rangeParams) {
        $res = web_Mysql::gi()->DBquery("SELECT z.id_produkt, z.id_parametr, z.hodnota, p.nazev FROM market_parametry_zaznamy z
            JOIN market_produkty p ON p.id = z.id_produkt
            WHERE z.id_parametr IN (" . implode(',', $rangeParams) . ")" . $visible);
        while ($r = web_Mysql::gi()->DBarray($res)) {
            $value = trim((string)$r['hodnota']);
            if (!preg_match('/^-?\d+(?:[.,]\d+)?$/u', $value)) {
                $notNumber++;
                if (count($notNumberSamples) < 5) {
                    $notNumberSamples[] = (int)$r['id_produkt'] . ' ' . mb_substr((string)$r['nazev'], 0, 40, 'UTF-8') . ': ' . $value;
                }
            } elseif (parameterValueOutOfBand((int)$r['id_parametr'], (float)str_replace(',', '.', $value))) {
                $offAxis++;
                if (count($offAxisSamples) < 5) {
                    $offAxisSamples[] = (int)$r['id_produkt'] . ' ' . mb_substr((string)$r['nazev'], 0, 40, 'UTF-8') . ': ' . $value;
                }
            }
        }
    }
    $add('range_text', 'Text v číselném parametru s posuvníkem', $notNumber, $notNumberSamples);
    $add('range_band', 'Číslo mimo rozumný rozsah osy (jiný řád, jednotka)', $offAxis, $offAxisSamples);

    // 5. Another unit than the parameter name's
    $unitConflicts = 0;
    $unitSamples = [];
    $res = web_Mysql::gi()->DBquery("SELECT z.id_produkt, z.hodnota, pa.zobrazeny_nazev, p.nazev FROM market_parametry_zaznamy z
        JOIN market_parametry pa ON pa.id = z.id_parametr AND pa.zobrazeny_nazev LIKE '%[%]'
        JOIN market_produkty p ON p.id = z.id_produkt
        WHERE z.hodnota REGEXP '[0-9] ?(mm|cm|dm|km|m|mg|kg|g|t|ml|l)$'" . $visible);
    while ($r = web_Mysql::gi()->DBarray($res)) {
        if (parameterValueUnitConflict((string)$r['hodnota'], (string)$r['zobrazeny_nazev'])) {
            $unitConflicts++;
            if (count($unitSamples) < 5) {
                $unitSamples[] = (int)$r['id_produkt'] . ' ' . mb_substr((string)$r['nazev'], 0, 40, 'UTF-8') . ': ' . $r['zobrazeny_nazev'] . ' = ' . $r['hodnota'];
            }
        }
    }
    $add('unit', 'Jiná jednotka než v názvu parametru', $unitConflicts, $unitSamples);

    // 6. One value on most products of a number parameter - interval codes, a mass default
    $dominant = [];
    $res = web_Mysql::gi()->DBquery("SELECT z.id_parametr, z.hodnota, COUNT(*) AS pocet FROM market_parametry_zaznamy z
        WHERE z.hodnota REGEXP '^[0-9]+$'
          AND NOT EXISTS (SELECT 1 FROM market_pojmy po WHERE po.id_parametr = z.id_parametr)
        GROUP BY z.id_parametr, z.hodnota");
    $perParam = [];
    while ($r = web_Mysql::gi()->DBarray($res)) {
        $perParam[(int)$r['id_parametr']][(string)$r['hodnota']] = (int)$r['pocet'];
    }
    foreach ($perParam as $paramId => $values) {
        $total = array_sum($values);
        arsort($values);
        $topValue = (string)key($values);
        $topCount = (int)current($values);
        if ($total >= 300 && $topCount / $total > 0.6 && count($values) >= 3) {
            $dominant[] = 'parametr ' . $paramId . ': hodnota ' . $topValue . ' u ' . round($topCount * 100 / $total) . ' % z ' . $total;
        }
    }
    $add('dominant', 'Jedna hodnota u většiny produktů číselného parametru', count($dominant), $dominant);

    $report = ['time' => time(), 'checks' => $checks];
    @file_put_contents($file, json_encode($report, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
    return $report;
}
<?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="https://www.sitemaps.org/schemas/sitemap/0.9"><url>
            <loc>https://www.hracky365.cz/</loc>
            <priority>1.00</priority>
        </url></urlset>