<?php

declare(strict_types=1);

namespace Numinix\SeekmodoWordPress\Commerce;

use Numinix\SeekmodoWordPress\Rest\Routes;

/**
 * Commerce typeahead. Same REST surface as Content/Typeahead but
 * shapes the rows for product cards (image, name, price, sale_price,
 * on_sale, stock_status).
 *
 * Routes register under the same `seekmodo/v1/typeahead` endpoint as
 * the content vertical, switching on `?vertical=commerce`. The
 * Content typeahead handler is the source of truth for the route
 * registration; this class is the shape mapper for commerce-vertical
 * responses + native-fallback rendering.
 *
 * Two-sided shape concern (v0.3.0):
 *
 *   - INPUT (gateway row): post-v0.3.0 the gateway commerce
 *     collection uses Typesense-aligned field names (`name`, `url`,
 *     `in_stock`, `image_url`, `price`, `sale_price`, `on_sale`,
 *     `currency`, `sku`). Pre-v0.3.0 the connector emitted
 *     WC-shaped names (`title`, `permalink`, `stock_status`); we
 *     keep aliases on the input side so a connector still talking
 *     to a pre-migration tenant doesn't crash.
 *
 *   - OUTPUT (storefront row): the storefront / theme contract
 *     stays on WC-friendly names (`title`, `permalink`,
 *     `stock_status` as the `instock|outofstock|onbackorder`
 *     enum). This keeps third-party themes that wired up to the
 *     pre-v0.3.0 typeahead stable across the schema realignment.
 */
final class Typeahead
{
    public static function register(): void
    {
        if (!function_exists('add_action')) {
            return;
        }
        // The route itself is registered by Content/Typeahead; we hook
        // a `seekmodo_typeahead_shape_row` filter to project commerce-
        // shaped rows.
        add_filter('seekmodo_typeahead_shape_row', [self::class, 'shape'], 10, 2);
        add_filter('seekmodo_typeahead_native_fallback', [self::class, 'nativeFallback'], 10, 3);
    }

    /**
     * @param array<string,mixed> $row
     * @return array<string,mixed>
     */
    public static function shape(array $row, string $vertical): array
    {
        if ($vertical !== 'commerce') {
            return $row;
        }

        $title     = self::firstNonEmptyString($row, ['name', 'title']);
        $permalink = self::firstNonEmptyString($row, ['url', 'permalink']);
        $image_url = self::firstNonEmptyString($row, ['image_url', 'thumbnail_url']);

        // Collapse the boolean `in_stock` (post-v0.3.0) back to the
        // `stock_status` enum the theme contract expects. Honor an
        // explicit string `stock_status` if a transitional doc still
        // ships one.
        if (isset($row['stock_status']) && is_string($row['stock_status']) && $row['stock_status'] !== '') {
            $stock_status = (string) $row['stock_status'];
        } elseif (array_key_exists('in_stock', $row)) {
            $stock_status = !empty($row['in_stock']) ? 'instock' : 'outofstock';
        } else {
            $stock_status = 'instock';
        }

        return [
            'id'           => isset($row['id']) ? (int) $row['id'] : 0,
            'title'        => $title,
            'permalink'    => $permalink,
            'image_url'    => $image_url,
            'price'        => isset($row['price']) ? (float) $row['price'] : null,
            'sale_price'   => isset($row['sale_price']) ? (float) $row['sale_price'] : null,
            'on_sale'      => !empty($row['on_sale']),
            'stock_status' => $stock_status,
            'currency'     => isset($row['currency']) ? (string) $row['currency'] : '',
            'sku'          => isset($row['sku']) ? (string) $row['sku'] : '',
        ];
    }

    /**
     * @param array<int,array<string,mixed>> $rows
     * @return array<int,array<string,mixed>>
     */
    public static function nativeFallback(array $rows, string $q, int $max): array
    {
        if (function_exists('numinix_seekmodo_enhanced_native_enabled')
            && numinix_seekmodo_enhanced_native_enabled()) {
            return \Numinix\SeekmodoWordPress\Search\EnhancedNativeRegistry::runCommerceTypeahead($q, $max);
        }
        if (!function_exists('wc_get_product') || !function_exists('get_posts')) {
            return $rows;
        }
        $ids = get_posts([
            's'              => $q,
            'post_type'      => 'product',
            'post_status'    => 'publish',
            'posts_per_page' => $max,
            'fields'         => 'ids',
            'no_found_rows'  => true,
        ]);
        if (!is_array($ids)) {
            return $rows;
        }
        $out = [];
        foreach ($ids as $id) {
            $p = wc_get_product((int) $id);
            if (!is_object($p)) {
                continue;
            }
            $out[] = [
                'id'           => (int) (method_exists($p, 'get_id') ? $p->get_id() : 0),
                'title'        => method_exists($p, 'get_name') ? (string) $p->get_name() : '',
                'permalink'    => method_exists($p, 'get_permalink') ? (string) $p->get_permalink() : '',
                'image_url'    => self::imageUrl($p),
                'price'        => method_exists($p, 'get_price') ? (float) $p->get_price() : null,
                'sale_price'   => method_exists($p, 'get_sale_price') ? self::asFloat($p->get_sale_price()) : null,
                'on_sale'      => method_exists($p, 'is_on_sale') ? (bool) $p->is_on_sale() : false,
                'stock_status' => method_exists($p, 'get_stock_status') ? (string) $p->get_stock_status() : 'instock',
                'sku'          => method_exists($p, 'get_sku') ? (string) $p->get_sku() : '',
                'currency'     => function_exists('get_woocommerce_currency') ? (string) get_woocommerce_currency() : '',
            ];
        }

        return $out;
    }

    private static function imageUrl($product): string
    {
        if (!is_object($product) || !method_exists($product, 'get_image_id')) {
            return '';
        }
        $image_id = (int) $product->get_image_id();
        if ($image_id <= 0 || !function_exists('wp_get_attachment_image_url')) {
            return '';
        }

        return (string) wp_get_attachment_image_url($image_id, 'medium');
    }

    /**
     * @param array<string,mixed> $row
     * @param array<int,string>   $keys
     */
    private static function firstNonEmptyString(array $row, array $keys): string
    {
        foreach ($keys as $k) {
            if (isset($row[$k]) && is_string($row[$k]) && $row[$k] !== '') {
                return (string) $row[$k];
            }
        }

        return '';
    }

    /**
     * @param mixed $v
     */
    private static function asFloat($v): ?float
    {
        if ($v === null || $v === '' || $v === false) {
            return null;
        }

        return (float) $v;
    }
}
