<?php

namespace App\Livewire\Admin;

use App\Models\MailTemplate;
use App\Services\Mail\MailTemplateRenderer;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Validate;
use Livewire\Component;

/**
 * The answers an operator gives over and over, written once.
 *
 * Most customer questions are the same five questions, and typing the answer
 * again every time is how two customers get told two different things about the
 * same subject.
 *
 * A template is not sent from here and never sent by itself: it is inserted into
 * the compose field on a customer's page, placeholders already filled from that
 * customer's record, and the last two sentences are still the operator's to
 * write. What goes out is what they saw and edited.
 *
 * Creating happens in the form on this page — R20 exempts a create form that IS
 * the page. Editing happens in a modal (Admin\EditMailTemplate), because a
 * textarea growing inside a table row is the row-height jump R20 exists to stop.
 */
#[Layout('layouts.admin')]
class MailTemplates extends Component
{
    #[Validate('required|string|max:255')]
    public string $name = '';

    #[Validate('required|string|max:255')]
    public string $subject = '';

    #[Validate('required|string|max:5000')]
    public string $body = '';

    public function mount(): void
    {
        $this->authorize('customers.manage');
    }

    public function create(): void
    {
        $this->authorize('customers.manage');

        $data = $this->validate();

        MailTemplate::create([
            'name' => trim($data['name']),
            'subject' => trim($data['subject']),
            'body' => $data['body'],
            // At the end of the list, where a new one belongs until somebody
            // decides otherwise.
            'sort' => (int) MailTemplate::query()->max('sort') + 10,
            'active' => true,
        ]);

        $this->reset(['name', 'subject', 'body']);
        $this->dispatch('notify', message: __('templates.created'));
    }

    /**
     * Retire one, or bring it back.
     *
     * Not deleted: an answer that is no longer given is still the answer
     * somebody was given last year, and the register of sent mail refers to it.
     */
    public function toggleActive(string $uuid): void
    {
        $this->authorize('customers.manage');

        $template = MailTemplate::query()->where('uuid', $uuid)->first();

        if ($template !== null) {
            $template->update(['active' => ! $template->active]);
        }
    }

    /**
     * Swap one with its neighbour.
     *
     * It used to add or subtract a fixed amount, which is not the same thing:
     * with the seeded values ten apart, ±15 usually landed correctly — and
     * sometimes landed on a value another template already had, or on the 0
     * floor along with three others. Equal `sort` values fall back to the name,
     * so a click could reorder something else, or visibly nothing, which is
     * indistinguishable from a click that never registered.
     *
     * A swap always moves exactly one place, or does nothing at the ends, and
     * both of those are what the operator expects to see.
     */
    public function move(string $uuid, int $direction): void
    {
        $this->authorize('customers.manage');

        $template = MailTemplate::query()->where('uuid', $uuid)->first();

        if ($template === null) {
            return;
        }

        // The one immediately above or below in the order the page shows —
        // read through the same ordering, so the swap agrees with the list.
        $neighbour = MailTemplate::query()
            ->when($direction < 0, fn ($q) => $q
                ->where(fn ($w) => $w->where('sort', '<', $template->sort)
                    ->orWhere(fn ($t) => $t->where('sort', $template->sort)->where('name', '<', $template->name)))
                ->orderByDesc('sort')->orderByDesc('name'))
            ->when($direction > 0, fn ($q) => $q
                ->where(fn ($w) => $w->where('sort', '>', $template->sort)
                    ->orWhere(fn ($t) => $t->where('sort', $template->sort)->where('name', '>', $template->name)))
                ->orderBy('sort')->orderBy('name'))
            ->first();

        if ($neighbour === null) {
            return;     // already at the end; nothing to swap with
        }

        // Distinct values, even where the two were equal before: leaving them
        // equal would put the order back in the hands of the name.
        $mine = $template->sort;
        $theirs = $neighbour->sort;

        if ($mine === $theirs) {
            // Moving UP means taking the lower value, so the one the mover
            // receives is below the neighbour's — not above it. The other way
            // round swaps them into the order they were already in, which looks
            // exactly like the bug this method was rewritten to fix.
            $theirs = $mine + ($direction < 0 ? -1 : 1);
        }

        $template->update(['sort' => $theirs]);
        $neighbour->update(['sort' => $mine]);
    }

    public function render()
    {
        $this->authorize('customers.manage');

        return view('livewire.admin.mail-templates', [
            'templates' => MailTemplate::query()->orderBy('sort')->orderBy('name')->get(),
            // The list IS the documentation: a token added to the renderer
            // appears here without anybody remembering to write it down twice.
            'placeholders' => MailTemplateRenderer::PLACEHOLDERS,
        ]);
    }
}
