<?php

namespace App\Livewire\Settings;

use App\Models\CustomField;
use App\Traits\AuthorizesWorkspaceActions;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Computed;
use Livewire\Component;

/**
 * Settings → Contacts → "Custom Fields" panel.
 *
 * Provides the CRUD UI the documentation promised but the platform
 * never shipped — admins can now create, edit, delete, and reorder
 * custom fields for contacts without touching the database directly.
 *
 * The component manages the `custom_fields` table only. The actual
 * field VALUES live on `contacts.custom_fields` as a JSON column and
 * are written by the Livewire ContactForm. Editing a field's `key`
 * here will leave existing values orphaned under the old key — the UI
 * makes the key read-only after creation so admins can't unwittingly
 * break their data.
 */
class CustomFieldsManager extends Component
{
    use AuthorizesWorkspaceActions;

    /** New-field form state. Mirrors the add modal. */
    public string $name = '';
    public string $type = 'text';
    public string $placeholder = '';
    public string $default_value = '';
    public string $group_name = '';
    public bool $required = false;
    /** Comma- or newline-separated choice list for dropdown / multi_select. */
    public string $optionsText = '';

    /** Edit-row state. null = no row being edited. */
    public ?int $editingId = null;
    public string $editName = '';
    public string $editType = 'text';
    public string $editPlaceholder = '';
    public string $editDefaultValue = '';
    public string $editGroupName = '';
    public bool $editRequired = false;
    public string $editOptionsText = '';

    public function mount(): void
    {
        // Even though the workspace scope is enforced by BelongsToWorkspace,
        // belt-and-braces: refuse the whole component for users without
        // workspace-level manage permission so the add/edit/delete buttons
        // don't render for read-only collaborators.
        if (!$this->authorizeWorkspaceAction('view')) {
            return;
        }

        // Upgrade-install repair: existing CustomField rows from the
        // pre-manager era all have sort_order=0. moveUp / moveDown look
        // for an immediate neighbor via `sort_order < x` / `> x` — which
        // matches nothing when every value is identical, so reordering
        // silently no-ops on a fresh visit. Sequence the values here so
        // the first reorder click works as expected. Runs only when we
        // detect the all-zeros condition AND there's more than one row
        // (the only state where this matters).
        $this->normalizeSortOrdersIfNeeded();
    }

    /**
     * One-shot fixup: when every CustomField in the workspace has the
     * same sort_order (typical state on installs where the manager UI
     * never existed), assign sequential 1..N values grouped first by
     * group_name then by id. Idempotent — re-running on already-sorted
     * data is a no-op because the `same value across all rows` check
     * is the only trigger.
     */
    private function normalizeSortOrdersIfNeeded(): void
    {
        $fields = CustomField::query()
            ->orderBy('group_name')
            ->orderBy('id')
            ->get(['id', 'sort_order', 'group_name']);

        if ($fields->count() < 2) {
            return;
        }
        if ($fields->pluck('sort_order')->unique()->count() > 1) {
            // Already has distinct values — nothing to do.
            return;
        }

        $order = 1;
        foreach ($fields as $f) {
            CustomField::where('id', $f->id)->update(['sort_order' => $order++]);
        }
    }

    /** Pulled fresh each render so reorder / add / delete stays in sync. */
    #[Computed]
    public function fields()
    {
        return CustomField::query()
            ->orderBy('group_name')
            ->orderBy('sort_order')
            ->orderBy('id')
            ->get();
    }

    /** Available types for the picker. Driven by the model so they stay in sync. */
    #[Computed]
    public function typeOptions(): array
    {
        return [
            'text'         => 'Text',
            'long_text'    => 'Long text (textarea)',
            'number'       => 'Number',
            'date'         => 'Date',
            'email'        => 'Email',
            'url'          => 'URL',
            'phone'        => 'Phone',
            'dropdown'     => 'Dropdown (single choice)',
            'multi_select' => 'Multi-select (multiple choices)',
            'checkbox'     => 'Checkbox',
        ];
    }

    // ── ADD ─────────────────────────────────────────────────────

    public function add(): void
    {
        if (!$this->authorizeWorkspaceAction('manage')) return;

        $this->validate([
            'name'          => ['required', 'string', 'max:100'],
            'type'          => ['required', Rule::in(CustomField::TYPES)],
            'placeholder'   => ['nullable', 'string', 'max:255'],
            'default_value' => ['nullable', 'string', 'max:1000'],
            'group_name'    => ['nullable', 'string', 'max:80'],
            'optionsText'   => ['nullable', 'string', 'max:5000'],
            'required'      => ['boolean'],
        ]);

        $options = null;
        if (in_array($this->type, CustomField::TYPES_WITH_OPTIONS, true)) {
            $options = $this->parseOptions($this->optionsText);
            if (empty($options)) {
                $this->addError('optionsText', __('Dropdown and multi-select fields need at least one option.'));
                return;
            }
        }

        // Append to the end of its group / global list by default — admins
        // can drag the row up using the arrow buttons in the row UI.
        $sortOrder = (CustomField::query()->max('sort_order') ?? 0) + 1;

        CustomField::create([
            'name'          => trim($this->name),
            'type'          => $this->type,
            'options'       => $options,
            'placeholder'   => trim($this->placeholder) ?: null,
            'default_value' => trim($this->default_value) !== '' ? $this->default_value : null,
            'group_name'    => trim($this->group_name) ?: null,
            'required'      => $this->required,
            'sort_order'    => $sortOrder,
            // workspace_id auto-filled by BelongsToWorkspace trait
        ]);

        $this->resetAddForm();
        unset($this->fields);
        session()->flash('success', __('Custom field added.'));
    }

    private function resetAddForm(): void
    {
        $this->name = '';
        $this->type = 'text';
        $this->placeholder = '';
        $this->default_value = '';
        $this->group_name = '';
        $this->required = false;
        $this->optionsText = '';
    }

    // ── EDIT ────────────────────────────────────────────────────

    public function startEdit(int $id): void
    {
        if (!$this->authorizeWorkspaceAction('manage')) return;

        $field = CustomField::find($id);
        if (!$field) return;

        $this->editingId        = $field->id;
        $this->editName         = $field->name;
        $this->editType         = $field->type;
        $this->editPlaceholder  = (string) $field->placeholder;
        $this->editDefaultValue = (string) $field->default_value;
        $this->editGroupName    = (string) $field->group_name;
        $this->editRequired     = (bool) $field->required;
        $this->editOptionsText  = is_array($field->options)
            ? implode("\n", $field->options)
            : '';
    }

    public function cancelEdit(): void
    {
        $this->editingId = null;
    }

    public function saveEdit(): void
    {
        if (!$this->authorizeWorkspaceAction('manage')) return;
        if (!$this->editingId) return;

        $this->validate([
            'editName'         => ['required', 'string', 'max:100'],
            'editType'         => ['required', Rule::in(CustomField::TYPES)],
            'editPlaceholder'  => ['nullable', 'string', 'max:255'],
            'editDefaultValue' => ['nullable', 'string', 'max:1000'],
            'editGroupName'    => ['nullable', 'string', 'max:80'],
            'editOptionsText'  => ['nullable', 'string', 'max:5000'],
            'editRequired'     => ['boolean'],
        ]);

        $field = CustomField::find($this->editingId);
        if (!$field) {
            $this->editingId = null;
            return;
        }

        $options = null;
        if (in_array($this->editType, CustomField::TYPES_WITH_OPTIONS, true)) {
            $options = $this->parseOptions($this->editOptionsText);
            if (empty($options)) {
                $this->addError('editOptionsText', __('Dropdown and multi-select fields need at least one option.'));
                return;
            }
        }

        // Type-change loss warning is deliberately implicit: a dropdown
        // → text conversion silently keeps the prior options column
        // because options:null would erase the choice list and a future
        // revert would lose it. The UI hints at this in the type label.
        $field->update([
            'name'          => trim($this->editName),
            'type'          => $this->editType,
            'options'       => $options ?? $field->options,
            'placeholder'   => trim($this->editPlaceholder) ?: null,
            'default_value' => trim($this->editDefaultValue) !== '' ? $this->editDefaultValue : null,
            'group_name'    => trim($this->editGroupName) ?: null,
            'required'      => $this->editRequired,
        ]);

        $this->editingId = null;
        unset($this->fields);
        session()->flash('success', __('Custom field updated.'));
    }

    public function delete(int $id): void
    {
        if (!$this->authorizeWorkspaceAction('manage')) return;

        // Soft-delete via the SoftDeletes trait on the model.
        // Existing values on contacts.custom_fields stay intact, so a
        // restore (if we ever ship one) is fully reversible.
        CustomField::where('id', $id)->delete();
        unset($this->fields);
        session()->flash('success', __('Custom field removed.'));
    }

    // ── REORDER ─────────────────────────────────────────────────

    public function moveUp(int $id): void
    {
        if (!$this->authorizeWorkspaceAction('manage')) return;

        $field = CustomField::find($id);
        if (!$field) return;

        // Find the field immediately above this one (scoped to the same
        // group when one is set, so reordering inside a group doesn't
        // accidentally swap with an unrelated standalone field).
        $above = CustomField::query()
            ->where('group_name', $field->group_name)
            ->where('sort_order', '<', $field->sort_order)
            ->orderBy('sort_order', 'desc')
            ->first();

        if ($above) {
            $thisOrder  = $field->sort_order;
            $aboveOrder = $above->sort_order;
            $field->update(['sort_order' => $aboveOrder]);
            $above->update(['sort_order' => $thisOrder]);
            unset($this->fields);
        }
    }

    public function moveDown(int $id): void
    {
        if (!$this->authorizeWorkspaceAction('manage')) return;

        $field = CustomField::find($id);
        if (!$field) return;

        $below = CustomField::query()
            ->where('group_name', $field->group_name)
            ->where('sort_order', '>', $field->sort_order)
            ->orderBy('sort_order', 'asc')
            ->first();

        if ($below) {
            $thisOrder  = $field->sort_order;
            $belowOrder = $below->sort_order;
            $field->update(['sort_order' => $belowOrder]);
            $below->update(['sort_order' => $thisOrder]);
            unset($this->fields);
        }
    }

    // ── HELPERS ─────────────────────────────────────────────────

    /**
     * Parse the multi-line / comma-separated options textarea into a
     * clean array. Empty lines are dropped; duplicates are deduped
     * (case-preserving on first occurrence).
     */
    private function parseOptions(string $raw): array
    {
        $raw = str_replace(["\r\n", "\r"], "\n", $raw);
        $parts = preg_split('/[\n,]+/', $raw) ?: [];
        $clean = [];
        foreach ($parts as $p) {
            $p = trim($p);
            if ($p === '') continue;
            if (in_array($p, $clean, true)) continue;
            $clean[] = $p;
        }
        return $clean;
    }

    /** Preview of the auto-generated key so the admin sees it before saving. */
    #[Computed]
    public function previewKey(): string
    {
        if (trim($this->name) === '') return '';
        return Str::snake(trim($this->name));
    }

    public function render()
    {
        return view('livewire.settings.custom-fields-manager');
    }
}
