<?php

namespace App\Livewire;

use App\Services\NotificationService;
use App\Services\PreferencesStore;
use App\Support\DashboardWidgetLayout;
use Livewire\Attributes\Layout;
use Livewire\Component;

#[Layout('layouts.app')]
class DashboardPage extends Component
{
    /** @var list<string> */
    private const WIDTH_OPTIONS = ['full', 'two-thirds', 'half', 'third', 'quarter'];

    /** @var list<string> */
    private const HEIGHT_OPTIONS = ['compact', 'standard', 'expanded'];

    /** @var list<string> */
    public array $order = [];

    /** @var list<string> */
    public array $hidden = [];

    /** @var array<string, array{width?: string, height?: string}> */
    public array $sizes = [];

    public bool $editing = false;

    public ?string $draggingId = null;

    public ?string $overId = null;

    public bool $showPushPrompt = false;

    public function mount(NotificationService $notifications): void
    {
        $p = PreferencesStore::all();
        $this->order = $this->mergeOrder($p['dashboard']['order'] ?? []);
        $this->hidden = $this->sanitizeHidden($p['dashboard']['hidden'] ?? []);
        $this->sizes = $this->sanitizeSizes($p['dashboard']['sizes'] ?? []);

        $state = (string) ($p['notifications']['pushPromptState'] ?? 'pending');
        $lastShownAt = $p['notifications']['pushPromptLastShownAt'] ?? null;
        $canShowAgain = true;
        if (is_string($lastShownAt) && $lastShownAt !== '') {
            $canShowAgain = now()->diffInHours($lastShownAt) >= 24;
        }

        if (in_array($state, ['pending', 'snoozed'], true) && $canShowAgain) {
            $this->showPushPrompt = true;
            PreferencesStore::update(function (array $prefs) {
                $prefs['notifications']['pushPromptLastShownAt'] = now()->toIso8601String();

                return $prefs;
            });

            $user = auth()->user();
            if ($user !== null) {
                $notifications->emit($user, 'system.push_optin_prompt', [
                    'title' => __('notifications.types.system_push_prompt_title'),
                    'message' => __('notifications.types.system_push_prompt_message'),
                    'dedupe_key' => 'system:push-optin:'.now()->toDateString(),
                    'is_silent' => true,
                ]);
            }
        }
    }

    public function dismissPushPrompt(): void
    {
        $this->showPushPrompt = false;
        PreferencesStore::update(function (array $prefs) {
            $prefs['notifications']['pushPromptState'] = 'snoozed';

            return $prefs;
        });
    }

    /** @param  list<string>  $stored */
    protected function mergeOrder(array $stored): array
    {
        $defaults = PreferencesStore::DEFAULT_WIDGET_ORDER;
        $merged = [...$stored];
        foreach ($defaults as $id) {
            if (! in_array($id, $merged, true)) {
                $merged[] = $id;
            }
        }

        return array_values(array_filter($merged, fn ($id) => in_array($id, $defaults, true)));
    }

    protected function sanitizeHidden(mixed $stored): array
    {
        if (! is_array($stored)) {
            return [];
        }

        $defaults = PreferencesStore::DEFAULT_WIDGET_ORDER;
        $hidden = array_values(array_filter(
            $stored,
            fn ($id) => is_string($id) && in_array($id, $defaults, true)
        ));

        return array_values(array_unique($hidden));
    }

    protected function sanitizeSizes(mixed $stored): array
    {
        if (! is_array($stored)) {
            return [];
        }

        $defaults = PreferencesStore::DEFAULT_WIDGET_ORDER;
        $clean = [];

        foreach ($stored as $widgetId => $size) {
            if (! is_string($widgetId) || ! in_array($widgetId, $defaults, true) || ! is_array($size)) {
                continue;
            }

            $entry = [];
            if (is_string($size['width'] ?? null) && in_array($size['width'], self::WIDTH_OPTIONS, true)) {
                $entry['width'] = $size['width'];
            }
            if (is_string($size['height'] ?? null) && in_array($size['height'], self::HEIGHT_OPTIONS, true)) {
                $entry['height'] = $size['height'];
            }

            if ($entry !== []) {
                $clean[$widgetId] = $entry;
            }
        }

        return $clean;
    }

    /** @return list<string> */
    public function visibleWidgets(): array
    {
        if ($this->editing) {
            return $this->order;
        }

        return array_values(array_filter($this->order, fn ($id) => ! in_array($id, $this->hidden, true)));
    }

    public function toggleEditing(): void
    {
        $this->editing = ! $this->editing;
    }

    public function dragStart(string $id): void
    {
        if (! $this->editing || ! in_array($id, PreferencesStore::DEFAULT_WIDGET_ORDER, true)) {
            return;
        }
        $this->draggingId = $id;
    }

    public function dragOver(string $id): void
    {
        if ($this->draggingId && $this->draggingId !== $id) {
            $this->overId = $id;
        }
    }

    public function drop(string $targetId): void
    {
        if (! $this->draggingId || $this->draggingId === $targetId) {
            return;
        }
        $this->dropByIds($this->draggingId, $targetId);
    }

    public function dropByIds(string $sourceId, string $targetId): void
    {
        if ($sourceId === $targetId) {
            return;
        }
        if (
            ! in_array($sourceId, PreferencesStore::DEFAULT_WIDGET_ORDER, true)
            || ! in_array($targetId, PreferencesStore::DEFAULT_WIDGET_ORDER, true)
        ) {
            return;
        }
        $next = array_values($this->order);
        $from = array_search($sourceId, $next, true);
        $to = array_search($targetId, $next, true);
        if ($from === false || $to === false) {
            return;
        }
        $item = $next[$from];
        array_splice($next, (int) $from, 1);
        array_splice($next, (int) $to, 0, [$item]);
        $this->order = $next;
        $this->persistDashboard();
        $this->draggingId = null;
        $this->overId = null;
    }

    public function dragEnd(): void
    {
        $this->draggingId = null;
        $this->overId = null;
    }

    public function toggleHidden(string $id): void
    {
        if (in_array($id, $this->hidden, true)) {
            $this->hidden = array_values(array_filter($this->hidden, fn ($h) => $h !== $id));
        } else {
            $this->hidden[] = $id;
        }
        $this->persistDashboard();
    }

    public function resetLayout(): void
    {
        $this->order = PreferencesStore::DEFAULT_WIDGET_ORDER;
        $this->hidden = ['safe-to-spend'];
        $this->sizes = [];
        $this->persistDashboard();
    }

    public function patchWidgetWidth(string $id, string $width): void
    {
        if (! in_array($id, PreferencesStore::DEFAULT_WIDGET_ORDER, true) || ! in_array($width, self::WIDTH_OPTIONS, true)) {
            return;
        }
        $this->sizes[$id] = array_merge($this->sizes[$id] ?? [], ['width' => $width]);
        $this->persistDashboard();
    }

    public function patchWidgetHeight(string $id, string $height): void
    {
        if (! in_array($id, PreferencesStore::DEFAULT_WIDGET_ORDER, true) || ! in_array($height, self::HEIGHT_OPTIONS, true)) {
            return;
        }
        $this->sizes[$id] = array_merge($this->sizes[$id] ?? [], ['height' => $height]);
        $this->persistDashboard();
    }

    protected function persistDashboard(): void
    {
        $this->order = $this->mergeOrder($this->order);
        $this->hidden = $this->sanitizeHidden($this->hidden);
        $this->sizes = $this->sanitizeSizes($this->sizes);

        PreferencesStore::update(function (array $p) {
            $p['dashboard']['order'] = $this->order;
            $p['dashboard']['hidden'] = $this->hidden;
            $p['dashboard']['sizes'] = $this->sizes;

            return $p;
        });
    }

    /** @return array{width: string, height: string} */
    public function sizeFor(string $id): array
    {
        return DashboardWidgetLayout::resolveWidgetSize($id, $this->sizes);
    }

    public function cellClass(string $id): string
    {
        return DashboardWidgetLayout::getWidgetCellClassName($this->sizeFor($id));
    }

    public function render()
    {
        return view('livewire.dashboard-page', [
            'visibleWidgets' => $this->visibleWidgets(),
            'gridClass' => DashboardWidgetLayout::getDashboardGridClassName($this->editing),
        ])->title((string) __('budgets.dashboard_page.document_title'));
    }
}
