<?php

namespace App;

use App\Contracts\InteractsWithContentSync;
use App\Http\Resources\ElementUsageListResource;
use Illuminate\Support\Collection;
use Mtc\ContentManager\Contracts\ContentElement;
use Mtc\ContentManager\Contracts\ContentElementField;
use Mtc\ContentManager\Models\GlobalContent;
use Mtc\MercuryDataModels\NewCar;
use Mtc\MercuryDataModels\Page;
use Mtc\MercuryDataModels\VehicleOffer;

class ElementRepository extends \Mtc\ContentManager\ElementRepository implements InteractsWithContentSync
{
    public function exportToRemote(array $selections): array
    {
        return $this->elementModel->newQuery()
            ->with('fields.childElement')
            ->whereIn('id', $selections)
            ->get()
            ->map(function (ContentElement $element) {
                $data = $element->toArray();
                $data['fields'] = $element->fields
                    ->map(function (ContentElementField $field) {
                        $data = $field->toArray();
                        if ($data['child_element_id']) {
                            $data['child_element_id'] = $field->childElement?->slug;
                            unset($data['child_element']);
                        }
                        return $data;
                    });
                return $data;
            })
            ->toArray();
    }

    public function importRecord(array $entry): bool
    {
        $element = $this->elementModel->newQuery()
            ->create($entry);
        if (!empty($entry['fields'])) {
            collect($entry['fields'])
                ->each(function ($field) use ($element) {
                    if (!empty($field['child_element_id'])) {
                        $field['child_element_id'] = $this->elementModel->newQuery()
                            ->where('slug', $field['child_element_id'])
                            ->firstOrFail()
                            ?->id;
                    }
                    $element->fields()->create($field);
                });
        }

        return $element->exists;
    }

    public function canBeImported(array $entry): bool
    {
        return collect($entry['fields'])
            ->reject(fn($field) => !$field['child_element_id']
                || ($field['child_element_id'] = $this->elementModel->newQuery()
                    ->where('slug', $field['child_element_id'])
                    ->exists()))->isEmpty();
    }

    public function checkImportEntryValidity(array $dataEntry, array $allEntries): array
    {
        $errors = [];
        if (empty($dataEntry['slug'])) {
            $errors[] = __('validation.import_slug_missing', ['slug' => $dataEntry['slug']]);
        } elseif ($this->elementModel->newQuery()->where('slug', $dataEntry['slug'])->exists()) {
            $errors[] = __('validation.import_slug_taken', ['slug' => $dataEntry['slug']]);
        }
        $missingElements = $this->missingElements($dataEntry['fields'], $allEntries);
        $missingGlobalContent = $this->missingGlobalContent($dataEntry['fields']);
        foreach ($missingElements as $element) {
            $errors[] = __('validation.import_element_missing', [
                'slug' => $element['child_element_id'],
                'name' => $element['name']
            ]);
        }
        foreach ($missingGlobalContent as $content) {
            $errors[] = __('validation.import_global_content_missing', [
                'slug' => $content['global_content_id'],
                'name' => $content['name']
            ]);
        }
        return [
            'data' => $dataEntry,
            'errors' => $errors,
        ];
    }


    private function missingElements(Collection|array $fields, array $syncedEntries): Collection
    {
        /** @var Collection $elements */
        $syncedEntrySlugs = collect($syncedEntries)->pluck('slug');
        $elements = collect($fields)->pluck('child_element_id')->filter();
        $existing = $this->elementModel->newQuery()->whereIn('slug', $elements)->pluck('slug');
        $missing = $elements->diff($existing)
            ->reject(fn($missing) => $syncedEntrySlugs->search($missing) !== false);
        return collect($fields)
            ->filter(fn($field) => isset($field['child_element_id'])
                && $missing->search($field['child_element_id']) !== false);
    }

    private function missingGlobalContent(Collection|array $fields): Collection
    {
        $elements = collect($fields)->pluck('global_content_id')->filter();
        $existing = GlobalContent::query()->whereIn('slug', $elements)->pluck('slug');
        $missing = $elements->diff($existing);
        return collect($fields)
            ->filter(fn($field) => isset($field['global_content_id'])
                && $missing->search($field['global_content_id']) !== false);
    }


    public function listOptions(?string $category = null)
    {
        return $this->elementModel->newQuery()
            ->when($category, fn($query) => $query->where('category', $category))
            ->orderBy('title')
            ->active()
            ->select(['id', 'title as name'])
            ->get();
    }

    public function update(ContentElement $element, array $input): ContentElement
    {
        $this->setModel($element);
        $element->fill([
            'title' => $input['title'],
            'subtitle' => $input['subtitle'] ?? '',
            'icon' => $input['icon'] ?? null,
            'preview_image' => $input['preview_image']['path'] ?? null,
            'category' => $input['category'] ?? null,
            'ui_component' => $input['ui_component'] ?? null,
            'is_enabled' => $input['is_enabled'] ?? false,
            'drag_and_drop' => $input['drag_and_drop'] ?? false,
            'slug' => $input['slug'] ?? null,
            'data' => [
                'repeatable' => $input['repeatable'] ?? false,
                'nestable' => $input['nestable'] ?? false,
                'nested_limit' => $input['nested_limit'] ?? null,
            ],
        ])->save();

        $this->syncFields(collect($input['fields'] ?? []));
        return $element;
    }

    protected function fieldDataValues($field): array
    {
        $except = [];
        if ($this->hasCustomFieldUI($field) === false) {
            $except = [
                'componentName',
                'component',
                'icon',
            ];
        }
        return collect(parent::fieldDataValues($field))
            ->except([
                'error',
                'optionsMenuVisible',
            ])
            ->except($except)
            ->toArray();
    }

    private function hasCustomFieldUI(array $field): bool
    {
        if ($field['field_type'] === 'element') {
            return true;
        }

        // TODO: determine if value differs from primitive
        return false;
    }

    public function getUsage(int $elementId): array
    {
        return (new ElementUsageListResource(collect([
            'templates' => $this->findTemplatesUsingElement($elementId),
            'freeform_pages' => $this->findPagesUsingElement($elementId),
            'global_elements' => $this->findGlobalElementsUsingElement($elementId),
            'offers' => $this->findOffersUsingElement($elementId, 0),
            'new_cars' => $this->findNewCarsUsingElement($elementId, 0),
            'menus' => $this->findMenusUsingElement($elementId),
        ])
            ->flatten(1)))
        ->setElement($this->elementModel->newQuery()->find($elementId))
        ->toArray(request());
    }

    /**
     * Remove a content element
     *
     * @param int $elementId
     * @return bool
     */
    public function destroy(int $elementId): bool
    {
        /** @var ContentElement $element */
        $element = $this->elementModel
            ->newQuery()
            ->find($elementId);

        $element->fields()->delete();

        $templates = $this->fullUsageList($elementId, 'templates');
        $templates->each(function ($template) use ($elementId) {
            $template->pages->each(function ($page) use ($elementId) {
                $page->allContent()->where('element_id', $elementId)->delete();
            });

            $template->elements()->where('element_id', $elementId)->delete();
        });

        $pages = $this->fullUsageList($elementId, 'freeform_pages');
        $pages->each(fn($page) => $page->content()->where('element_id', $elementId)->delete());

        $menus = $this->fullUsageList($elementId, 'menus');
        $menus->each(fn($menu) => $menu->entries()->where('element_id', $elementId)->delete());

        $globalElements = $this->fullUsageList($elementId, 'global_elements');
        $globalElements->each(fn($globalElement) => $globalElement->delete());

        return $element->delete();
    }

    public function findPagesUsingElement(int $elementId, int $limit = 10): Collection
    {
        return Page::query()
            ->whereHas('allContent', fn($query) => $query->where('element_id', $elementId))
            ->limit($limit ?: null)
            ->get();
    }

    public function fullUsageList(int $elementId, string $type): Collection
    {
        return match ($type) {
            'templates' => $this->findTemplatesUsingElement($elementId, 0),
            'freeform_pages' => $this->findPagesUsingElement($elementId, 0),
            'offers' => $this->findOffersUsingElement($elementId, 0),
            'new_cars' => $this->findNewCarsUsingElement($elementId, 0),
            'global_elements' => $this->findGlobalElementsUsingElement($elementId, 0),
            'menus' => $this->findMenusUsingElement($elementId, 0),
        };
    }

    public function findOffersUsingElement(int $elementId, int $limit = 10): Collection
    {
        return VehicleOffer::query()
            ->whereHas('allContent', fn($query) => $query->where('element_id', $elementId))
            ->limit($limit ?: null)
            ->get();
    }

    public function findNewCarsUsingElement(int $elementId, int $limit = 10): Collection
    {
        return NewCar::query()
            ->whereHas('allContent', fn($query) => $query->where('element_id', $elementId))
            ->limit($limit ?: null)
            ->get();
    }
}
