<?php

declare(strict_types=1);

namespace Inpsyde\MultilingualPress\Module\Redirect\RedirectTarget;

use Inpsyde\MultilingualPress\Module\Redirect\GeoLocation\GeolocationFinderInterface;
use Inpsyde\MultilingualPress\Module\Redirect\Redirector\Redirector;

use function Inpsyde\MultilingualPress\combineAtts;

/**
 * @psalm-type RedirectTargetConfig = array{
 *     siteId?: int,
 *     contentId?: int,
 *     language?: string,
 *     priorityIndex?: int,
 *     url?: string,
 *     redirectFallbackPriority?: float
 * }
 */
class RedirectTarget implements RedirectTargetInterface
{
    public const KEY_SITE_ID = 'siteId';
    public const KEY_CONTENT_ID = 'contentId';
    public const KEY_LANGUAGE = 'language';
    public const KEY_PRIORITY_INDEX = 'priorityIndex';
    public const KEY_URL = 'url';
    public const KEY_REDIRECT_FALLBACK_PRIORITY = 'redirectFallbackPriority';

    protected const DEFAULTS = [
        self::KEY_CONTENT_ID => 0,
        self::KEY_LANGUAGE => '',
        self::KEY_PRIORITY_INDEX => 0,
        self::KEY_SITE_ID => 0,
        self::KEY_URL => '',
        self::KEY_REDIRECT_FALLBACK_PRIORITY => 0.0,
    ];

    public const FILTER_PRIORITY_FACTOR = 'multilingualpress.language_only_priority_factor';

    /**
     * A map of language codes to priorities.
     *
     * @var array<string, float>
     */
    protected $userLanguages;

    /**
     * @var GeolocationFinderInterface
     */
    protected $geolocationFinder;

    /**
     * @var array<string, int|float|string>
     * @psalm-var RedirectTargetConfig
     */
    protected $data;

    public function __construct(array $userLanguages, GeolocationFinderInterface $geolocationFinder, array $data = [])
    {
        $this->data = combineAtts(self::DEFAULTS, $data);
        $this->userLanguages = $userLanguages;
        $this->geolocationFinder = $geolocationFinder;
    }

    /**
     * @inheritDoc
     */
    public function siteId(): int
    {
        return $this->data[self::KEY_SITE_ID] ?? self::DEFAULTS[self::KEY_SITE_ID];
    }

    /**
     * @inheritDoc
     */
    public function contentId(): int
    {
        return $this->data[self::KEY_CONTENT_ID] ?? self::DEFAULTS[self::KEY_CONTENT_ID];
    }

    /**
     * @inheritDoc
     */
    public function url(): string
    {
        return $this->data[self::KEY_URL] ?? self::DEFAULTS[self::KEY_URL];
    }

    /**
     * @inheritDoc
     */
    public function language(): string
    {
        return $this->data[self::KEY_LANGUAGE] ?? self::DEFAULTS[self::KEY_LANGUAGE];
    }

    /**
     * @inheritDoc
     */
    public function priorityIndex(): int
    {
        return $this->data[self::KEY_PRIORITY_INDEX] ?? self::DEFAULTS[self::KEY_PRIORITY_INDEX];
    }

    public function redirectPriority(string $redirectType): float
    {
        switch ($redirectType) {
            case Redirector::REDIRECT_TYPE_GEOLOCATION:
                return $this->redirectPriorityBasedOnGeolocation();
            case Redirector::REDIRECT_TYPE_GEOLOCATION_WITH_FALLBACK_TO_BROWSER_LANGUAGE:
                $geoLocationPriority = $this->redirectPriorityBasedOnGeolocation();
                return $geoLocationPriority > 0 ? $geoLocationPriority : $this->redirectPriorityBasedOnBrowserLanguages();
            default:
                return $this->redirectPriorityBasedOnBrowserLanguages();
        }
    }

    /**
     * @inheritDoc
     */
    public function redirectFallbackPriority(): float
    {
        return $this->data[self::KEY_REDIRECT_FALLBACK_PRIORITY] ?? self::DEFAULTS[self::KEY_REDIRECT_FALLBACK_PRIORITY];
    }

    /**
     * Retrieves the redirect target priority based on browser language.
     *
     * @return float The redirect target priority.
     */
    protected function redirectPriorityBasedOnBrowserLanguages(): float
    {
        $languageTag = $this->language();

        /**
         * Filters the factor used to compute the priority of language-only matches.
         * This has to be between 0 and 1.
         *
         * @param float $factor
         *
         * @see get_user_priority()
         */
        $factor = (float)apply_filters(static::FILTER_PRIORITY_FACTOR, .8);

        $languageOnlyPriorityFactor = (float)max(0, min(1, $factor));
        $userLanguagePriority = $this->userLanguages[$languageTag] ?? '';

        if (!empty($userLanguagePriority)) {
            return (float)$userLanguagePriority;
        }

        if (substr_count($languageTag, '-')) {
            $languageTag = strtok($languageTag, '-');
            $userLanguagePriority = $this->userLanguages[$languageTag] ?? '';

            if (!empty($userLanguagePriority)) {
                return $languageOnlyPriorityFactor * $userLanguagePriority;
            }
        }

        return 0.0;
    }

    /**
     * Retrieves the redirect target priority based on geolocation.
     *
     * @return float The redirect target priority.
     */
    protected function redirectPriorityBasedOnGeolocation(): float
    {
        $splitCode = explode('-', $this->language());
        $countryCode = $splitCode[1] ?? '';

        if (!$countryCode) {
            return 0.0;
        }

        return $countryCode === $this->geolocationFinder->find() ? 1 : 0;
    }
}
