<?php

namespace App;

use App\Facades\Feature;
use App\Facades\Settings;
use App\Http\Resources\VehicleList;
use App\Modules\PlaceholderImages\ImaginStudio;
use App\Traits\CacheObject;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Mtc\Filter\Contracts\CustomPatternFilter;
use Mtc\Filter\Contracts\FilterObject;
use Mtc\Filter\Contracts\FilterSeoContract;
use Mtc\Filter\Contracts\IsFilter;
use Mtc\Filter\FilterIndex;
use Mtc\MercuryDataModels\Filters\VehicleAttributeBooleanFilter;
use Mtc\MercuryDataModels\Filters\VehicleAttributeSelectFilter;
use Mtc\MercuryDataModels\Filters\VehicleAttributeValueMaxFilter;
use Mtc\MercuryDataModels\Filters\VehicleAttributeValueMinFilter;
use Mtc\MercuryDataModels\Finance\Contracts\FinanceType;
use Mtc\MercuryDataModels\KeyloopLeaseVehicleVariant;
use Mtc\MercuryDataModels\SearchFacet;
use Mtc\MercuryDataModels\SearchTermLog;
use Mtc\MercuryDataModels\SeoData;
use Mtc\MercuryDataModels\SeoDefault;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleAttribute;
use Mtc\MercuryDataModels\VehicleFinance;
use Illuminate\Support\Facades\Lang;
use Mtc\MercuryDataModels\VehicleMake;
use Mtc\MercuryDataModels\VehicleModel;

class Filter extends \Mtc\Filter\Filter
{
    use Traits\MapElementBasedContent;
    use CacheObject;

    protected const SORT_INDEX_LAST = 9999;

    protected $applyFiltersToResults = true;

    private ?SeoDefault $seo_default = null;

    protected string $target_model = Vehicle::class;

    protected Collection $filterableAttributes;

    protected bool $stockProviderSortApplied = false;

    protected ?Collection $facetConfig = null;

    /**
     * Filter constructor
     *
     * @param Request $request
     * @param FilterObject $product_handler
     * @param FilterSeoContract $seo
     */
    public function __construct(Request $request, FilterObject $product_handler, FilterSeoContract $seo)
    {
        $this->request = $request;
        $this->seo = $seo;
        $this->config = Config::get('filter');
        $this->product_handler = $product_handler;
        $this->active_sort_option_name = $this->config['default_sort_choice'];
    }

    /**
     * Find the id of the slug
     *
     * @param string $model_type
     * @param $slug
     * @param int|null $makeId
     * @return int
     */
    public static function matchSlugToId(string $model_type, $slug, ?int $makeId = null)
    {
        return match ($model_type) {
            'make' => VehicleMake::query()->where('slug', $slug)->firstOrFail()->id,
            'model' => VehicleModel::query()
                ->where('slug', $slug)
                ->when($makeId, fn ($q) => $q->where('make_id', $makeId))
                ->firstOrFail()
                ->id,
            default => FilterIndex::query()
                ->where('filter_type', $model_type)
                ->where('slug', $slug)
                ->firstOrFail()
                ->filter_id
        };
    }

    /**
     * Find the id of the slug
     *
     * @param string $model_type
     * @param $slug
     * @return Model
     */
    public static function matchSlugToRecord(string $model_type, $slug)
    {
        return match ($model_type) {
            'make' => VehicleMake::query()->where('slug', $slug)->firstOrFail(),
            'model' => VehicleModel::query()->where('slug', $slug)->firstOrFail(),
            default => FilterIndex::query()
                ->where('filter_type', $model_type)
                ->where('slug', $slug)
                ->firstOrFail()
                ->filter
        };
    }

    /**
     * Parse Request to selections and filter data
     *
     * @return array
     */
    #[\Override]
    public function parseRequest(): array
    {
        $this->decodeRequest();

        return [
            'seo' => $this->getPageSeoData(),
            'selections' => $this->getSelectionList(),
            'sort_by' => $this->active_sort_option_name,
            'base_url' => url($this->config['url_entrypoint']),
            'page' => $this->request->input('page', 1),
        ];
    }

    /**
     * Handle Ajax request, perform filtering
     *
     * @return array
     */
    #[\Override]
    public function handle(): array
    {
        $this->request = request();
        $this->loadFacetConfig();

        if ($this->request->has('slug')) {
            $this->filter_url_elements = explode('/', (string) $this->request->input('slug'));
            $this->matchSortFromUrlElements();
            $this->matchSelectedFilters();
            $this->matchCustomSelections();
            $this->checkForSearchTerm();

            $redirect = $this->checkFacetOrderAndRedirect();
            if ($redirect) {
                return [
                    'type' => 'redirect',
                    'to' => '/' . ltrim($redirect, '/'),
                    'code' => 301
                ];
            }

            foreach ($this->groupSelectionsByType($this->request->input('selections', [])) as $type => $values) {
                $existing = $this->selections[$type] ?? [];
                $this->selections[$type] = array_values(array_unique(array_merge($existing, $values)));
            }
        } else {
            $this->selections = $this->groupSelectionsByType($this->request->input('selections') ?? []);
        }
        $this->trackSearchTerm();
        $this->run();

        $results = $this->getResults();

        return [
            'results' => $results,
            'filters' => $this->getCachedFilterResults(),
            'sort_options' => $this->getSortOptions(),
            'sort_by' => $this->active_sort_option_name,
            'selections' => $this->getSelectionList(),
            'url' => $this->getPageUrl(),
            'seo' => $this->fillSeo(),
            'finance_example' => $this->getRepresentativeFinance($results),
            'imagin_studio_base_url' => Feature::isEnabled('imagin-studio-placeholders')
                ? new ImaginStudio()->getBaseUrl()
                : null,
            'load_more' => [
                'enabled' => Settings::get('automotive-enable_load_more', false),
                'text' => Settings::get('automotive-load_more_text'),
                'previous_text' => Settings::get('automotive-load_more_previous_text'),
            ]
        ];
    }

    /**
     * Set the sort option name for post requests
     * This is done by finding if url has a part in its path that is a valid sort option
     * If multiple options are set only last one will be used
     */
    protected function matchSortFromUrlElements(): void
    {
        $matched_sort = array_intersect(
            $this->filter_url_elements,
            array_map(fn ($entry) => $entry['code'], $this->getSortOptions())
        );
        $this->filter_url_elements = array_filter(array_diff($this->filter_url_elements, $matched_sort));
        $this->active_sort_option_name = !empty($matched_sort)
            ? array_pop($matched_sort)
            : $this->config['default_sort_choice'];
    }


    public function handleWidget()
    {
        $this->request = request();
        $this->applyFiltersToResults = Settings::get('filter-apply-selections-to-results-for-widget', false);
        $this->selections = $this->groupSelectionsByType($this->request->input('selections') ?? []);
        $this->run();
        return [
            'result_count' => $this->getResultCount(false),
            'filters' => $this->getFilterResults(true),
            'url' => $this->getPageUrl(),
        ];
    }

    /**
     * Get enabled filters
     *
     * @return Collection
     */
    #[\Override]
    public function getFilters(): Collection
    {
        if (empty($this->filters)) {
            $this->filters = collect($this->getConfigFilters())
                ->map(fn ($filter_class, $type) => App::make(
                    $filter_class,
                    [
                        'type' => $type,
                        'filter_target_model' => $this->target_model
                    ]
                ))
                ->filter(fn ($filter) => $filter instanceof IsFilter)
                ->filter(fn ($filter, $name) => Settings::get($this->getSettingsFilterName($name, $filter)) === true)
                ->sortBy(fn ($filter, $name) => Settings::getOrder($this->getSettingsFilterName($name, $filter)));
        }

        return $this->filters;
    }

    /**
     * Fetch the list of supported sort options
     * Returned in format of slug => display name
     *
     * @return array
     */
    #[\Override]
    public function getSortOptions(): array
    {
        return collect($this->getConfigSortOptions())
            ->filter(fn ($filter, $filter_name) => Settings::get("automotive-vehicle-sorting-$filter_name") === true)
            ->sortBy(fn ($filter, $name) => Settings::getOrder("automotive-vehicle-sorting-$name"))
            ->map(fn ($sort_class, $sort_name) => [
                'code' => $sort_name,
                'label' => $this->getSortOptionName($sort_name)
            ])
            ->values()
            ->toArray();
    }

    /**
     * Get the selections as a flat list
     *
     * @return Collection
     */
    #[\Override]
    public function getSelectionList(): Collection
    {
        if ($this->index === null) {
            $this->getFilterIndexForSelections();
        }

        return collect($this->selections)
            ->filter(fn ($filter_group, $filter_type) => isset($this->getConfigFilters()[$filter_type]))
            ->flatMap(fn ($filter_group, $filter_type) => collect($filter_group)
                ->map(function ($value) use ($filter_type) {

                    $to_return = [
                        'type' => $filter_type,
                        'value' => $value,
                        'name' => $this->selectionName($filter_type, $value),
                        'slug' => $value,
                    ];

                    if ($filter_type === 'model') {
                        $to_return['make_slug'] = null;
                        $filter_results = $this->getCachedFilterResults();

                        if (array_key_exists($filter_type, $filter_results)) {
                            $to_return['make_slug'] = collect($filter_results[$filter_type]['results'])
                                ->filter(fn ($model_result) => $model_result['slug'] == $value)
                                ->first()['make_slug'] ?? null;
                        }
                    }

                    return $to_return;
                }));
    }

    /**
     * Build the URL of the page that has been viewed
     *
     * @param bool $absolute
     * @return string
     */
    #[\Override]
    public function getPageUrl(bool $absolute = true): string
    {
        if ($this->facetConfig === null || $this->facetConfig->isEmpty()) {
            return $this->getPageUrlLegacy($absolute);
        }

        $facetSlugs = [];
        $queryParams = [];

        foreach ($this->selections as $type => $values) {
            $values = is_array($values) ? $values : [$values];
            if ($this->isFacetFilter($type) && count($values) === 1) {
                $position = $this->facetConfig->firstWhere('filter_type', $type)?->position ?? 99;
                $facetSlugs[$position] = $values[0];
            } else {
                foreach ($values as $value) {
                    $queryParams[$type][] = $value;
                }
            }
        }

        ksort($facetSlugs);
        $path = implode('/', $facetSlugs);

        if ($this->request->input('page') > 1) {
            $queryParams['page'] = $this->request->input('page');
        }
        if ($this->active_sort_option_name !== $this->config['default_sort_choice']) {
            $queryParams['sort_by'] = $this->active_sort_option_name;
        }

        $append = !empty($queryParams) ? '?' . $this->buildQueryString($queryParams) : '';
        return $path . $append;
    }

    protected function buildQueryString(array $params): string
    {
        $parts = [];

        foreach ($params as $key => $value) {
            if (is_array($value)) {
                foreach ($value as $v) {
                    $parts[] = urlencode($key) . '=' . urlencode($v);
                }
            } else {
                $parts[] = urlencode($key) . '=' . urlencode($value);
            }
        }

        return implode('&', $parts);
    }

    protected function getPageUrlLegacy(bool $absolute = true): string
    {
        $append = '';
        $customSort = $this->active_sort_option_name !== $this->config['default_sort_choice'];
        $params = array_filter([
            'page' => $this->request->input('page') > 1 ? $this->request->input('page') : null,
            'sort_by' => $customSort ? $this->active_sort_option_name : null,
        ]);
        if (!empty($params)) {
            $append = '?' . http_build_query($params);
        }
        return $this->filterElementsToSlugs($this->selections)->join('/') . $append;
    }


    /**
     * Get the selection name from index
     *
     * @param string $type
     * @param $value
     * @return string|null
     */
    protected function selectionName(string $type, $value): ?string
    {
        $filterInstance = App::make($this->getConfigFilters()[$type], ['type' => $type]);
        $customPatternFilter = $filterInstance instanceof CustomPatternFilter;

        if ($customPatternFilter || method_exists($filterInstance, 'getSelectionName')) {
            return $filterInstance->getSelectionName($value);
        }

        return $this->selectionNameInIndex($type, $value);
    }


    /**
     * Get the selection name from index
     *
     * @param string $type
     * @param $value
     * @return string|null
     */
    protected function selectionNameInIndex(string $type, $value): ?string
    {
        return $this->index
            ->where('filter_type', $type)
            ->firstWhere('slug', $value)
            ?->name;
    }

    /**
     * Create a list of all filter elements (selections, ordering, terms etc.) in one list of slugs
     *
     * @param array $selections
     * @return Collection
     */
    #[\Override]
    protected function filterElementsToSlugs(array $selections): Collection
    {
        $all = collect([]);
        collect($selections)
            ->sortBy(fn ($selection, $type) => array_search($type, array_keys($this->getConfigFilters()), true))
            ->each(function ($type_selections, $type) use ($all) {
                $filter = $this->getFilter($type);
                // Unsupported selection
                if ($filter === null) {
                    return;
                }

                collect($type_selections)->each(function ($selection) use ($all, $filter, $type) {
                    $slugs = $this->getFilterIndexForSelections()
                        ->groupBy('filter_type')
                        ->map(fn (Collection $group) => $group->keyBy('slug'));

                    if ($filter instanceof CustomPatternFilter) {
                        $all->push($filter->createSlug($selection));
                    } elseif (isset($slugs[$type][$selection]['slug'])) {
                        $all->push($slugs[$type][$selection]['slug']);
                    }
                });
            });

        return $all;
    }

    protected function getCachedFilterResults(): array
    {
        if (collect($this->selections)->flatten()->count() > 2) {
            return $this->getFilterResults();
        }

        $append = collect($this->selections)
            ->map(fn ($values, $type) => $type . '=' . implode('|', (array) $values))
            ->sort()
            ->values()
            ->implode('-');

        return $this->cache('empty-selection-filter-results' . $append, 15, fn () => $this->getFilterResults());
    }

    /**
     * Retrieve filter results based on selections
     *
     * @param bool $prependAny
     * @return array
     */
    #[\Override]
    protected function getFilterResults(bool $prependAny = false): array
    {
        $input = $this->request->input();
        $filters = $this->getFilters()
            ->when(
                !empty($input['only_filters']),
                fn ($all) => $all->filter(fn ($filter, $name) => in_array($name, $input['only_filters'] ?? [], true))
            )
            ->map(fn (IsFilter $filter, $name) => $filter->format($this->retrieveSingleFilterResults($filter, $name)))
            ->filter()
            ->map(function (array $filterGroup, $type) use ($prependAny) {
                $filterGroup['results'] = collect($filterGroup['results'])
                    ->map(function ($entry) use ($type) {
                        if (is_array($entry)) {
                            $valueField = $type === 'price' ? 'value' : 'id';
                            $entry['selected'] = isset($this->selections[$type])
                                && in_array($entry[$valueField], $this->selections[$type]);
                        }
                        return $entry;
                    })
                    ->when($prependAny, fn ($group) => $group->prepend([
                        'id' => 0,
                        'name' => __('labels.any'),
                    ]))
                    ->values();
                return $filterGroup;
            })
            ->toArray();

        return $this->groupRangeFilters($filters);
    }

    /**
     * Get (product) results.
     * Sets the orderBy for the main query
     * Executes the query to retrieve data
     * Formats data to defined response format
     *
     * @return int
     */
    protected function getResultCount(bool $include_extras = true): int
    {
        return $this->product_handler->getResultCount(clone $this->query, $include_extras);
    }

    /**
     * Apply filters for other filters.
     * This applies the same  filters as result filtering except for current active filter.
     * It also adds product filter condition
     *
     * @param Builder $query
     * @param string $active_filter
     */
    #[\Override]
    protected function applyForFilters(Builder $query, string $active_filter): void
    {
        if ($this->applyFiltersToResults) {
            $this->getFilters()
                ->reject(fn (IsFilter $filter, string $type) => $this->filterNotApplicable($active_filter, $type))
                ->each(fn (IsFilter $filter, string $type) => $filter->applyFilter($query, $this->selections[$type]));
        } elseif ($active_filter === 'model') {
            $this->getFilters()
                ->filter(fn (IsFilter $filter, string $type) => !empty($this->selections[$type]) && 'make' === $type)
                ->each(fn (IsFilter $filter, string $type) => $filter->applyFilter($query, $this->selections[$type]));
        }

        $this->product_handler->applyFilter($query);
    }

    protected function filterNotApplicable(string $active_filter, string $type)
    {
        if (empty($this->selections[$type]) || $active_filter === $type) {
            return true;
        }

        if (
            Settings::get('automotive-vehicle-filters-filter-makes-by-selected-model') === false
            && $type == 'model'
            && $active_filter == 'make'
        ) {
            return true;
        }

        // Due to the way how data is fetched for min/max filters we also discard counterpart from
        if (Str::endsWith($active_filter, ['_min', '_max'])) {
            return substr($active_filter, 0, -4) === substr($type, 0, -4);
        }

        return false;
    }


    /**
     * Group selections by type
     * @param array $selections
     * @param string $group_by
     * @return array
     */
    #[\Override]
    protected function groupSelectionsByType(array $selections = [], string $group_by = 'type'): array
    {
        return collect($selections)
            ->filter(fn ($selection) => !empty($selection['value']))
            ->groupBy($group_by)
            ->map(fn ($group) => collect($group)->pluck('value')->unique()->values())
            ->toArray();
    }

    protected function fillSeo(): array
    {
        $robots = $this->getRobotsForCurrentLevel();

        if (!empty($this->request->header('x-path'))) {
            $data = SeoData::query()->where('path', $this->request->header('x-path'))->first();
            if ($data) {
                $data->load('mediaUses.media');
                $data->content =  collect($data->content)
                    ->map(fn ($entry) => $this->mapContent($data, $entry, $this->request, 'content'))
                    ->toArray();
                $result = $data->only([
                    'title',
                    'description',
                    'heading',
                    'content',
                    'content_description',
                    'canonical',
                    'json_schema',
                ]);
                $result['robots'] = $robots;
                return $result;
            }
        }
        $default = $this->seoDefault();
        return [
            'title' => $this->seoTitle(),
            'description' => $this->seoDescription(),
            'heading' => $this->seoHeading($default),
            'content_description' => $this->seoContentDescription($default),
            'cheapest_item' => $this->getCheapestOffer(),
            'robots' => $robots,
        ];
    }

    protected function getRobotsForCurrentLevel(): array
    {
        if ($this->facetConfig === null || $this->facetConfig->isEmpty()) {
            return ['index' => true, 'follow' => true];
        }

        $activeLevel = 0;
        foreach ($this->facetConfig as $facet) {
            if (isset($this->selections[$facet->filter_type])) {
                $values = $this->selections[$facet->filter_type];
                if (is_array($values) && count($values) === 1) {
                    $activeLevel = $facet->position;
                } elseif (!is_array($values)) {
                    $activeLevel = $facet->position;
                }
            }
        }

        if ($activeLevel === 0) {
            return ['index' => true, 'follow' => true];
        }

        $facet = $this->facetConfig->firstWhere('position', $activeLevel);
        return [
            'index' => $facet?->robots_index ?? true,
            'follow' => $facet?->robots_follow ?? true,
        ];
    }

    private function seoTitle(): string
    {
        $default = $this->seoDefault();

        $result = $this->replaceSeoTags([
            '{{MAKE}}' => $this->replaceSelectionByType('make'),
            '{{MODEL}}' => $this->replaceSelectionByType('model'),
            '{{FUEL_TYPE}}' => $this->replaceSelectionByType('fuel_type'),
            '{{BODY_STYLE}}' => $this->replaceSelectionByType('body_type'),
            '{{TRANSMISSION}}' => $this->replaceSelectionByType('transmission'),
            '{{COLOUR}}' => $this->replaceSelectionByType('colour'),
            '{{VEHICLE_TYPE}}' => $this->replaceSelectionByType('vehicle_type'),
            '{{CONDITION}}' => $this->replaceSelectionByType('is_new'),
            '{{CURRENT_PAGE}}' => $this->getCurrentPageText(),
            '{{PAGE}}' => $this->getCurrentPage(),
            '{{SITE_NAME}}' => Settings::get('app-name'),
            '{{NEW_USED}}' => $this->replaceSelectionByType('is_new'),
            '{{LOCATION}}' => $this->replaceSelectionByType('location'),
            '{{SOLD_STATUS}}' => $this->soldStatusLabel(),

        ], $default->title ?? '');
        return str_replace(['||', '|  |', '| |'], '|', $result);
    }

    private function seoDescription(): string
    {
        $default = $this->seoDefault();

        $result = $this->replaceSeoTags([
            '{{MAKE}}' => $this->replaceSelectionByType('make'),
            '{{MODEL}}' => $this->replaceSelectionByType('model'),
            '{{FUEL_TYPE}}' => $this->replaceSelectionByType('fuel_type'),
            '{{BODY_STYLE}}' => $this->replaceSelectionByType('body_type'),
            '{{TRANSMISSION}}' => $this->replaceSelectionByType('transmission'),
            '{{COLOUR}}' => $this->replaceSelectionByType('colour'),
            '{{VEHICLE_TYPE}}' => $this->replaceSelectionByType('vehicle_type'),
            '{{CONDITION}}' => $this->replaceSelectionByType('is_new'),
            '{{CURRENT_PAGE}}' => $this->getCurrentPageText(),
            '{{PAGE}}' => $this->getCurrentPage(),
            '{{SITE_NAME}}' => Settings::get('app-name'),
            '{{NEW_USED}}' => $this->replaceSelectionByType('is_new'),
            '{{LOCATION}}' => $this->replaceSelectionByType('location'),
            '{{SOLD_STATUS}}' => $this->soldStatusLabel(),

        ], $default->description ?? '');

        return str_replace(['||', '|  |', '| |'], '|', $result);
    }

    private function seoHeading(?SeoDefault $default): string
    {
        if (empty($default->heading)) {
            return '';
        }

        $result = $this->replaceSeoTags([
            '{{MAKE}}' => $this->replaceSelectionByType('make'),
            '{{MODEL}}' => $this->replaceSelectionByType('model'),
            '{{FUEL_TYPE}}' => $this->replaceSelectionByType('fuel_type'),
            '{{BODY_STYLE}}' => $this->replaceSelectionByType('body_type'),
            '{{TRANSMISSION}}' => $this->replaceSelectionByType('transmission'),
            '{{COLOUR}}' => $this->replaceSelectionByType('colour'),
            '{{VEHICLE_TYPE}}' => $this->replaceSelectionByType('vehicle_type'),
            '{{CONDITION}}' => $this->replaceSelectionByType('is_new'),
            '{{SITE_NAME}}' => Settings::get('app-name'),
            '{{NEW_USED}}' => $this->replaceSelectionByType('is_new'),
            '{{LOCATION}}' => $this->replaceSelectionByType('location'),
            '{{SOLD_STATUS}}' => $this->soldStatusLabel(),
        ], $default->heading);

        return str_replace(['||', '|  |', '| |'], '|', $result);
    }

    private function seoContentDescription(?SeoDefault $default): string
    {
        if (empty($default->content_description)) {
            return '';
        }

        $result = $this->replaceSeoTags([
            '{{MAKE}}' => $this->replaceSelectionByType('make'),
            '{{MODEL}}' => $this->replaceSelectionByType('model'),
            '{{FUEL_TYPE}}' => $this->replaceSelectionByType('fuel_type'),
            '{{BODY_STYLE}}' => $this->replaceSelectionByType('body_type'),
            '{{TRANSMISSION}}' => $this->replaceSelectionByType('transmission'),
            '{{COLOUR}}' => $this->replaceSelectionByType('colour'),
            '{{VEHICLE_TYPE}}' => $this->replaceSelectionByType('vehicle_type'),
            '{{CONDITION}}' => $this->replaceSelectionByType('is_new'),
            '{{SITE_NAME}}' => Settings::get('app-name'),
            '{{NEW_USED}}' => $this->replaceSelectionByType('is_new'),
            '{{LOCATION}}' => $this->replaceSelectionByType('location'),
            '{{SOLD_STATUS}}' => $this->soldStatusLabel(),
        ], $default->content_description);

        return str_replace(['||', '|  |', '| |'], '|', $result);
    }

    /**
     * Get the current pagination page of the filter
     *
     * @return int
     */
    #[\Override]
    public function getCurrentPage(): int
    {
        return (int)$this->request->input('page', 1);
    }

    public function getCurrentPageText(): string
    {
        $page = (int)$this->request->input('page', 1);

        return $page > 1
            ? __('pagination.current_page', ['page' => $page])
            : '';
    }

    private function seoDefault(): ?SeoDefault
    {
        if (!isset($this->seo_default)) {
            $this->seo_default = SeoDefault::query()->where('section', 'filter')->first();
        }
        return $this->seo_default;
    }

    private function replaceSelectionByType(string $type): string
    {
        if (empty($this->selections[$type]) || count($this->selections[$type]) > 1) {
            return '';
        }

        $value = $this->selections[$type][0] ?? '';

        if ($type === 'is_new') {
            return ucfirst($value);
        }

        if ($type === 'vehicle_type' && strtolower($value) === 'lcv') {
            return Settings::get('seo-vehicle-type-label-lcv', 'LCV');
        }

        return $this->selectionNameInIndex($type, $value) ?? '';
    }

    /**
     * Resolve the {{SOLD_STATUS}} placeholder for filter pages.
     *
     * Filter listings show for-sale stock by default, so the placeholder reads "For Sale"
     * on any page without an explicit sold filter (e.g. /used-cars/ford/focus) and "Sold"
     * only when the sold filter is applied (e.g. /sold-vehicles/ford/focus).
     *
     * When both 'sold' and 'available' are selected the listing shows all stock regardless
     * of sold status, so the label is omitted rather than mislabelling a mixed listing.
     * This mirrors replaceSelectionByType(), which also drops the label for multi-value
     * selections.
     */
    private function soldStatusLabel(): string
    {
        $values = $this->selections['is_sold'] ?? [];
        $soldSelected = in_array('sold', $values, true);
        $availableSelected = in_array('available', $values, true);

        if ($soldSelected && $availableSelected) {
            return '';
        }

        return $soldSelected
            ? __('labels.vehicle.sold')
            : __('labels.vehicle.for-sale');
    }

    private function replaceSeoTags(array $replacements, string $string): string
    {
        $result = str_replace(array_keys($replacements), $replacements, $string);
        return preg_replace('/\s+/', ' ', trim($result));
    }

    /**
     * @return Builder[]|\Illuminate\Database\Eloquent\Collection|Collection
     */
    #[\Override]
    protected function getFilterIndexForSelections(): Collection
    {
        if (empty($this->index)) {
            $index_query = FilterIndex::query();
            collect($this->selections)
                ->reject(fn ($selections, $type) => $this->getCustomPatternFilters()->has($type))
                ->each(function ($selections, $type) use ($index_query) {
                    $index_query->orWhere(function ($selection_query) use ($type, $selections) {
                        $selection_query->where('filter_type', $type)->whereIn('slug', $selections);
                    });
                });
            $this->index = $index_query
                ->get();
        }

        return $this->index;
    }

    protected function loadFacetConfig(): void
    {
        if (!Feature::isEnabled('search-facets')) {
            $this->facetConfig = collect();
            return;
        }
        $this->facetConfig = SearchFacet::query()->orderBy('position')->get();
    }

    protected function isFacetFilter(string $filterType): bool
    {
        return $this->facetConfig?->contains('filter_type', $filterType) ?? false;
    }

    protected function checkFacetOrderAndRedirect(): ?string
    {
        if ($this->facetConfig === null || $this->facetConfig->isEmpty()) {
            return null;
        }

        FilterIndex::query()
            ->whereIn('slug', array_keys($this->selections) ? array_merge(...array_values($this->selections)) : [])
            ->get()
            ->groupBy('filter_type');

        $facetSelections = [];
        $queryParams = [];

        foreach ($this->facetConfig as $facet) {
            $type = $facet->filter_type;
            if (isset($this->selections[$type])) {
                $slugs = $this->selections[$type];
                if (is_array($slugs)) {
                    if (count($slugs) === 1) {
                        $facetSelections[$facet->position] = $slugs[0];
                    } else {
                        $facetSelections[$facet->position] = $slugs[0];
                        $queryParams[$type] = array_slice($slugs, 1);
                    }
                } else {
                    $facetSelections[$facet->position] = $slugs;
                }
            }
        }

        ksort($facetSelections);
        $expectedPath = implode('/', array_values($facetSelections));

        $actualFacetSlugs = [];
        $requestSlug = $this->request->input('slug', '');
        $requestElements = $requestSlug ? explode('/', $requestSlug) : [];

        foreach ($requestElements as $element) {
            $matched = false;
            foreach ($this->facetConfig as $facet) {
                $type = $facet->filter_type;
                if (isset($this->selections[$type]) && in_array($element, (array)$this->selections[$type])) {
                    $actualFacetSlugs[] = $element;
                    $matched = true;
                    break;
                }
            }

            if (!$matched) {
                foreach ($this->selections as $type => $slugs) {
                    if (!$this->isFacetFilter($type) && in_array($element, (array)$slugs)) {
                        $queryParams[$type][] = $element;
                        break;
                    }
                }
            }
        }

        $actualPath = implode('/', $actualFacetSlugs);
        $hasNonFacetSelectionsInPath = !empty(array_filter(
            $queryParams,
            fn ($type) => !$this->isFacetFilter($type),
            ARRAY_FILTER_USE_KEY
        ));

        $shouldRedirect = ($expectedPath !== $actualPath || $hasNonFacetSelectionsInPath)
            && ($expectedPath || $hasNonFacetSelectionsInPath);
        if ($shouldRedirect) {
            $baseUrl = $this->request->input('base_url', '');
            $query = !empty($queryParams) ? '?' . $this->buildQueryString($queryParams) : '';
            return ltrim($baseUrl, '/') . ($expectedPath ? '/' . $expectedPath : '') . $query;
        }

        return null;
    }

    #[\Override]
    protected function matchSelectedFilters(): void
    {
        if (empty($this->filter_url_elements)) {
            return;
        }

        $matched = FilterIndex::query()
            ->whereIn('slug', $this->filter_url_elements)
            ->get();

        // When a slug exists under multiple filter types (e.g. "electric" as both model and fuel_type),
        // keep only the match that has active vehicles in the database.
        $matched = $matched->groupBy('slug')->flatMap(function ($group) {
            if ($group->count() <= 1) {
                return $group;
            }


            $withVehicles = $group->filter(fn ($entry) => $this->filterEntryHasVehicles($entry));

            if ($withVehicles->isNotEmpty()) {
                return collect([$withVehicles->first()]);
            }

            // No vehicles for any match — fall back to the one configured as a search facet
            $facetTypes = $this->facetConfig?->pluck('filter_type')->toArray() ?? [];
            $facetMatch = $group->first(fn ($entry) => in_array($entry->filter_type, $facetTypes));

            return collect([$facetMatch ?? $group->first()]);
        });

        $this->filter_url_elements = array_diff($this->filter_url_elements, $matched->pluck('slug')->toArray());

        $this->selections = collect($matched)
            ->groupBy('filter_type')
            ->map(fn ($group) => collect($group)->pluck('slug')->unique()->values())
            ->toArray();
    }

    /**
     * Check if a FilterIndex entry has active vehicles in the database.
     * Used to disambiguate when the same slug exists under multiple filter types.
     */
    private function filterEntryHasVehicles(FilterIndex $entry): bool
    {
        $columnMap = [
            'make' => 'make_id',
            'model' => 'model_id',
            'fuel_type' => 'fuel_type_id',
            'body_type' => 'body_style_id',
            'transmission' => 'transmission_id',
            'drivetrain' => 'drivetrain_id',
        ];

        $filterType = $entry->filter_type;

        if (isset($columnMap[$filterType])) {
            return Vehicle::active()
                ->where($columnMap[$filterType], $entry->filter_id)
                ->exists();
        }

        if ($filterType === 'colour') {
            return Vehicle::active()
                ->where('colour', $entry->name)
                ->exists();
        }

        return false;
    }

    /**
     * @param VehicleList $results
     * @return mixed
     */
    public function getRepresentativeFinance(JsonResource $results): ?VehicleFinance
    {
        $items = method_exists($results->resource, 'items')
            ? $results->resource->items()
            : $results->resource;

        $financeTypes = array_map(fn ($item) => $item->value, FinanceType::cases());
        $loadSpecificVehicleFinanceTypeExample = $this->target_model === Vehicle::class
            && in_array(Settings::get('filter-finance-example-type'), $financeTypes);

        if ($loadSpecificVehicleFinanceTypeExample) {
            if ($items instanceof Collection) {
                $items = $items->toArray();
            }
            return VehicleFinance::query()
                ->whereIn('vehicle_id', array_map(fn ($item) => $item['id'], $items))
                ->where('finance_type', Settings::get('filter-finance-example-type'))
                ->orderBy('monthly_price')
                ->first();
        }

        if (Settings::get('finance_representative_example_with_highest_apr')) {
            return $this->getRepresentativeFinanceExampleWithHighestApr(collect($items));
        }

        return $this->getFirstRepresentativeFinanceExample($items);
    }

    private function getFirstRepresentativeFinanceExample($items): ?VehicleFinance
    {
        foreach ($items as $item) {
            if (
                $item->relationLoaded('defaultFinanceExample')
                && !empty($item->defaultFinanceExample)
                && $item->defaultFinanceExample->monthly_price > 0
            ) {
                return $item->defaultFinanceExample;
            }
        }
        return null;
    }

    private function getRepresentativeFinanceExampleWithHighestApr($items): ?VehicleFinance
    {
        // 1. Filter the items to only include those with a valid finance example.
        $validFinanceExamples = $items->filter(fn ($item) => $item->relationLoaded('defaultFinanceExample')
            && !empty($item->defaultFinanceExample)
            && $item->defaultFinanceExample->monthly_price > 0)->map(fn ($item) => $item->defaultFinanceExample);

        if ($validFinanceExamples->isEmpty()) {
            return null;
        }

        $maxApr = $validFinanceExamples->max('apr');

        return $validFinanceExamples->firstWhere('apr', $maxApr);
    }

    protected function groupRangeFilters($filters)
    {
        $filters_to_return = [];

        foreach ($filters as $filter_name => $filter) {
            if (
                $filter['ui_component'] !== 'range-filter'
                || !array_key_exists('is_range_maximum', $filter)
                || empty($filter['range_group'])
            ) {
                $filters_to_return[$filter_name] = $filter;
                continue;
            }

            if (!array_key_exists((string) $filter['range_group'], $filters_to_return)) {
                $filters_to_return[$filter['range_group']] = $filter;
                $filters_to_return[$filter['range_group']]['results'] = [];
            }

            if ($filter['is_range_maximum']) {
                $filters_to_return[$filter['range_group']]['results']['filter_max'] = $filter['results'];
                $filters_to_return[$filter['range_group']]['search_term_max'] = $filter_name;
            } else {
                $filters_to_return[$filter['range_group']]['results']['filter_min'] = $filter['results'];
                $filters_to_return[$filter['range_group']]['search_term_min'] = $filter_name;
            }

            unset($filters_to_return[$filter['range_group']]['is_range_maximum']);
        }

        return $filters_to_return;
    }

    protected function trackSearchTerm(): void
    {
        $sessionCookieName = config('session.cookie');
        if (!$this->request->cookies->has($sessionCookieName)) {
            return;
        }

        $term = $this->selections['search'][0] ?? null;
        if (!$term) {
            return;
        }

        $normalized = mb_strtolower(trim($term));

        if ($this->request->hasSession()) {
            $sessionKey = 'tracked_search_term';
            if ($this->request->session()->get($sessionKey) === $normalized) {
                return;
            }
            $this->request->session()->put($sessionKey, $normalized);
        }

        SearchTermLog::track($term);
    }

    protected function getConfigFilters(): array
    {
        $additionalFilters = $this->cache('additional-filters', 15, fn () => $this->getAdditionalFilters());

        return array_merge($this->config['filters'], $additionalFilters);
    }

    protected function getConfigSortOptions(): array
    {
        $additional_sort_options = $this->cache(
            'additional-sort-options',
            15,
            fn () => $this->product_handler->additionalSortOptions()
        );
        return array_merge(
            $this->config['sort_options'][$this->product_handler->sortOptionName()],
            $additional_sort_options
        );
    }

    public function instantiateFilter(string $type): IsFilter
    {
        return App::make($this->getConfigFilters()[$type], [
            'type' => $type,
            'filter_target_model' => $this->target_model,
        ]);
    }

    protected function getSettingsFilterName(string $name, $filter = null): string
    {
        if ($filter instanceof VehicleAttributeValueMinFilter) {
            return 'automotive-vehicle-filters-vehicle_attribute_range';
        }

        return "automotive-vehicle-filters-$name";
    }

    protected function shouldApplyStockProviderSort(): bool
    {
        if (Settings::get('filter-results-order-by-stock-provider') !== true) {
            return false;
        }

        $allowedSortOptions = Settings::get('filter-results-stock-provider-sort-options', []);

        if (empty($allowedSortOptions)) {
            return false;
        }

        return in_array($this->active_sort_option_name, $allowedSortOptions);
    }

    protected function buildStockProviderCaseExpression(): string
    {
        $providers = Settings::get('filter-results-stock-provider-priority', []);

        if (empty($providers)) {
            return (string) self::SORT_INDEX_LAST;
        }

        $pdo = DB::connection()->getPdo();
        $case = "CASE COALESCE(NULLIF(vehicles.stock_provider, ''), 'default') ";
        foreach ($providers as $index => $provider) {
            $quoted = $pdo->quote($provider);
            $case .= "WHEN {$quoted} THEN {$index} ";
        }
        $case .= "ELSE " . self::SORT_INDEX_LAST . " END";

        return $case;
    }

    protected function shouldSortSoldVehiclesLast(): bool
    {
        return Settings::get('filter-results-sort-sold-vehicles-last') === true;
    }

    #[\Override]
    protected function setSorting($query)
    {
        if ($this->shouldApplyStockProviderSort() && !$this->stockProviderSortApplied) {
            $case = $this->buildStockProviderCaseExpression();
            if ($case !== (string) self::SORT_INDEX_LAST) {
                $query->orderByRaw($case);
            }
        }

        if ($this->shouldSortSoldVehiclesLast()) {
            $query->orderBy('is_sold', 'asc');
        }

        return App::make(
            $this->getConfigSortOptions()[$this->active_sort_option_name]
        )->handle($query, $this->active_sort_option_name);
    }

    protected function getSortOptionName($sort_name): string
    {
        $translationKey = "filter::filter.sort_options.$sort_name";
        if (Lang::has($translationKey)) {
            return __($translationKey);
        }

        if (preg_match('/^(.+)-(asc|desc)$/', $sort_name, $matches)) {
            $slug = $matches[1];
            $direction = $matches[2];
            $attributeName = VehicleAttribute::query()
                ->where('sortable', 1)
                ->where('slug', $slug)
                ->value('name');

            if ($attributeName) {
                $label = $direction === 'asc' ? '(Low > High)' : '(High > Low)';
                return "$attributeName $label";
            }
        }

        $vehicleAttribute = VehicleAttribute::query()
            ->where('sortable', 1)
            ->where('slug', $sort_name)
            ->value('name');

        return $vehicleAttribute ?: '';
    }

    protected function getAdditionalFilters(): array
    {
        $filters = [];

        if (!isset($this->filterableAttributes)) {
            $this->filterableAttributes = VehicleAttribute::query()
                ->where('filterable', 1)
                ->where('model', App::make($this->target_model)->getMorphClass())
                ->get();
        }

        foreach ($this->filterableAttributes as $filterableAttribute) {
            match ($filterableAttribute->type) {
                'boolean' => $filters[$filterableAttribute->slug] = VehicleAttributeBooleanFilter::class,
                'number', 'integer' => (function () use (&$filters, $filterableAttribute) {
                    $filters[$filterableAttribute->slug . '_min'] = VehicleAttributeValueMinFilter::class;
                    $filters[$filterableAttribute->slug . '_max'] = VehicleAttributeValueMaxFilter::class;
                })(),
                'select' => $filters[$filterableAttribute->slug] = VehicleAttributeSelectFilter::class,
                default => null,
            };
        }

        return $filters;
    }

    protected function getCheapestOffer(): ?float
    {
        return $this->target_model == KeyloopLeaseVehicleVariant::class
            ? $this->query->min('price_ex_vat_ex_vrt')
            : $this->query->min('price');
    }
}
