<?php

namespace App\Filament\Resources\PageResource\Pages;

use App\Filament\Resources\PageResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
use Filament\Support\Enums\MaxWidth;
use Illuminate\Support\Facades\Cache;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;

use Filament\Resources\Pages\EditRecord\Concerns\Translatable;

class EditPage extends EditRecord
{
    use Translatable;
    protected static string $resource = PageResource::class;
    public ?string $previewContentHash = null;
    public ?int $lastAutoPreviewAtMs = null;

    public function mount(int|string $record): void
    {
        parent::mount($record);
        $this->dispatch('page-edit-ready', componentId: $this->getId());
    }

    /**
     * Runs before every Livewire response — the ideal hook to sync the live
     * preview because $this->data is guaranteed to be fresh (same request as
     * the Builder action / wire:model update / locale switch that changed it).
     */
    public function dehydrate(): void
    {
        $this->previewDraft();
    }

    public function updated(string $name, mixed $value): void
    {
        $this->attemptAutoPreview($name, $value);
    }

    public function updatedData(mixed $value, ?string $name = null): void
    {
        $property = is_string($name) && $name !== '' ? 'data.' . $name : 'data';
        $this->attemptAutoPreview($property, $value);
    }

    /** Use full width so form + preview have maximum space and no empty margin. */
    public function getMaxContentWidth(): MaxWidth|string|null
    {
        return MaxWidth::Full;
    }

    protected function getHeaderActions(): array
    {
        return [
            Actions\DeleteAction::make(),
            Actions\Action::make('preview')
                ->label('معاينة')
                ->icon('heroicon-o-eye')
                ->url(fn () => route('page.show', $this->record->slug))
                ->openUrlInNewTab()
                ->color('gray'),
            Actions\LocaleSwitcher::make(),
        ];
    }

    protected function getFormActions(): array
    {
        return [
            $this->getSaveFormAction(),
            $this->getCancelFormAction(),
        ];
    }

    protected function getSaveFormAction(): Actions\Action
    {
        return parent::getSaveFormAction()
            ->label('حفظ التغييرات')
            ->icon('heroicon-o-check-circle')
            ->extraAttributes(['id' => 'save-button']);
    }

    protected function getRedirectUrl(): string
    {
        return $this->getResource()::getUrl('edit', ['record' => $this->record]);
    }

    protected function getSavedNotificationTitle(): ?string
    {
        return 'تم حفظ الصفحة بنجاح';
    }

    protected function afterSave(): void
    {
        // Dispatch browser event to refresh preview
        $this->dispatch('page-saved', previewUrl: route('page.show', $this->record->slug));
    }

    /**
     * Store current form state as draft and dispatch the preview URL so the iframe can show it without saving.
     */
    public function previewDraft(bool $force = false): void
    {
        file_put_contents('/tmp/preview_debug.log', "--- previewDraft START (Force: " . ($force ? 'yes' : 'no') . ") ---\n", FILE_APPEND);
        // Always read from the form engine — it is authoritative for Builder/Repeater state.
        // $this->data['content'] may be stale when previewDraft() is called via a direct
        // $wire.previewDraft() JS call (separate Livewire round-trip from the Builder action).
        try {
            $formRaw = $this->form->getRawState();
            $raw = is_array($formRaw) ? $formRaw : [];
        } catch (\Throwable) {
            $raw = [];
        }
        if (empty($raw)) {
            $raw = property_exists($this, 'data') && is_array($this->data) ? $this->data : [];
        }
        $activeLocale = $this->activeLocale ?? app()->getLocale();
        
        $extract = function ($fieldRaw) use ($activeLocale) {
            if (is_array($fieldRaw) && isset($fieldRaw[$activeLocale])) {
                return $fieldRaw[$activeLocale];
            }
            return $fieldRaw;
        };

        $data = [
            'locale' => $activeLocale,
            'title' => $extract($raw['title'] ?? $this->record?->title ?? ''),
            'slug' => $extract($raw['slug'] ?? $this->record?->slug ?? 'preview'),
            'meta_title' => $extract($raw['meta_title'] ?? null),
            'meta_description' => $extract($raw['meta_description'] ?? null),
            'meta_keywords' => $extract($raw['meta_keywords'] ?? null),
            'content' => $extract($raw['content'] ?? []),
            'custom_html' => $extract($raw['custom_html'] ?? null),
        ];

        file_put_contents('/tmp/preview_debug.log', "--- CALLED ---\nRAW CONTENT LENGTH: " . count($raw['content'] ?? []) . "\n" . json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n", FILE_APPEND);
        
        $data = $this->normalizePreviewData($data);
        
        file_put_contents('/tmp/preview_debug.log', "NORMALIZED CONTENT:\n" . json_encode($data['content'] ?? [], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n", FILE_APPEND);
        
        $hashPayload = $this->normalizeForHash($data);
        $hash = md5((string) json_encode($hashPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR));
        file_put_contents('/tmp/preview_debug.log', "HASH: $hash\n", FILE_APPEND);

        if (! $force && is_string($this->previewContentHash) && hash_equals($this->previewContentHash, $hash)) {
            return;
        }

        $this->previewContentHash = $hash;

        $draftCache = Cache::store('file');
        $userId = (int) auth()->id();
        $pageId = (int) $this->record->getKey();
        $previewKey = 'page_draft_preview_user_' . $userId . '_page_' . $pageId;

        $draftCache->put($previewKey, $data, now()->addMinutes(30));

        $url = route('preview-draft.page', ['page' => $this->record, 'v' => $hash]);
        $this->dispatch('draft-preview-url', url: $url, hash: $hash, silent: ! $force, force: $force);
    }

    public function previewDraftNow(): void
    {
        $this->previewDraft(true);
    }

    private function normalizePreviewData(mixed $value): mixed
    {
        if ($value instanceof TemporaryUploadedFile) {
            try {
                return $value->temporaryUrl();
            } catch (\Throwable) {
                return $value->getFilename();
            }
        }

        if ($value instanceof \Illuminate\Contracts\Support\Arrayable) {
            $value = $value->toArray();
        }

        if (! is_array($value)) {
            return $value;
        }

        if ($this->isEphemeralFileMap($value)) {
            $normalized = array_values(array_filter(array_map(
                fn (mixed $item) => $this->normalizePreviewData($item),
                array_values($value)
            ), fn (mixed $item) => $item !== null && $item !== ''));

            return count($normalized) === 1 ? ($normalized[0] ?? null) : $normalized;
        }

        foreach ($value as $key => $item) {
            $value[$key] = $this->normalizePreviewData($item);
        }

        return $value;
    }

    private function normalizeForHash(mixed $value): mixed
    {
        if ($value instanceof TemporaryUploadedFile) {
            return 'tmp-upload:' . $value->getFilename();
        }

        if (is_string($value)) {
            if (str_contains($value, 'livewire-file:')
                || str_contains($value, '/livewire/preview-file/')
                || str_contains($value, '/livewire-tmp/')
                || str_contains($value, '/tmp/livewire')) {
                return 'tmp-upload';
            }

            return $this->normalizeUrlString($value);
        }

        if (! is_array($value)) {
            return $value;
        }

        // FileUpload can be stored as random-key => path, where key changes between requests.
        // Normalize to values only so unchanged uploads don't trigger false hash changes.
        if ($this->isEphemeralFileMap($value)) {
            $normalizedValues = array_map(fn (mixed $item) => $this->normalizeForHash($item), array_values($value));
            sort($normalizedValues);
            return $normalizedValues;
        }

        // Normalize common FileUpload payloads to stable string identifiers.
        if (isset($value['path']) && is_string($value['path'])) {
            return $value['path'];
        }

        if (isset($value['temporaryUrl']) && is_string($value['temporaryUrl']) && ! isset($value['path'])) {
            return 'tmp-upload';
        }

        foreach (['temporaryUrl', 'preview_url', 'url'] as $urlKey) {
            if (isset($value[$urlKey]) && is_string($value[$urlKey])) {
                return $this->normalizeUrlString($value[$urlKey]);
            }
        }

        // Single-file arrays often look like [randomKey => "hero/file.jpg"].
        if (! array_is_list($value) && count($value) === 1) {
            $single = reset($value);
            if (is_string($single)) {
                return $this->normalizeUrlString($single);
            }
        }

        if (array_is_list($value)) {
            return array_map(fn (mixed $item) => $this->normalizeForHash($item), $value);
        }

        // Remove volatile upload metadata that changes between requests.
        unset(
            $value['temporaryUrl'],
            $value['preview_url'],
            $value['url'],
            $value['headers'],
            $value['expires'],
            $value['signature'],
            $value['signed']
        );

        if (! array_is_list($value)) {
            ksort($value);
        }

        foreach ($value as $key => $item) {
            $value[$key] = $this->normalizeForHash($item);
        }

        return $value;
    }

    private function normalizeUrlString(string $value): string
    {
        if (! str_starts_with($value, 'http://') && ! str_starts_with($value, 'https://')) {
            return $value;
        }

        $parts = parse_url($value);
        if (! is_array($parts)) {
            return $value;
        }

        return ($parts['host'] ?? '') . ($parts['path'] ?? '');
    }

    private function isEphemeralFileMap(array $value): bool
    {
        if (empty($value) || array_is_list($value)) {
            return false;
        }

        foreach ($value as $key => $item) {
            if (! is_string($key) || ! $this->looksEphemeralKey($key)) {
                return false;
            }

            if (is_string($item) || $item instanceof TemporaryUploadedFile) {
                continue;
            }

            if (is_array($item) && $this->looksLikeUploadPayload($item)) {
                continue;
            }

            return false;
        }

        return true;
    }

    private function looksLikeUploadPayload(array $value): bool
    {
        if ($value === []) {
            return false;
        }

        $primaryKeys = ['path', 'temporaryUrl', 'preview_url', 'url'];
        $hasPrimary = false;

        foreach ($primaryKeys as $primaryKey) {
            if (isset($value[$primaryKey]) && is_string($value[$primaryKey]) && $value[$primaryKey] !== '') {
                $hasPrimary = true;
                break;
            }
        }

        if (! $hasPrimary) {
            return false;
        }

        $allowedKeys = [
            'path',
            'temporaryUrl',
            'preview_url',
            'url',
            'name',
            'filename',
            'basename',
            'original_filename',
            'size',
            'filesize',
            'type',
            'mime_type',
            'extension',
            'headers',
            'expires',
            'signature',
            'signed',
        ];

        foreach (array_keys($value) as $key) {
            if (! is_string($key) || ! in_array($key, $allowedKeys, true)) {
                return false;
            }
        }

        return true;
    }

    private function looksEphemeralKey(string $key): bool
    {
        return (bool) preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $key)
            || (bool) preg_match('/^[A-Za-z0-9_-]{20,}$/', $key);
    }

    private function shouldAutoPreviewProperty(string $name): bool
    {
        // Avoid fighting with Livewire/FilePond transient upload lifecycle keys.
        if (str_contains($name, 'temporaryUploadedFile') || str_contains($name, 'filepond')) {
            return false;
        }

        return true;
    }

    private function containsTransientUpload(mixed $value): bool
    {
        if ($value instanceof TemporaryUploadedFile) {
            return true;
        }

        if (is_string($value)) {
            return str_contains($value, 'livewire-file:')
                || str_contains($value, '/livewire/preview-file/')
                || str_contains($value, '/livewire-tmp/')
                || str_contains($value, '/tmp/livewire');
        }

        if (! is_array($value)) {
            return false;
        }

        if (isset($value['temporaryUrl']) && is_string($value['temporaryUrl'])) {
            return true;
        }

        foreach ($value as $item) {
            if ($this->containsTransientUpload($item)) {
                return true;
            }
        }

        return false;
    }

    private function attemptAutoPreview(string $property, mixed $value = null): void
    {
        if (! str_starts_with($property, 'data')) {
            return;
        }

        if ($property !== 'data' && ! $this->shouldAutoPreviewProperty($property)) {
            return;
        }

        if ($this->containsTransientUpload($value)) {
            return;
        }

        $nowMs = (int) floor(microtime(true) * 1000);
        if (is_int($this->lastAutoPreviewAtMs) && ($nowMs - $this->lastAutoPreviewAtMs) < 700) {
            return;
        }

        $this->lastAutoPreviewAtMs = $nowMs;
        $this->previewDraft();
    }
}
