<?php

namespace App\Livewire\Contacts;

use App\Exceptions\PlanLimitReachedException;
use App\Models\Contact;
use App\Models\CustomField;
use App\Models\Tag;
use App\Models\Workspace;
use App\Services\PlanLimitService;
use App\Traits\AuthorizesWorkspaceActions;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Computed;
use Livewire\Component;

class ContactForm extends Component
{
    use AuthorizesWorkspaceActions;

    public ?int $contactId = null;

    public string $first_name = '';
    public string $last_name = '';
    public string $email = '';
    public string $phone = '';
    public string $company = '';
    public string $job_title = '';
    public string $city = '';
    public string $country = '';
    public string $timezone = '';
    public array $selectedTags = [];
    public array $customFieldValues = [];

    /**
     * Inline-create-tag input. The Tags section in the form ships with
     * a "New tag" text field — typing a name and pressing Enter (or
     * clicking Add) creates the tag in the workspace immediately and
     * auto-selects it for this contact. Lets staff capture a tag they
     * just thought of without leaving the contact dialog to go to the
     * Tags settings page first.
     */
    public string $newTagName = '';

    public function mount(?int $contactId = null): void
    {
        // Authorization: create for new, interact for edit
        if ($contactId) {
            if (!$this->authorizeWorkspaceAction('interact')) return;
        } else {
            if (!$this->authorizeWorkspaceAction('create')) return;
        }

        $this->contactId = $contactId;

        // Initialize custom field values. For a new contact we seed
        // each field with its configured default_value so the admin
        // doesn't have to re-type the same baseline every time. For
        // multi_select fields the initial value is an empty array so
        // wire:model.live on a <select multiple> binds cleanly.
        $customFields = CustomField::where('workspace_id', auth()->user()->active_workspace_id)
            ->orderBy('sort_order')
            ->get();

        foreach ($customFields as $field) {
            if ($field->type === 'multi_select') {
                $this->customFieldValues[$field->key] = [];
            } else {
                $this->customFieldValues[$field->key] = $contactId
                    ? ''                              // overwritten below from $savedFields
                    : (string) ($field->default_value ?? '');
            }
        }

        if ($contactId) {
            $contact = Contact::where('workspace_id', auth()->user()->active_workspace_id)
                ->with('tags')
                ->findOrFail($contactId);

            $this->first_name = $contact->first_name ?? '';
            $this->last_name = $contact->last_name ?? '';
            $this->email = $contact->email ?? '';
            $this->phone = $contact->phone ?? '';
            $this->company = $contact->company ?? '';
            $this->job_title = $contact->job_title ?? '';
            $this->city = $contact->city ?? '';
            $this->country = $contact->country ?? '';
            $this->timezone = $contact->timezone ?? '';
            $this->selectedTags = $contact->tags->pluck('id')->toArray();

            // Load custom field values. multi_select rows are stored
            // as JSON arrays on contacts.custom_fields; coerce stored
            // scalars or null into arrays so the form binds cleanly
            // even on rows saved before the multi_select type existed.
            $savedFields = $contact->custom_fields ?? [];
            foreach ($customFields as $field) {
                $saved = $savedFields[$field->key] ?? null;
                if ($field->type === 'multi_select') {
                    $this->customFieldValues[$field->key] = is_array($saved)
                        ? array_values($saved)
                        : ($saved !== null && $saved !== '' ? [$saved] : []);
                } else {
                    $this->customFieldValues[$field->key] = $saved ?? '';
                }
            }
        }
    }

    public function rules(): array
    {
        $wsId = auth()->user()->active_workspace_id;

        $rules = [
            'first_name' => 'required|string|max:100',
            'last_name' => 'nullable|string|max:100',
            'email' => 'required|email|max:255',
            'phone' => 'nullable|string|max:30',
            'company' => 'nullable|string|max:150',
            'job_title' => 'nullable|string|max:150',
            'city' => 'nullable|string|max:100',
            'country' => 'nullable|string|max:100',
            'timezone' => 'nullable|string|max:50',
            'selectedTags' => 'array',
            // Scope tag validation by workspace
            'selectedTags.*' => ['integer', Rule::exists('tags', 'id')->where('workspace_id', $wsId)],
        ];

        // Add custom field validation
        $customFields = CustomField::where('workspace_id', $wsId)->get();
        foreach ($customFields as $field) {
            $fieldRules = [];
            if ($field->required) {
                $fieldRules[] = 'required';
            } else {
                $fieldRules[] = 'nullable';
            }

            // multi_select stores an array; everything else stores a string.
            // long_text gets a higher max so paragraphs fit; the standard
            // 500-char ceiling on `text`/`url`/etc. catches typo dumps.
            if ($field->type === 'multi_select') {
                $rules["customFieldValues.{$field->key}"] = $field->required
                    ? 'array|min:1'
                    : 'array';
                $rules["customFieldValues.{$field->key}.*"] = 'string|max:255';
                continue;
            }

            $fieldRules[] = match ($field->type) {
                'number'    => 'numeric',
                'date'      => 'date',
                'email'     => 'email',
                'url'       => 'url',
                'checkbox'  => 'boolean',
                'long_text' => 'string|max:5000',
                default     => 'string|max:500',
            };

            $rules["customFieldValues.{$field->key}"] = implode('|', $fieldRules);
        }

        return $rules;
    }

    public function save(): void
    {
        // Authorization: create for new, interact for edit
        if ($this->contactId) {
            if (!$this->authorizeWorkspaceAction('interact')) return;
        } else {
            if (!$this->authorizeWorkspaceAction('create')) return;
        }

        $validated = $this->validate();
        $workspaceId = auth()->user()->active_workspace_id;

        // Enforce plan limit when creating a new contact
        if (! $this->contactId) {
            try {
                $workspace = Workspace::findOrFail($workspaceId);
                app(PlanLimitService::class)->assertCanCreate($workspace, 'contacts');
            } catch (PlanLimitReachedException $e) {
                session()->flash('error', $e->getMessage());
                return;
            }
        }

        // Duplicate email check (workspace-scoped)
        $emailExists = Contact::where('workspace_id', $workspaceId)
            ->where('email', $this->email)
            ->when($this->contactId, fn ($q) => $q->where('id', '!=', $this->contactId))
            ->exists();

        if ($emailExists) {
            $this->addError('email', 'A contact with this email already exists in this workspace.');
            return;
        }

        $data = [
            'first_name' => $this->first_name,
            'last_name' => $this->last_name,
            'email' => $this->email,
            'phone' => $this->phone,
            'company' => $this->company,
            'job_title' => $this->job_title,
            'city' => $this->city,
            'country' => $this->country,
            'timezone' => $this->timezone,
            'custom_fields' => array_filter($this->customFieldValues, fn($v) => $v !== '' && $v !== null),
        ];

        if ($this->contactId) {
            $contact = Contact::where('workspace_id', $workspaceId)
                ->findOrFail($this->contactId);
            $contact->update($data);
            session()->flash('success', 'Contact updated.');
        } else {
            $contact = Contact::create(array_merge($data, [
                'workspace_id' => $workspaceId,
                'status' => 'active',
            ]));
            session()->flash('success', 'Contact created.');
        }

        // Sync tags (already validated as workspace-scoped via rules)
        $validTags = Tag::where('workspace_id', $workspaceId)
            ->whereIn('id', $this->selectedTags)
            ->pluck('id')
            ->toArray();
        $existingTagIds = $contact->tags()->pluck('tags.id')->all();
        $contact->tags()->sync($validTags);

        // Fire TagAdded only for newly-attached tags
        $newlyAddedTagIds = array_diff($validTags, $existingTagIds);
        if (!empty($newlyAddedTagIds)) {
            foreach (Tag::whereIn('id', $newlyAddedTagIds)->get() as $addedTag) {
                try { event(new \App\Events\TagAdded($contact, $addedTag)); } catch (\Throwable $e) {}
            }
        }

        $this->dispatch('contact-saved');
    }

    public function close(): void
    {
        $this->dispatch('contact-saved');
    }

    /**
     * Create a workspace-scoped tag from the inline input and add it
     * to selectedTags so it's attached when the contact is saved.
     *
     * Idempotent: typing an existing name just selects it (instead of
     * creating a duplicate row). The Contact form's existing tag
     * checkbox list refreshes automatically because $this->tags is a
     * #[Computed] property and Livewire re-runs it on every render.
     *
     * Workspace permission check uses the same 'interact' gate the
     * rest of the form uses — keeps tag creation aligned with the
     * permission model for editing contacts.
     */
    public function addNewTag(): void
    {
        $name = trim($this->newTagName);
        if ($name === '') {
            return;
        }
        if (mb_strlen($name) > 50) {
            $this->addError('newTagName', 'Tag name is too long (max 50 chars).');
            return;
        }

        if (!$this->authorizeWorkspaceAction('interact')) return;

        $workspaceId = auth()->user()->active_workspace_id;

        // Case-insensitive match so "VIP" / "vip" / "Vip" don't all
        // become separate rows over time. We collate via lower(name).
        $existing = Tag::where('workspace_id', $workspaceId)
            ->whereRaw('LOWER(name) = ?', [mb_strtolower($name)])
            ->first();

        $tag = $existing ?: Tag::create([
            'workspace_id' => $workspaceId,
            'name'         => $name,
            // Default neutral blue — admins can edit the colour later
            // in Settings → Tags. Avoids dropping a colour picker into
            // this already-busy form.
            'color'        => '#3b82f6',
        ]);

        if (!in_array($tag->id, $this->selectedTags, true)) {
            $this->selectedTags[] = $tag->id;
        }

        $this->newTagName = '';
        $this->resetErrorBag('newTagName');
    }

    #[Computed]
    public function tags()
    {
        return Tag::where('workspace_id', auth()->user()->active_workspace_id)
            ->orderBy('name')
            ->get();
    }

    #[Computed]
    public function customFields()
    {
        return CustomField::where('workspace_id', auth()->user()->active_workspace_id)
            ->orderBy('group_order')
            ->orderBy('sort_order')
            ->get();
    }

    /**
     * Custom fields grouped by their `group_name` for the collapsible
     * "Field Groups" UX the docs promised. Fields without a group
     * appear first under a 'null' key so the existing flat layout
     * still works for workspaces that never used groups.
     *
     * Shape: [ groupName|null => Collection<CustomField>, … ]
     */
    #[Computed]
    public function groupedCustomFields(): array
    {
        $byGroup = [];
        foreach ($this->customFields as $field) {
            $g = $field->group_name ?: '';
            $byGroup[$g][] = $field;
        }
        // Keep ungrouped at the top, groups follow in alphabetical order
        // (matches what the manager UI shows when admins sort columns).
        $ordered = [];
        if (isset($byGroup[''])) {
            $ordered[''] = $byGroup[''];
            unset($byGroup['']);
        }
        ksort($byGroup);
        return $ordered + $byGroup;
    }

    public function render()
    {
        return view('livewire.contacts.contact-form');
    }
}
