<?php

namespace App\Livewire\Website;

use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Website\Blocks\BlockRegistry;
use App\Domain\Website\Blocks\FieldType;
use App\Domain\Website\Models\WebsiteBlock;
use App\Domain\Website\Models\WebsitePage;
use App\Domain\Website\Services\WebsiteBlockService;
use App\Domain\Shared\Traits\ChecksTemporaryUploads;
use Livewire\Component;
use Livewire\WithFileUploads;

/**
 * The block-tree editor.
 *
 * Field inputs are generated from each block type's BlockField schema, so a new
 * block type becomes fully editable here without touching this component.
 */
class PageBuilder extends Component
{
    use WithFileUploads;
    use ChecksTemporaryUploads;

    public WebsitePage $page;

    public ?int $selectedId = null;

    /** Working copy of the selected block's content, bound to the form. */
    public array $form = [];

    public array $styleForm = [];

    public string $variant = 'default';

    public bool $showPicker = false;

    public ?int $pickerParentId = null;

    public string $pickerSlot = 'default';

    public string $pickerSearch = '';

    public string $panel = 'content';

    /** Transient upload + the form path it should be written to. */
    public $upload = null;

    public ?string $uploadTarget = null;

    public string $previewport = 'desktop';

    public function mount(WebsitePage $page): void
    {
        $this->authorize('settings.manage');
        $this->page = $page;
    }

    public function getRegistryProperty(): BlockRegistry
    {
        return app(BlockRegistry::class);
    }

    public function getSelectedProperty(): ?WebsiteBlock
    {
        return $this->selectedId
            ? WebsiteBlock::where('website_page_id', $this->page->id)->find($this->selectedId)
            : null;
    }

    public function openPicker(?int $parentId = null, string $slot = 'default'): void
    {
        $this->pickerParentId = $parentId;
        $this->pickerSlot = $slot;
        $this->pickerSearch = '';
        $this->showPicker = true;
    }

    public function addBlock(string $type, WebsiteBlockService $service): void
    {
        $this->authorize('settings.manage');

        try {
            $parent = $this->pickerParentId ? WebsiteBlock::find($this->pickerParentId) : null;
            $block = $service->add($this->page, $type, $parent, $this->pickerSlot);

            $this->showPicker = false;
            $this->select($block->id);
            session()->flash('success', __('تمت إضافة العنصر'));
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        } catch (\InvalidArgumentException $e) {
            session()->flash('error', __('نوع العنصر غير معروف'));
        }
    }

    public function select(int $id): void
    {
        $block = WebsiteBlock::where('website_page_id', $this->page->id)->findOrFail($id);

        $this->selectedId = $block->id;
        $this->variant = $block->variant;
        $this->styleForm = $block->style ?? [];
        $this->panel = 'content';

        // Seed every declared field so wire:model has a key to bind to, even
        // for fields added to the block type after this row was created.
        $definition = $block->definition();
        $this->form = array_replace_recursive(
            $definition ? $definition->defaultData() : [],
            $block->data ?? [],
        );
    }

    public function saveBlock(WebsiteBlockService $service): void
    {
        $this->authorize('settings.manage');
        $block = $this->selected;

        if (! $block) {
            return;
        }

        $definition = $block->definition();

        if ($definition) {
            $this->validate(
                collect($definition->validationRules())
                    ->mapWithKeys(fn ($rule, $key) => [str_replace('data.', 'form.', $key) => $rule])
                    ->all(),
            );
        }

        try {
            $service->update($block, [
                'data' => $this->form,
                'style' => $this->styleForm,
                'variant' => $this->variant,
            ]);
            session()->flash('success', __('تم حفظ العنصر'));
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        }
    }

    /** Called by the media input before choosing a file. */
    public function setUploadTarget(string $path): void
    {
        $this->uploadTarget = $path;
    }

    /**
     * Persists an uploaded image and writes its public URL into the block form
     * at the recorded path, which may be nested inside a repeater row.
     */
    public function updatedUpload(): void
    {
        $this->authorize('settings.manage');

        if (! $this->temporaryUploadIsUsable('upload', $this->upload)) {
            return;
        }

        $this->validate([
            'upload' => 'image|max:4096',
        ], [
            'upload.image' => __('الملف يجب أن يكون صورة'),
            'upload.max' => __('حجم الصورة يجب ألا يتجاوز 4 ميجابايت'),
        ]);

        if (! $this->uploadTarget || ! str_starts_with($this->uploadTarget, 'form.')) {
            return;
        }

        $path = $this->upload->store('website/'.$this->page->academy_id, 'public');
        $url = \Illuminate\Support\Facades\Storage::disk('public')->url($path);

        $key = substr($this->uploadTarget, strlen('form.'));
        $existing = data_get($this->form, $key);

        // Gallery fields accumulate; single-image fields are replaced.
        if (is_array($existing)) {
            $existing[] = $url;
            data_set($this->form, $key, $existing);
        } else {
            data_set($this->form, $key, $url);
        }

        $this->reset('upload');
        $this->uploadTarget = null;
    }

    public function removeMedia(string $path, ?int $index = null): void
    {
        $key = str_starts_with($path, 'form.') ? substr($path, strlen('form.')) : $path;

        if ($index === null) {
            data_set($this->form, $key, null);

            return;
        }

        $list = data_get($this->form, $key, []);
        unset($list[$index]);
        data_set($this->form, $key, array_values($list));
    }

    public function addRepeaterRow(string $field): void
    {
        $definition = $this->selected?->definition();
        $blockField = $definition?->field($field);

        if (! $blockField || $blockField->type !== FieldType::Repeater) {
            return;
        }

        $row = [];
        foreach ($blockField->fields as $sub) {
            $row[$sub->key] = $sub->translatable ? ['ar' => '', 'en' => ''] : $sub->default;
        }

        $this->form[$field][] = $row;
    }

    public function removeRepeaterRow(string $field, int $index): void
    {
        if (! isset($this->form[$field][$index])) {
            return;
        }

        unset($this->form[$field][$index]);
        $this->form[$field] = array_values($this->form[$field]);
    }

    public function moveRepeaterRow(string $field, int $index, int $direction): void
    {
        $rows = $this->form[$field] ?? [];
        $target = $index + $direction;

        if (! isset($rows[$index], $rows[$target])) {
            return;
        }

        [$rows[$index], $rows[$target]] = [$rows[$target], $rows[$index]];
        $this->form[$field] = array_values($rows);
    }

    public function toggleBlock(int $id, WebsiteBlockService $service): void
    {
        $this->authorize('settings.manage');
        $block = WebsiteBlock::where('website_page_id', $this->page->id)->findOrFail($id);
        $service->update($block, ['is_enabled' => ! $block->is_enabled]);
    }

    public function duplicateBlock(int $id, WebsiteBlockService $service): void
    {
        $this->authorize('settings.manage');

        try {
            $service->duplicate(WebsiteBlock::where('website_page_id', $this->page->id)->findOrFail($id));
            session()->flash('success', __('تم نسخ العنصر'));
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        }
    }

    public function deleteBlock(int $id, WebsiteBlockService $service): void
    {
        $this->authorize('settings.manage');
        $service->delete(WebsiteBlock::where('website_page_id', $this->page->id)->findOrFail($id));

        if ($this->selectedId === $id) {
            $this->selectedId = null;
            $this->form = [];
        }

        session()->flash('success', __('تم حذف العنصر'));
    }

    /** Nudges a block within its own sibling list. */
    public function moveBlock(int $id, int $direction, WebsiteBlockService $service): void
    {
        $this->authorize('settings.manage');
        $block = WebsiteBlock::where('website_page_id', $this->page->id)->findOrFail($id);

        $siblings = WebsiteBlock::where('website_page_id', $this->page->id)
            ->where('parent_id', $block->parent_id)
            ->where('slot', $block->slot)
            ->orderBy('sort_order')->orderBy('id')
            ->pluck('id')->all();

        $index = array_search($block->id, $siblings, true);
        $target = $index + $direction;

        if ($index === false || ! isset($siblings[$target])) {
            return;
        }

        [$siblings[$index], $siblings[$target]] = [$siblings[$target], $siblings[$index]];
        $service->reorder($siblings);
    }

    /** Accepts a flat ordered id list from the drag-and-drop tree. */
    public function reorderBlocks(array $orderedIds, WebsiteBlockService $service): void
    {
        $this->authorize('settings.manage');

        $owned = WebsiteBlock::where('website_page_id', $this->page->id)
            ->whereIn('id', $orderedIds)
            ->pluck('id')->all();

        $service->reorder(array_values(array_filter($orderedIds, fn ($id) => in_array($id, $owned, true))));
    }

    public function render()
    {
        $tree = WebsiteBlock::where('website_page_id', $this->page->id)
            ->whereNull('parent_id')
            ->with('childrenRecursive')
            ->orderBy('sort_order')
            ->get();

        $groups = $this->registry->grouped();

        if ($this->pickerSearch !== '') {
            $needle = mb_strtolower($this->pickerSearch);
            $groups = collect($groups)
                ->map(fn ($types) => array_values(array_filter(
                    $types,
                    fn ($t) => str_contains(mb_strtolower($t->label()), $needle)
                        || str_contains($t->key(), $needle)
                        || str_contains(mb_strtolower($t->description()), $needle),
                )))
                ->filter()
                ->all();
        }

        return view('livewire.website.page-builder', [
            'tree' => $tree,
            'groups' => $groups,
            'block' => $this->selected,
            'definition' => $this->selected?->definition(),
        ]);
    }
}
