<?php

if (!defined('ABSPATH')) {
    exit;
}

final class TFT_Miniapp_Normalizer
{
    public static function normalize_tour(WP_Post $post): array
    {
        $fields = function_exists('get_fields') ? (get_fields($post->ID) ?: []) : [];
        $hero = is_array($fields['hero'] ?? null) ? $fields['hero'] : [];
        $about = is_array($fields['about'] ?? null) ? $fields['about'] : [];
        $title = self::plain_text($hero['title'] ?? $post->post_title);
        $summary = self::plain_text($hero['desc'] ?? '');
        $description_html = self::about_html($about);
        $prices = self::tour_prices($post, $fields);
        $variants = self::tour_variants($post, $fields);
        $media = self::tour_media($post, $fields);
        $categories = self::tour_categories($post, $title . ' ' . $summary);
        $duration_days = self::duration_days($post, $fields, $title . ' ' . $summary);
        $featured = (bool) get_post_meta($post->ID, '_tft_miniapp_featured', true);

        return [
            'id' => $post->post_type . ':' . $post->ID,
            'wpId' => $post->ID,
            'kind' => self::tour_kind($post->post_type),
            'city' => self::tour_city($post, $title),
            'slug' => $post->post_name,
            'title' => $title ?: self::plain_text($post->post_title),
            'summary' => $summary,
            'descriptionHtml' => $description_html,
            'categories' => $categories,
            'badges' => self::tour_badges($featured, $categories),
            'featured' => $featured,
            'durationDays' => $duration_days,
            'durationLabel' => self::duration_label($duration_days),
            'prices' => $prices,
            'variants' => $variants,
            'minAdultPrice' => self::min_adult_price($prices),
            'media' => $media,
            'included' => self::simple_list($fields['included'] ?? [], ['service', 'title', 'name']),
            'excluded' => self::simple_list($fields['paid'] ?? [], ['service_paid', 'service', 'title', 'name']),
            'program' => self::program($fields),
            'conditions' => self::conditions($fields),
            'whatToTake' => self::tour_what_to_take($post, $fields),
            'sourceUrl' => get_permalink($post),
            'updatedAt' => mysql2date(DATE_ATOM, $post->post_modified_gmt ?: $post->post_modified, false),
        ];
    }

    public static function normalize_transfers(WP_Post $post): array
    {
        $fields = function_exists('get_fields') ? (get_fields($post->ID) ?: []) : [];
        $offers = [];
        self::walk_transfer_fields($fields, $offers, $post);

        return array_values(self::unique_by_id($offers));
    }

    private static function tour_kind(string $post_type): string
    {
        if (str_starts_with($post_type, 'yacht')) {
            return 'yacht';
        }
        if (str_starts_with($post_type, 'zoo')) {
            return 'zoo';
        }
        return 'tour';
    }

    private static function tour_city(WP_Post $post, string $tour_title = ''): string
    {
        $identity = mb_strtolower(
            $tour_title . ' ' . $post->post_name . ' ' . $post->post_title
        );
        $mentions_phuket = str_contains($identity, 'phuket') || str_contains($identity, 'пхук');
        $mentions_pattaya = str_contains($identity, 'pattaya') || str_contains($identity, 'паттай');

        // Some legacy zoo records live under a city-specific post type that
        // does not match the actual attraction. An unambiguous city in the
        // customer-facing title is more reliable than that legacy container.
        if ($mentions_phuket && !$mentions_pattaya) {
            return 'phuket';
        }
        if ($mentions_pattaya && !$mentions_phuket) {
            return 'pattaya';
        }

        $permalink = (string) get_permalink($post);
        $path = mb_strtolower(self::normalized_url_path($permalink));
        if (preg_match('#/(?:phuket|phuket-tours)(?:/|$)#u', $path)) {
            return 'phuket';
        }
        if (preg_match('#/(?:pattaya|pattaya-tours)(?:/|$)#u', $path)) {
            return 'pattaya';
        }

        $post_type = mb_strtolower($post->post_type);
        if (str_contains($post_type, 'phuket') || $post_type === 'phuket') {
            return 'phuket';
        }
        if (str_contains($post_type, 'pattaya') || $post_type === 'pattaya') {
            return 'pattaya';
        }

        if ($mentions_phuket) {
            return 'phuket';
        }
        return 'pattaya';
    }

    private static function plain_text($value): string
    {
        if (!is_scalar($value)) {
            return '';
        }

        $decoded = html_entity_decode(
            (string) $value,
            ENT_QUOTES | ENT_HTML5,
            'UTF-8'
        );
        $text = wp_strip_all_tags(wp_kses_post($decoded), true);
        return trim((string) preg_replace('/\s+/u', ' ', $text));
    }

    private static function clean_html($value): string
    {
        if (!is_scalar($value)) {
            return '';
        }

        $html = wp_kses_post((string) $value);
        $html = (string) preg_replace(
            '/\s(?:class|style|dir|tabindex|data-[a-z0-9_-]+)="[^"]*"/iu',
            '',
            $html
        );
        return trim($html);
    }

    private static function about_html(array $about): string
    {
        $blocks = [];
        foreach (($about['text_media'] ?? []) as $row) {
            if (is_array($row)) {
                $html = self::clean_html($row['desc'] ?? '');
                if ($html !== '') {
                    $blocks[] = $html;
                }
            }
        }
        return implode("\n", $blocks);
    }

    private static function money($value): array
    {
        $raw = is_scalar($value) ? trim((string) $value) : '';
        $normalized = str_replace([',', "\xc2\xa0", ' '], ['.', '', ''], $raw);
        preg_match('/\d+(?:\.\d+)?/', $normalized, $match);
        $amount = isset($match[0]) ? (float) $match[0] : null;

        return [
            'amount' => $amount,
            'currency' => 'THB',
            'raw' => $raw,
        ];
    }

    private static function tour_prices(WP_Post $post, array $fields): array
    {
        $variants = [];
        $programs = is_array($fields['programs'] ?? null) ? $fields['programs'] : [];
        if (is_array($programs['variants'] ?? null)) {
            $variants = $programs['variants'];
        } elseif (is_array($fields['variants'] ?? null)) {
            $variants = $fields['variants'];
        }

        $result = [];
        foreach ($variants as $index => $variant) {
            if (!is_array($variant)) {
                continue;
            }
            $adult = self::first_value($variant, ['price_adult', 'adult', 'adult_price', 'price']);
            $child = self::first_value($variant, ['price_kids', 'price_child', 'child', 'kids', 'child_price']);
            $infant = self::first_value($variant, ['price_infant', 'infant', 'infant_price']);
            if ($adult === '' && $child === '' && $infant === '') {
                continue;
            }
            $row = [
                'variant' => self::plain_text($variant['name'] ?? ('Вариант ' . ($index + 1))),
                'adult' => self::money($adult),
                'child' => self::money($child),
            ];
            if ($infant !== '') {
                $row['infant'] = self::money($infant);
            }
            $result[] = $row;
        }

        if (!$result) {
            $prices = is_array($fields['prices'] ?? null) ? $fields['prices'] : [];
            $adult = self::first_value($prices, ['adult', 'price_adult']);
            $child = self::first_value($prices, ['child', 'kids', 'price_child']);
            if ($adult === '') {
                $adult = get_post_meta($post->ID, '_tft_miniapp_adult_price', true);
            }
            if ($child === '') {
                $child = get_post_meta($post->ID, '_tft_miniapp_child_price', true);
            }
            $result[] = [
                'variant' => 'Основная программа',
                'adult' => self::money($adult),
                'child' => self::money($child),
            ];
        }

        return $result;
    }

    private static function tour_variants(WP_Post $post, array $fields): array
    {
        $programs = is_array($fields['programs'] ?? null) ? $fields['programs'] : [];
        $variants = is_array($programs['variants'] ?? null) ? $programs['variants'] : [];
        $result = [];

        foreach ($variants as $index => $variant) {
            if (!is_array($variant)) {
                continue;
            }

            $adult = self::first_value($variant, ['price_adult', 'adult', 'adult_price', 'price']);
            $child = self::first_value($variant, ['price_kids', 'price_child', 'child', 'kids', 'child_price']);
            $infant = self::first_value($variant, ['price_infant', 'infant', 'infant_price']);
            $days = is_array($variant['days'] ?? null) ? $variant['days'] : [];
            $duration_days = max(1, count($days));
            $conditions = [];

            foreach (['alert_1', 'alert_2'] as $key) {
                $text = self::plain_text($variant[$key] ?? '');
                if ($text !== '') {
                    $conditions[] = $text;
                }
            }

            $row = [
                'id' => 'program-' . ($index + 1),
                'name' => self::plain_text($variant['name'] ?? ('Вариант ' . ($index + 1))),
                'description' => self::variant_description($variant, $variants, $index),
                'adult' => self::money($adult),
                'child' => self::money($child),
                'durationDays' => $duration_days,
                'durationLabel' => self::duration_label($duration_days),
                'program' => self::program_days($days),
                'included' => self::simple_list(
                    $variant['included'] ?? [],
                    ['service', 'title', 'name', 'text']
                ),
                'conditions' => array_values(array_unique($conditions)),
                'whatToTake' => self::mapped_what_to_take(
                    $post->post_type . ':' . $post->ID,
                    'program-' . ($index + 1)
                ),
            ];
            if ($infant !== '') {
                $row['infant'] = self::money($infant);
            }
            $result[] = $row;
        }

        return $result;
    }

    private static function variant_description(
        array $variant,
        array $variants,
        int $variant_index
    ): string {
        $explicit = self::plain_text(self::first_value(
            $variant,
            ['desc', 'description', 'subtitle', 'about']
        ));
        if ($explicit !== '') {
            return $explicit;
        }

        $name = self::plain_text(
            $variant['name'] ?? ('Вариант ' . ($variant_index + 1))
        );
        $parts = [];
        if ($name !== '') {
            $parts[] = 'Программа «' . trim($name, " \t\n\r\0\x0B«»\"") . '».';
        }

        $highlights = self::unique_variant_highlights(
            $variant,
            $variants,
            $variant_index
        );
        if ($highlights) {
            $highlight_text = implode('; ', $highlights);
            $parts[] = 'Ключевые отличия: ' . $highlight_text
                . (preg_match('/[.!?…]$/u', $highlight_text) ? '' : '.');
        }

        $option_names = self::simple_list(
            $variant['options'] ?? [],
            ['name', 'title']
        );
        if ($option_names) {
            $parts[] = 'Доступные форматы: ' . implode(', ', $option_names) . '.';
        }

        $days = is_array($variant['days'] ?? null) ? $variant['days'] : [];
        if ($days) {
            $parts[] = 'Ниже указан точный маршрут и тайминг именно этого варианта.';
        } else {
            $parts[] = 'Состав и стоимость относятся именно к выбранному варианту.';
        }

        return implode(' ', $parts);
    }

    private static function unique_variant_highlights(
        array $variant,
        array $variants,
        int $variant_index
    ): array {
        $other_steps = [];
        foreach ($variants as $index => $other_variant) {
            if ($index === $variant_index || !is_array($other_variant)) {
                continue;
            }
            foreach (self::variant_schedule_descriptions($other_variant) as $description) {
                $other_steps[self::comparison_text($description)] = true;
            }
        }

        $result = [];
        foreach (self::variant_schedule_descriptions($variant) as $description) {
            $comparison = self::comparison_text($description);
            if (
                $comparison === ''
                || isset($other_steps[$comparison])
                || self::is_generic_schedule_step($description)
            ) {
                continue;
            }
            $result[] = self::summary_fragment($description);
            if (count($result) === 2) {
                break;
            }
        }
        return array_values(array_unique($result));
    }

    private static function variant_schedule_descriptions(array $variant): array
    {
        $result = [];
        foreach (($variant['days'] ?? []) as $day) {
            if (!is_array($day)) {
                continue;
            }
            foreach (($day['schedule'] ?? []) as $item) {
                if (!is_array($item)) {
                    continue;
                }
                $description = self::plain_text(
                    $item['desc'] ?? $item['description'] ?? ''
                );
                if ($description !== '') {
                    $result[] = $description;
                }
            }
        }
        return $result;
    }

    private static function comparison_text(string $value): string
    {
        return trim((string) preg_replace(
            '/[^\p{L}\p{N}]+/u',
            ' ',
            mb_strtolower(self::plain_text($value))
        ));
    }

    private static function is_generic_schedule_step(string $value): bool
    {
        return (bool) preg_match(
            '/^(?:выезд|отправление|возвращение|прибытие|трансфер|сбор|завтрак|обед|ужин|заселение|освобождение)\b/iu',
            self::plain_text($value)
        );
    }

    private static function summary_fragment(string $value, int $limit = 150): string
    {
        $text = rtrim(self::plain_text($value), " \t\n\r\0\x0B.,;:");
        if (mb_strlen($text) <= $limit) {
            return $text;
        }
        $truncated = rtrim(
            mb_substr($text, 0, $limit - 1),
            " \t\n\r\0\x0B.,;:"
        );
        $last_space = mb_strrpos($truncated, ' ');
        if ($last_space !== false && $last_space > (int) ($limit * 0.65)) {
            $truncated = mb_substr($truncated, 0, $last_space);
        }
        return rtrim($truncated, " \t\n\r\0\x0B.,;:") . '…';
    }

    private static function tour_what_to_take(WP_Post $post, array $fields): array
    {
        $mapped = self::mapped_what_to_take($post->post_type . ':' . $post->ID);
        if ($mapped) {
            return $mapped;
        }
        return self::first_list(
            $fields,
            ['what_to_take', 'take', 'things', 'recommendations']
        );
    }

    private static function mapped_what_to_take(
        string $tour_id,
        ?string $variant_id = null
    ): array {
        $data = self::what_to_take_data();
        $row = $variant_id
            ? ($data['variantMappings'][$tour_id][$variant_id] ?? [])
            : ($data['tourMappings'][$tour_id] ?? []);
        if (!is_array($row) || !is_array($row['whatToTake'] ?? null)) {
            return [];
        }
        return array_values(array_filter(array_map(
            static fn ($item): string => self::plain_text($item),
            $row['whatToTake']
        )));
    }

    private static function what_to_take_data(): array
    {
        static $data = null;
        if (is_array($data)) {
            return $data;
        }
        $path = TFT_MINIAPP_API_PATH . 'data/what-to-take.json';
        if (!is_readable($path)) {
            $data = [];
            return $data;
        }
        $decoded = json_decode((string) file_get_contents($path), true);
        $data = is_array($decoded) ? $decoded : [];
        return $data;
    }

    private static function first_value(array $source, array $keys)
    {
        foreach ($keys as $key) {
            if (isset($source[$key]) && $source[$key] !== null && $source[$key] !== '') {
                return $source[$key];
            }
        }
        return '';
    }

    private static function min_adult_price(array $prices): ?float
    {
        $amounts = [];
        foreach ($prices as $price) {
            $amount = $price['adult']['amount'] ?? null;
            if (is_numeric($amount)) {
                $amounts[] = (float) $amount;
            }
        }
        return $amounts ? min($amounts) : null;
    }

    private static function tour_media(WP_Post $post, array $fields): array
    {
        $ids = [];
        if (has_post_thumbnail($post)) {
            $ids[] = get_post_thumbnail_id($post);
        }

        $hero = is_array($fields['hero'] ?? null) ? $fields['hero'] : [];
        $gallery = is_array($fields['gallery'] ?? null) ? $fields['gallery'] : [];
        foreach (array_merge(
            self::attachment_ids($hero['gallery'] ?? []),
            self::attachment_ids($gallery['photo_list'] ?? [])
        ) as $id) {
            $ids[] = $id;
        }

        $result = [];
        foreach (array_values(array_unique(array_filter(array_map('absint', $ids)))) as $id) {
            $large = wp_get_attachment_image_src($id, 'large');
            $thumbnail = wp_get_attachment_image_src($id, 'medium');
            if (!$large) {
                continue;
            }
            $result[] = [
                'id' => $id,
                'url' => $large[0],
                'thumbnailUrl' => $thumbnail ? $thumbnail[0] : $large[0],
                'alt' => self::plain_text(get_post_meta($id, '_wp_attachment_image_alt', true)),
                'width' => (int) $large[1],
                'height' => (int) $large[2],
            ];
        }
        return $result;
    }

    private static function attachment_ids($value): array
    {
        if (!is_array($value)) {
            return is_numeric($value) ? [(int) $value] : [];
        }
        $result = [];
        foreach ($value as $item) {
            if (is_numeric($item)) {
                $result[] = (int) $item;
            } elseif (is_array($item) && isset($item['ID'])) {
                $result[] = (int) $item['ID'];
            }
        }
        return $result;
    }

    public static function site_filter_definitions(string $city): array
    {
        $catalog = self::site_filter_catalog($city);
        return $catalog['filters'];
    }

    private static function site_filter_catalog(string $city): array
    {
        static $cache = [];
        if (isset($cache[$city])) {
            return $cache[$city];
        }

        $page = get_page_by_path($city === 'phuket' ? 'phuket' : 'pattaya');
        $html = $page && function_exists('get_field')
            ? (string) get_field('block_6', $page->ID)
            : '';
        $filters = [];
        $assignments = [];

        if ($html !== '') {
            preg_match_all(
                '/<button\b[^>]*data-filter=(["\'])([^"\']+)\1[^>]*>(.*?)<\/button>/isu',
                $html,
                $button_matches,
                PREG_SET_ORDER
            );
            foreach ($button_matches as $match) {
                $id = sanitize_key(html_entity_decode($match[2], ENT_QUOTES | ENT_HTML5, 'UTF-8'));
                $title = self::plain_text($match[3]);
                $title = trim((string) preg_replace('/\s+\d+\s*$/u', '', $title));
                if ($id !== '' && $title !== '') {
                    $filters[$id] = $title;
                }
            }

            preg_match_all(
                '/<a\b(?=[^>]*\btft-patcards__card\b)[^>]*>/isu',
                $html,
                $card_matches
            );
            foreach ($card_matches[0] as $tag) {
                $href = self::html_attribute($tag, 'href');
                $raw_filters = self::html_attribute($tag, 'data-filters');
                $path = self::normalized_url_path($href);
                if ($path === '' || $raw_filters === '') {
                    continue;
                }
                $ids = array_values(array_intersect(
                    preg_split('/\s+/u', trim($raw_filters)) ?: [],
                    array_keys($filters)
                ));
                if ($ids) {
                    $assignments[$path] = $ids;
                }
            }
        }

        $cache[$city] = [
            'filters' => array_map(
                static fn (string $id, string $title): array => [
                    'id' => $id,
                    'title' => $title,
                ],
                array_keys($filters),
                array_values($filters)
            ),
            'assignments' => $assignments,
        ];
        return $cache[$city];
    }

    private static function html_attribute(string $tag, string $name): string
    {
        $pattern = '/\b' . preg_quote($name, '/') . '\s*=\s*(["\'])(.*?)\1/isu';
        if (!preg_match($pattern, $tag, $match)) {
            return '';
        }
        return html_entity_decode($match[2], ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }

    private static function normalized_url_path(string $url): string
    {
        $path = wp_parse_url($url, PHP_URL_PATH);
        if (!is_string($path) || $path === '') {
            return '';
        }
        return untrailingslashit(rawurldecode($path));
    }

    private static function tour_categories(WP_Post $post, string $haystack): array
    {
        $city = self::tour_city($post);
        $catalog = self::site_filter_catalog($city);
        $path = self::normalized_url_path((string) get_permalink($post));
        if ($path !== '' && !empty($catalog['assignments'][$path])) {
            return $catalog['assignments'][$path];
        }

        // When the public landing has a curated catalog, it is the source of
        // truth for filter membership. Supplemental yachts and zoos remain
        // visible in "Все", but must not inflate the site's category counts.
        if (!empty($catalog['filters'])) {
            return [];
        }

        $terms = wp_get_object_terms($post->ID, 'tft_miniapp_category', ['fields' => 'slugs']);
        $categories = is_wp_error($terms) ? [] : array_values($terms);
        if ($categories) {
            $legacy_map = [
                'marine' => 'sea',
                'family' => 'kids',
                'active' => 'extreme',
                'adrenaline' => 'extreme',
                'zoos' => 'nature',
                'evening' => 'evening',
            ];
            return array_values(array_unique(array_filter(array_map(
                static fn (string $category): string => $legacy_map[$category] ?? $category,
                $categories
            ))));
        }

        $text = mb_strtolower($haystack);
        if (str_starts_with($post->post_type, 'yacht')) {
            $categories[] = 'sea';
        }
        if (str_starts_with($post->post_type, 'zoo')) {
            $categories[] = 'nature';
            $categories[] = 'kids';
        }

        $rules = [
            'sea' => ['остров', 'море', 'катер', 'снорклинг', 'рыбалк', 'круиз'],
            'kids' => ['семей', 'дет', 'парк', 'дельфинар', 'аквапарк'],
            'extreme' => ['квадроцикл', 'багги', 'рафтинг', 'треккинг', 'актив', 'парашют', 'гидроцикл', 'экстрим', 'адреналин'],
            'evening' => ['вечер', 'закат', 'шоу', 'ужин'],
            'nature' => ['зоопарк', 'слон', 'животн', 'джунгл', 'водопад'],
            'islands' => ['остров'],
            'fishing' => ['рыбалк', 'дайвинг'],
            'culture' => ['храм', 'обзорн', 'музей', 'культур'],
            'bangkok' => ['бангкок'],
        ];
        foreach ($rules as $slug => $needles) {
            foreach ($needles as $needle) {
                if (str_contains($text, $needle)) {
                    $categories[] = $slug;
                    break;
                }
            }
        }
        return array_values(array_unique($categories));
    }

    private static function tour_badges(bool $featured, array $categories): array
    {
        $badges = $featured ? ['hit'] : [];
        foreach (['kids', 'extreme', 'sea', 'nature'] as $category) {
            if (in_array($category, $categories, true)) {
                $badges[] = $category;
            }
        }
        return array_slice(array_values(array_unique($badges)), 0, 3);
    }

    private static function duration_days(WP_Post $post, array $fields, string $haystack): int
    {
        $explicit = (int) get_post_meta($post->ID, '_tft_miniapp_duration_days', true);
        if ($explicit > 1) {
            return min(30, $explicit);
        }

        $max_days = 1;
        $programs = is_array($fields['programs'] ?? null) ? $fields['programs'] : [];
        foreach (($programs['variants'] ?? []) as $variant) {
            if (is_array($variant) && is_array($variant['days'] ?? null)) {
                $max_days = max($max_days, count($variant['days']));
            }
        }

        if ($max_days === 1 && preg_match('/(\d+)\s*(?:дня|дней|день)/ui', $haystack, $match)) {
            $max_days = (int) $match[1];
        }
        return max(1, min(30, $max_days));
    }

    private static function duration_label(int $days): string
    {
        if ($days === 1) {
            return '1 день';
        }
        if ($days >= 2 && $days <= 4) {
            return $days . ' дня';
        }
        return $days . ' дней';
    }

    private static function program(array $fields): array
    {
        $programs = is_array($fields['programs'] ?? null) ? $fields['programs'] : [];
        $variants = is_array($programs['variants'] ?? null) ? $programs['variants'] : [];
        $first = is_array($variants[0] ?? null) ? $variants[0] : [];
        return self::program_days(is_array($first['days'] ?? null) ? $first['days'] : []);
    }

    private static function program_days(array $days): array
    {
        $result = [];
        foreach ($days as $index => $day) {
            if (!is_array($day)) {
                continue;
            }
            $schedule = [];
            foreach (($day['schedule'] ?? []) as $item) {
                if (!is_array($item)) {
                    continue;
                }
                $description = self::plain_text($item['desc'] ?? $item['description'] ?? '');
                if ($description === '') {
                    continue;
                }
                $schedule[] = [
                    'time' => self::plain_text($item['time'] ?? ''),
                    'description' => $description,
                ];
            }
            $result[] = [
                'title' => self::plain_text($day['title'] ?? ('День ' . ($index + 1))),
                'schedule' => $schedule,
            ];
        }
        return $result;
    }

    private static function simple_list($value, array $keys): array
    {
        if (!is_array($value)) {
            $text = self::plain_text($value);
            return $text === '' ? [] : [$text];
        }
        $result = [];
        foreach ($value as $item) {
            if (is_scalar($item)) {
                $text = self::plain_text($item);
            } elseif (is_array($item)) {
                $text = self::plain_text(self::first_value($item, $keys));
            } else {
                $text = '';
            }
            if ($text !== '') {
                $result[] = $text;
            }
        }
        return array_values(array_unique($result));
    }

    private static function first_list(array $fields, array $keys): array
    {
        foreach ($keys as $key) {
            if (!empty($fields[$key])) {
                return self::simple_list($fields[$key], ['title', 'name', 'text', 'service']);
            }
        }
        return [];
    }

    private static function conditions(array $fields): array
    {
        $result = self::first_list($fields, ['conditions', 'terms', 'rules']);
        $programs = is_array($fields['programs'] ?? null) ? $fields['programs'] : [];
        foreach (($programs['variants'] ?? []) as $variant) {
            if (!is_array($variant)) {
                continue;
            }
            foreach (['alert_1', 'alert_2'] as $key) {
                $text = self::plain_text($variant[$key] ?? '');
                if ($text !== '') {
                    $result[] = $text;
                }
            }
        }
        return array_values(array_unique($result));
    }

    private static function walk_transfer_fields($value, array &$offers, WP_Post $post): void
    {
        if (!is_array($value)) {
            return;
        }

        $title = self::first_value($value, ['price_name', 'route', 'question', 'name', 'title', 'direction']);
        $price = self::first_value($value, ['price', 'stoimost', 'cost', 'amount', 'value']);
        if (is_scalar($title) && self::plain_text($title) !== '' && is_scalar($price) && self::plain_text($price) !== '') {
            $normalized_title = self::plain_text($title);
            $route = self::transfer_route($normalized_title);
            $vehicle = self::plain_text(self::first_value($value, ['vehicle', 'car', 'transport']));
            if ($vehicle === '' && preg_match_all('/\(([^)]+)\)/u', $normalized_title, $vehicle_matches)) {
                foreach ($vehicle_matches[1] as $candidate) {
                    if (preg_match('/седан|минив[эе]н|иннова|автобус|suv|van|bus/ui', (string) $candidate)) {
                        $vehicle = self::plain_text($candidate);
                        break;
                    }
                }
            }
            $id = 'transfer:' . $post->ID . ':' . substr(md5($normalized_title . '|' . $vehicle . '|' . (string) $price), 0, 12);
            $offers[] = [
                'id' => $id,
                'city' => self::transfer_city($post, $normalized_title),
                'title' => $normalized_title,
                'description' => self::transfer_description(self::first_value($value, ['description', 'desc', 'answer', 'text'])),
                'from' => self::plain_text(self::first_value($value, ['from', 'departure', 'origin'])) ?: $route['from'],
                'to' => self::plain_text(self::first_value($value, ['to', 'arrival', 'destination'])) ?: $route['to'],
                'vehicle' => $vehicle,
                'price' => self::money($price),
                'sourceUrl' => get_permalink($post),
            ];
        }

        foreach ($value as $child) {
            if (is_array($child)) {
                self::walk_transfer_fields($child, $offers, $post);
            }
        }
    }

    private static function transfer_route(string $title): array
    {
        $parts = preg_split('/\s+(?:-|–|—|→)\s+/u', $title, 2);
        if (!is_array($parts) || count($parts) !== 2) {
            return ['from' => '', 'to' => ''];
        }

        return [
            'from' => self::plain_text($parts[0]),
            'to' => self::plain_text($parts[1]),
        ];
    }

    private static function transfer_description($value): string
    {
        if (!is_scalar($value)) {
            return '';
        }
        $without_embedded_code = preg_replace(
            '/<(?:style|script)\b[^>]*>.*?<\/(?:style|script)>/isu',
            ' ',
            (string) $value
        );
        return self::plain_text($without_embedded_code);
    }

    private static function transfer_city(WP_Post $post, string $offer_title = ''): string
    {
        $text = mb_strtolower($offer_title . ' ' . $post->post_name . ' ' . $post->post_title);
        if (str_contains($text, 'phuket') || str_contains($text, 'пхук')) {
            return 'phuket';
        }
        if (str_contains($text, 'pattaya') || str_contains($text, 'паттай')) {
            return 'pattaya';
        }
        return 'thailand';
    }

    private static function unique_by_id(array $items): array
    {
        $result = [];
        foreach ($items as $item) {
            $result[$item['id']] = $item;
        }
        return $result;
    }
}
