<?php

namespace Daylight\Core\Modules\PageBuilder\Concerns;

use Daylight\Core\Modules\PageBuilder\Contracts\HasComponents;
use Daylight\Core\Modules\PageBuilder\Contracts\HasPageBuilder;
use Daylight\Core\Modules\PageBuilder\Models\Component;
use Daylight\Core\Modules\PageBuilder\Registries\ComponentRegistry;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;

trait InteractsWithPageBuilderRequest
{
    public function syncComponentsFromRequest(HasPageBuilder $request, HasComponents $object): void
    {
        $registry = app(ComponentRegistry::class);
        $preservedComponents = [];

        $request->getComponentsFromRequest()->each(function (array $component) use ($registry, &$preservedComponents, $object) {
            $definition = $registry->getDefinition($component['type'])->fields();

            $relatedFields = $definition->filter(fn ($f) => $f->type === 'related');
            $mediaFields = $definition->filter(fn ($f) => $f->type === 'media');
            $repeaterFields = $definition->filter(fn ($f) => $f->type === 'repeater');

            $originalData = $component['data'];
            $component['data'] = $this->stripRepeaterMediaFields($component['data'], $repeaterFields);

            $componentModel = Component::updateOrCreate(
                ['id' => $component['id']],
                [
                    'buildable_id' => $object->id,
                    'buildable_type' => get_class($object),
                    ...Arr::except($component, $this->getExcludedFields($relatedFields, $mediaFields)),
                ],
            );

            $preservedComponents[] = $componentModel->id;

            $this->syncRelatedItems($componentModel, $component['data'], $relatedFields);
            $this->syncMediaItems($componentModel, $component['data'], $originalData, $mediaFields, $repeaterFields);
        });

        $object->components()->whereNotIn('id', $preservedComponents)->delete();
    }

    private function stripRepeaterMediaFields(array $data, Collection $repeaterFields): array
    {
        return collect($data)->map(function ($value, $field) use ($repeaterFields) {
            $repeater = $repeaterFields->firstWhere('name', $field);
            if (! $repeater || ! is_array($value)) {
                return $value;
            }

            $mediaFields = collect($repeater->items)->where('type', 'media')->pluck('name');

            return collect($value)->map(fn ($item) => collect($item)->except($mediaFields))->toArray();
        })->toArray();
    }

    private function getExcludedFields(Collection $relatedFields, Collection $mediaFields): array
    {
        return [
            ...$relatedFields->pluck('name')->map(fn ($name) => "data.$name")->toArray(),
            ...$mediaFields->pluck('name')->map(fn ($name) => "data.$name")->toArray(),
            'id',
        ];
    }

    private function syncRelatedItems($componentModel, array $data, Collection $relatedFields): void
    {
        $relations = $relatedFields->flatMap(function ($field) use ($data) {
            $items = $data[$field->name] ?? null;
            if (! $items) {
                return [];
            }

            return collect($items)->map(fn ($item) => [
                'relatable_id' => $item,
                'relatable_type' => $field->relatedType,
            ]);
        });

        $componentModel->relatables()->delete();
        $componentModel->relatables()->createMany($relations->values()->toArray());
    }

    private function syncMediaItems($componentModel, array $data, array $originalData, Collection $mediaFields, Collection $repeaterFields): void
    {
        $media = collect();

        foreach ($mediaFields as $field) {
            $items = $data[$field->name] ?? [];
            $media = $media->merge(
                collect($items)->map(fn ($item) => [
                    'id' => $item['id'],
                    'properties' => ['field' => $field->name],
                ])
            );
        }

        foreach ($repeaterFields as $field) {
            $mediaFieldNames = collect($field->items)->where('type', 'media')->pluck('name');
            $entries = $originalData[$field->name] ?? [];

            foreach ($entries as $index => $entry) {
                foreach ($mediaFieldNames as $mediaFieldName) {
                    $item = $entry[$mediaFieldName][0] ?? null;
                    if (! isset($item['id'])) {
                        continue;
                    }

                    $media->push([
                        'id' => $item['id'],
                        'properties' => [
                            'field' => "{$field->name}.{$index}.{$mediaFieldName}",
                        ],
                    ]);
                }
            }
        }

        $componentModel->media()->detach();

        if ($media->isNotEmpty()) {
            $media->each(fn ($item) => $componentModel->media()->attach($item['id'], ['properties' => $item['properties']]));
        }
    }
}
