<?php

declare(strict_types=1);

namespace Numinix\SeekmodoWordPress\LocalIndex;

use Numinix\SeekmodoWordPress\Content\PostTypeRegistry;

/**
 * Extract indexable field text from posts and products.
 * Shared by local index and gateway indexers.
 */
final class DocumentBuilder
{
    /**
     * @return array{
     *   doc_id: int,
     *   type: string,
     *   title: string,
     *   content: string,
     *   excerpt: string,
     *   comment: string,
     *   tag: string,
     *   category: string,
     *   taxonomy: string,
     *   customfield: string,
     *   author: string
     * }
     */
    public static function fromPost($post): array
    {
        if (!is_object($post) || !isset($post->ID)) {
            return self::emptyDoc();
        }

        $post_id = (int) $post->ID;
        $post_type = (string) ($post->post_type ?? 'post');

        $title = self::plain((string) ($post->post_title ?? ''));
        $content = self::plain((string) ($post->post_content ?? ''));
        $excerpt = self::plain((string) ($post->post_excerpt ?? ''));
        if ($excerpt === '' && $content !== '') {
            $excerpt = mb_substr($content, 0, 240);
        }

        $author = '';
        $author_id = isset($post->post_author) ? (int) $post->post_author : 0;
        if ($author_id > 0 && function_exists('get_the_author_meta')) {
            $author = self::plain((string) get_the_author_meta('display_name', $author_id));
        }

        [$tagText, $catText, $taxText] = self::taxonomyText($post_id, $post_type);
        $customfield = self::customFieldText($post_id);
        $comment = IndexSettings::indexComments() ? self::commentText($post_id) : '';

        $extra = '';
        if (function_exists('apply_filters')) {
            $extra = (string) apply_filters('seekmodo_local_index_content', '', $post);
            if ($extra === '' && VerticalConfig::relevanssiCompatEnabled()) {
                $extra = (string) apply_filters('relevanssi_post_content', '', $post);
            }
        }
        if ($extra !== '') {
            $extra = self::plain($extra);
            if ($extra !== '' && !str_contains($content, $extra)) {
                $content = trim($content . ' ' . $extra);
            } elseif ($extra !== '') {
                $content = $extra;
            }
        }

        $doc = [
            'doc_id'      => $post_id,
            'type'        => $post_type,
            'title'       => $title,
            'content'     => $content,
            'excerpt'     => $excerpt,
            'comment'     => $comment,
            'tag'         => $tagText,
            'category'    => $catText,
            'taxonomy'    => $taxText,
            'customfield' => $customfield,
            'author'      => $author,
        ];

        if (function_exists('apply_filters')) {
            /** @var array<string,mixed> $doc */
            $doc = apply_filters('seekmodo_local_index_document', $doc, $post);
        }

        return [
            'doc_id'      => (int) ($doc['doc_id'] ?? $post_id),
            'type'        => (string) ($doc['type'] ?? $post_type),
            'title'       => self::plain((string) ($doc['title'] ?? '')),
            'content'     => self::plain((string) ($doc['content'] ?? '')),
            'excerpt'     => self::plain((string) ($doc['excerpt'] ?? '')),
            'comment'     => self::plain((string) ($doc['comment'] ?? '')),
            'tag'         => self::plain((string) ($doc['tag'] ?? '')),
            'category'    => self::plain((string) ($doc['category'] ?? '')),
            'taxonomy'    => self::plain((string) ($doc['taxonomy'] ?? '')),
            'customfield' => self::plain((string) ($doc['customfield'] ?? '')),
            'author'      => self::plain((string) ($doc['author'] ?? '')),
        ];
    }

    /**
     * @param mixed $product WC_Product
     * @return array{doc_id: int, type: string, title: string, content: string, excerpt: string, comment: string, tag: string, category: string, taxonomy: string, customfield: string, author: string}
     */
    public static function fromProduct($product): array
    {
        if (!is_object($product) || !method_exists($product, 'get_id')) {
            return self::emptyDoc();
        }

        $id = (int) $product->get_id();
        $name = self::plain((string) (method_exists($product, 'get_name') ? $product->get_name() : ''));
        $desc = '';
        if (method_exists($product, 'get_short_description')) {
            $desc = self::plain((string) $product->get_short_description());
        }
        if ($desc === '' && method_exists($product, 'get_description')) {
            $desc = self::plain((string) $product->get_description());
        }
        $sku = method_exists($product, 'get_sku') ? self::plain((string) $product->get_sku()) : '';
        $brand = '';
        if (function_exists('wp_get_post_terms')) {
            foreach (['product_brand', 'pwb-brand', 'yith_product_brand'] as $tax) {
                if (!taxonomy_exists($tax)) {
                    continue;
                }
                $terms = wp_get_post_terms($id, $tax, ['fields' => 'names']);
                if (is_array($terms) && $terms !== []) {
                    $brand = self::plain(implode(' ', $terms));
                    break;
                }
            }
        }
        $cats = '';
        if (function_exists('wp_get_post_terms')) {
            $terms = wp_get_post_terms($id, 'product_cat', ['fields' => 'names']);
            if (is_array($terms)) {
                $cats = self::plain(implode(' ', $terms));
            }
        }

        return [
            'doc_id'      => $id,
            'type'        => 'product',
            'title'       => $name,
            'content'     => trim($name . ' ' . $desc . ' ' . $sku),
            'excerpt'     => mb_substr($desc, 0, 240),
            'comment'     => '',
            'tag'         => $brand,
            'category'    => $cats,
            'taxonomy'    => $cats,
            'customfield' => $sku,
            'author'      => '',
        ];
    }

    /**
     * @return array{doc_id: int, type: string, title: string, content: string, excerpt: string, comment: string, tag: string, category: string, taxonomy: string, customfield: string, author: string}
     */
    private static function emptyDoc(): array
    {
        return [
            'doc_id' => 0, 'type' => '', 'title' => '', 'content' => '', 'excerpt' => '',
            'comment' => '', 'tag' => '', 'category' => '', 'taxonomy' => '', 'customfield' => '', 'author' => '',
        ];
    }

    private static function plain(string $text): string
    {
        if ($text === '') {
            return '';
        }
        $text = wp_strip_all_tags($text);

        return trim(preg_replace('/\s+/u', ' ', $text) ?? '');
    }

    /**
     * @return array{0: string, 1: string, 2: string}
     */
    private static function taxonomyText(int $post_id, string $post_type): array
    {
        $tags = [];
        $cats = [];
        $other = [];
        if (!function_exists('get_object_taxonomies') || !function_exists('wp_get_post_terms')) {
            return ['', '', ''];
        }
        foreach (get_object_taxonomies($post_type) as $tax) {
            if ($tax === 'post_format' || $tax === 'nav_menu') {
                continue;
            }
            $terms = wp_get_post_terms($post_id, $tax, ['fields' => 'names']);
            if (!is_array($terms)) {
                continue;
            }
            foreach ($terms as $name) {
                $s = (string) $name;
                if ($s === '') {
                    continue;
                }
                if ($tax === 'post_tag' || $tax === 'product_tag') {
                    $tags[] = $s;
                } elseif ($tax === 'category' || $tax === 'product_cat') {
                    $cats[] = $s;
                } else {
                    $other[] = $s;
                }
            }
        }

        return [implode(' ', $tags), implode(' ', $cats), implode(' ', $other)];
    }

    private static function customFieldText(int $post_id): string
    {
        $chunks = [];
        foreach (IndexSettings::defaultMetaKeys() as $key) {
            if (!function_exists('get_post_meta')) {
                break;
            }
            $val = get_post_meta($post_id, $key, true);
            if (is_string($val) && trim($val) !== '') {
                $chunks[] = self::plain($val);
            }
        }

        global $wpdb;
        foreach (IndexSettings::defaultMetaPatterns() as $pattern) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $rows = $wpdb->get_col($wpdb->prepare(
                "SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key LIKE %s",
                $post_id,
                $pattern
            ));
            if (is_array($rows)) {
                foreach ($rows as $row) {
                    $text = self::plain((string) $row);
                    if ($text !== '') {
                        $chunks[] = $text;
                    }
                }
            }
        }

        $mode = IndexSettings::customFieldsMode();
        if ($mode !== 'none' && function_exists('get_post_custom')) {
            $custom = get_post_custom($post_id);
            if (is_array($custom)) {
                foreach ($custom as $key => $values) {
                    if (str_starts_with((string) $key, '_')) {
                        continue;
                    }
                    if (!is_array($values)) {
                        continue;
                    }
                    foreach ($values as $val) {
                        if (!is_string($val)) {
                            continue;
                        }
                        $text = self::plain($val);
                        if ($text !== '') {
                            $chunks[] = $text;
                        }
                    }
                }
            }
        }

        return implode(' ', array_unique(array_filter($chunks)));
    }

    private static function commentText(int $post_id): string
    {
        if (!function_exists('get_comments')) {
            return '';
        }
        $comments = get_comments(['post_id' => $post_id, 'status' => 'approve', 'number' => 50]);
        if (!is_array($comments)) {
            return '';
        }
        $parts = [];
        foreach ($comments as $c) {
            if (is_object($c) && isset($c->comment_content)) {
                $parts[] = self::plain((string) $c->comment_content);
            }
        }

        return implode(' ', $parts);
    }

    /**
     * @return list<string>
     */
    public static function indexablePostTypes(): array
    {
        return PostTypeRegistry::indexable();
    }
}
