<?php

declare(strict_types=1);

namespace Numinix\SeekmodoWordPress\LocalIndex;

/**
 * Local pins, rescue rules, and synonyms (no subscription required).
 */
final class LocalMerchandising
{
    /**
     * Merge local DB merchandising into a fallback_bundle shape.
     *
     * @param array{version: int, synonyms: array, pins: array, rescue: array, bestsellers: array} $bundle
     * @return array{version: int, synonyms: array, pins: array, rescue: array, bestsellers: array}
     */
    public static function mergeIntoBundle(array $bundle): array
    {
        global $wpdb;
        $tables = Schema::tableNames();

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $synRows = $wpdb->get_results("SELECT term, synonym FROM {$tables['synonyms']}", ARRAY_A);
        if (is_array($synRows)) {
            foreach ($synRows as $row) {
                $bundle['synonyms'][] = [
                    'term'       => (string) ($row['term'] ?? ''),
                    'expansions' => [(string) ($row['synonym'] ?? '')],
                ];
            }
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $pinRows = $wpdb->get_results("SELECT query_pattern, post_ids FROM {$tables['pins']}", ARRAY_A);
        if (is_array($pinRows)) {
            foreach ($pinRows as $row) {
                $ids = self::parseIds((string) ($row['post_ids'] ?? ''));
                if ($ids !== []) {
                    $bundle['pins'][] = [
                        'q'           => (string) ($row['query_pattern'] ?? ''),
                        'product_ids' => $ids,
                    ];
                }
            }
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $rescueRows = $wpdb->get_results("SELECT query_pattern, post_ids FROM {$tables['rescue']}", ARRAY_A);
        if (is_array($rescueRows)) {
            foreach ($rescueRows as $row) {
                $ids = self::parseIds((string) ($row['post_ids'] ?? ''));
                if ($ids !== []) {
                    $bundle['rescue'][] = [
                        'q_pattern'   => (string) ($row['query_pattern'] ?? ''),
                        'product_ids' => $ids,
                    ];
                }
            }
        }

        return $bundle;
    }

    public static function addSynonym(string $term, string $synonym): void
    {
        global $wpdb;
        $tables = Schema::tableNames();
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
        $wpdb->insert($tables['synonyms'], [
            'term'    => mb_strtolower(trim($term)),
            'synonym' => mb_strtolower(trim($synonym)),
        ], ['%s', '%s']);
    }

    public static function addPin(string $query, string $postIdsCsv): void
    {
        global $wpdb;
        $tables = Schema::tableNames();
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
        $wpdb->insert($tables['pins'], [
            'query_pattern' => mb_strtolower(trim($query)),
            'post_ids'      => $postIdsCsv,
        ], ['%s', '%s']);
    }

    public static function addRescue(string $query, string $postIdsCsv): void
    {
        global $wpdb;
        $tables = Schema::tableNames();
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
        $wpdb->insert($tables['rescue'], [
            'query_pattern' => mb_strtolower(trim($query)),
            'post_ids'      => $postIdsCsv,
        ], ['%s', '%s']);
    }

    /**
     * Apply local pins (and empty-result rescue) to a result ID list.
     *
     * Used by LocalSearch / SearchEngine so merchandising works without the
     * SDK EnhancedNativeOrchestrator autoload path (and without a subscription).
     *
     * @param list<int> $ids
     * @return list<int>
     */
    public static function applyToResults(string $q, array $ids): array
    {
        $needle = self::normalizeQuery($q);
        if ($needle === '') {
            return array_values(array_map('intval', $ids));
        }

        $ids = array_values(array_filter(array_map('intval', $ids)));
        $pinned = self::idsForPattern('pins', $needle);
        if ($pinned !== []) {
            $seen = array_fill_keys($pinned, true);
            $rest = [];
            foreach ($ids as $id) {
                if ($id > 0 && !isset($seen[$id])) {
                    $rest[] = $id;
                }
            }

            return array_merge($pinned, $rest);
        }

        if ($ids === []) {
            return self::idsForPattern('rescue', $needle);
        }

        return $ids;
    }

    /**
     * Normalize a query the same way Enhanced Native pin matching expects.
     */
    public static function normalizeQuery(string $q): string
    {
        $q = str_replace(["\u{2019}", "\u{2018}", '`', '´'], "'", $q);
        $q = trim(preg_replace('/\s+/u', ' ', $q) ?? '');
        if ($q === '') {
            return '';
        }

        $stop = ['the', 'a', 'an', 'for', 'and', 'or'];
        $parts = preg_split('/\s+/u', mb_strtolower($q)) ?: [];
        $filtered = [];
        foreach ($parts as $part) {
            if ($part !== '' && !in_array($part, $stop, true)) {
                $filtered[] = $part;
            }
        }

        return implode(' ', $filtered);
    }

    /**
     * @param 'pins'|'rescue' $tableKey
     * @return list<int>
     */
    private static function idsForPattern(string $tableKey, string $needle): array
    {
        global $wpdb;
        $tables = Schema::tableNames();
        $table = $tables[$tableKey] ?? '';
        if ($table === '' || $needle === '') {
            return [];
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $csv = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT post_ids FROM {$table} WHERE query_pattern = %s ORDER BY id DESC LIMIT 1",
                $needle
            )
        );

        return self::parseIds(is_string($csv) ? $csv : '');
    }

    /**
     * @return list<int>
     */
    private static function parseIds(string $csv): array
    {
        $parts = preg_split('/\s*,\s*/', trim($csv)) ?: [];

        return array_values(array_filter(array_map('intval', $parts)));
    }
}
