<?php

namespace App\Campaigns;

/**
 * Which of these is the one they almost certainly mean.
 *
 * A buyer working on stockprices.com picks the StockPrices ad account and then
 * has to find the StockPrices Page in a list of fifty. The answer is knowable
 * from what they have already told us, so it is offered first and badged.
 *
 * Always a reordering, never a filter. Guessing wrong has to cost a scroll,
 * not the right answer, because the wrong Page is somebody else's brand posting
 * somebody else's ad.
 */
class NameMatch
{
    /**
     * Parts of a name that say nothing about which brand it is.
     *
     * Without these, "Rateit-ARB" and "IBlockAds" both match a hint containing
     * "ads", and everything looks like a suggestion.
     */
    private const NOISE = [
        'www', 'com', 'net', 'org', 'io', 'co', 'ai', 'app', 'inc', 'ltd',
        'the', 'and', 'ads', 'ad', 'page', 'hub', 'official', 'media',

        // Everything a person says while asking for a campaign. Once the hints
        // include what they typed rather than only a URL, these arrive on every
        // request and match whatever happens to be named after them: "I want a
        // sales campaign" would suggest an account called Sales Team.
        'campaign', 'campaigns', 'advertising', 'advert', 'promote', 'promotion',
        'traffic', 'sales', 'leads', 'lead', 'awareness', 'engagement', 'conversions',
        'meta', 'facebook', 'instagram', 'google', 'tiktok', 'linkedin', 'taboola',
        'run', 'running', 'want', 'need', 'create', 'make', 'new', 'start', 'launch',
        'please', 'help', 'set', 'setup', 'build', 'budget', 'daily', 'lifetime',
        'account', 'target', 'targeting', 'audience', 'for', 'with', 'this', 'that',
        'from', 'about', 'our', 'your', 'their', 'can', 'you', 'let', 'would', 'like',
    ];

    /**
     * Mark the items whose name matches any hint, matches first.
     *
     * @param  list<array<string, mixed>>  $items
     * @param  list<string|null>  $hints  a domain, an account name, anything naming the brand
     * @return list<array<string, mixed>>
     */
    public static function suggestFirst(array $items, array $hints, string $nameKey = 'name'): array
    {
        // Hints are tried in priority order and the first one that matches
        // anything wins outright.
        //
        // Pooling them meant a weaker signal could badge a second answer beside
        // the right one. Advertising iblockads.org from the
        // MidoWeb-WWBlog-GMT-RSOC account badged both IBlockAds and WWBlog-Hub,
        // and the stable sort left the wrong one on top: the account name is
        // incidental, the page being advertised is the point.
        foreach (array_filter($hints) as $hint) {
            $words = self::words([$hint]);

            if ($words === []) {
                continue;
            }

            $matched = false;

            foreach ($items as $i => $item) {
                $items[$i]['suggested'] = self::matches((string) ($item[$nameKey] ?? ''), $words);
                $matched = $matched || $items[$i]['suggested'];
            }

            if (! $matched) {
                continue;
            }

            // Stable, so an already-sensible order survives underneath the match.
            usort($items, fn (array $a, array $b): int => ($b['suggested'] ?? false) <=> ($a['suggested'] ?? false));

            return $items;
        }

        return $items;
    }

    /**
     * The words worth matching on, from any number of hints.
     *
     * "https://www.stockprices.com/offer" and "StockPrices" both reduce to
     * "stockprices", which is what makes an account name find its own Page.
     *
     * @param  list<string|null>  $hints
     * @return list<string>
     */
    public static function words(array $hints): array
    {
        $words = [];

        foreach (array_filter($hints) as $hint) {
            // A URL is only useful as its host; the path is about the offer,
            // not about the brand.
            $source = str_contains((string) $hint, '://')
                ? (parse_url((string) $hint, PHP_URL_HOST) ?: '')
                : (string) $hint;

            foreach (preg_split('/[^a-z0-9]+/', strtolower($source)) ?: [] as $word) {
                if (strlen($word) > 2 && ! in_array($word, self::NOISE, true)) {
                    $words[] = $word;
                }
            }
        }

        return array_values(array_unique($words));
    }

    /** @param  list<string>  $words */
    public static function matches(string $name, array $words): bool
    {
        $name = strtolower($name);

        foreach ($words as $word) {
            if (str_contains($name, $word)) {
                return true;
            }
        }

        return false;
    }
}
