<?php
/**
 * Copyright © Simplyin. All rights reserved.
 */
declare(strict_types=1);

namespace Simplyin\Payment\Service;

use Simplyin\Payment\Helper\ApiClient;

/**
 * Single source of truth for filtering parcel lockers by the merchant's
 * configured carriers.
 *
 * Both the saved-lockers path (userData.parcelLockers, flat top-level
 * providerName) and the getClosest backend result (nested
 * info.provider.name) must be pruned to the providers the merchant actually
 * mapped, so the picker never renders a carrier the merchant did not configure.
 * Centralising the rule here keeps the REST controller, the GraphQL resolver
 * and the userData choke-points from drifting apart, and mirrors WooCommerce
 * (Widget_Controller::getParcelLockers) and PrestaShop
 * (DeliveryFormRenderer::prepareParcelLockers).
 *
 * Matching is fail-closed: an empty provider list yields no lockers.
 */
class ParcelLockerFilter
{
    /**
     * @var ApiClient
     */
    private $apiClient;

    /**
     * @param ApiClient $apiClient
     */
    public function __construct(ApiClient $apiClient)
    {
        $this->apiClient = $apiClient;
    }

    /**
     * Filter lockers down to the merchant's available providers.
     *
     * Pure function: handles both the flat (providerName) and nested
     * (info.provider.name) locker shapes.
     *
     * @param array $lockers
     * @param array $availableProviders
     * @return array
     */
    public function filterByProviders(array $lockers, array $availableProviders): array
    {
        // Fail-closed: no configured providers → no lockers (matches Woo/Presta).
        if (empty($availableProviders)) {
            return [];
        }

        return array_values(array_filter($lockers, function ($locker) use ($availableProviders) {
            // Drop non-array entries so array-offset access below can't emit warnings.
            if (!is_array($locker)) {
                return false;
            }
            $providerName = $this->extractProviderName($locker);
            // Empty providerName must not pass (stripos(x, '') === 0 would let it
            // through for any provider) — keep the guard genuinely fail-closed.
            if ($providerName === '') {
                return false;
            }
            foreach ($availableProviders as $provider) {
                // Skip empty provider entries: stripos(x, '') === 0 would otherwise
                // match every locker and re-open the fail-closed filter.
                $provider = (string)$provider;
                if ($provider === '') {
                    continue;
                }
                if (stripos($providerName, $provider) !== false || stripos($provider, $providerName) !== false) {
                    return true;
                }
            }
            return false;
        }));
    }

    /**
     * Prune userData.parcelLockers against the configured providers.
     *
     * Server-side choke-point: mirrors Woo Widget_Controller::getParcelLockers
     * and Presta DeliveryFormRenderer::prepareParcelLockers so the picker's
     * default view never shows a carrier the merchant did not map. userData
     * without a parcelLockers key is returned unchanged.
     *
     * @param array $userData
     * @return array
     */
    public function filterUserDataParcelLockers(array $userData): array
    {
        if (!empty($userData['parcelLockers']) && is_array($userData['parcelLockers'])) {
            $userData['parcelLockers'] = $this->filterByProviders(
                $userData['parcelLockers'],
                $this->apiClient->getAvailableParcelLockerProviders()
            );
        }

        return $userData;
    }

    /**
     * Extract the provider name from either locker shape.
     *
     * Flat saved lockers carry a top-level providerName; getClosest backend
     * results carry a nested info.provider.name.
     *
     * @param array $locker
     * @return string
     */
    private function extractProviderName(array $locker): string
    {
        // Cast defensively — a non-string value would TypeError strtolower on PHP 8+.
        return strtolower(trim((string)($locker['providerName'] ?? $locker['info']['provider']['name'] ?? '')));
    }
}
