<?php

namespace App\Models;

use App\Traits\BelongsToWorkspace;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;

/**
 * One per workspace-defined contact custom field.
 *
 * Field types (column `type`):
 *   text, number, date, email, url, phone, dropdown, checkbox,
 *   multi_select, long_text
 *
 * `key` is the JSON storage key on `contacts.custom_fields` and the
 * merge variable name in templates ({custom.your_key}). It must be
 * snake_case and unique within the workspace. The model auto-derives
 * it from `name` when the caller leaves it blank — admins typing in
 * "Subscription Tier" get key="subscription_tier" without a separate
 * input.
 *
 * `options` is the choice list for `dropdown` and `multi_select`.
 * `placeholder`, `default_value`, and `group_name` are surfaced on
 * the contact form (see resources/views/livewire/contacts/contact-form.blade.php).
 */
class CustomField extends Model
{
    use BelongsToWorkspace, SoftDeletes;

    /** All valid `type` values. Drives the picker in CustomFieldsManager. */
    public const TYPES = [
        'text', 'long_text', 'number', 'date',
        'email', 'url', 'phone',
        'dropdown', 'multi_select', 'checkbox',
    ];

    /** Types whose `options` column carries the choice list. */
    public const TYPES_WITH_OPTIONS = ['dropdown', 'multi_select'];

    protected $fillable = [
        'workspace_id',
        'name',
        'key',
        'type',
        'options',
        'placeholder',
        'default_value',
        'group_name',
        'group_order',
        'required',
        'sort_order',
    ];

    protected function casts(): array
    {
        return [
            'options' => 'array',
            'required' => 'boolean',
            'sort_order' => 'integer',
            'group_order' => 'integer',
        ];
    }

    /**
     * Auto-derive `key` from `name` when the admin didn't type one,
     * and collision-handle by appending _2, _3, … so two fields named
     * "Tier" in the same workspace don't both try to write to the
     * same JSON slot on contacts.custom_fields.
     *
     * We hook `creating` and `updating` (not `saving`) on purpose:
     * the BelongsToWorkspace trait sets `workspace_id` in its own
     * `creating` listener. Listeners fire in registration order, and
     * since the trait is `use`d at the top of this class its boot
     * runs before our `booted()`, so by the time our `creating`
     * handler fires the workspace_id is already populated. Using
     * `saving` would race that — workspace_id would be null and the
     * uniqueness check would scope across every workspace at once.
     */
    protected static function booted(): void
    {
        $populateKey = function (CustomField $field) {
            // Fall back to the auth'd workspace if the trait hasn't run
            // yet (e.g. when a model is constructed manually in tinker).
            $workspaceId = $field->workspace_id
                ?? (auth()->check() ? auth()->user()->active_workspace_id : null);

            if (empty($field->key) && !empty($field->name)) {
                $field->key = self::generateUniqueKey(
                    $field->name,
                    (int) $workspaceId,
                    $field->id,
                );
            } else {
                // Even when the caller supplies a key, snake_case it so
                // CSV imports / API callers don't accidentally store
                // "Tier Name" with spaces as a key the form layer can't
                // reach via dot-notation.
                $field->key = Str::snake(trim((string) $field->key));
            }
        };

        static::creating($populateKey);
        static::updating($populateKey);
    }

    /**
     * Build a snake_case key from a human label, suffixing _2/_3/…
     * until it's unique within the workspace. Excludes the current
     * row when updating so re-saving an unchanged field doesn't
     * cause its key to drift.
     */
    public static function generateUniqueKey(string $name, int $workspaceId, ?int $excludeId = null): string
    {
        $base = Str::snake(trim($name)) ?: 'field';
        $candidate = $base;
        $i = 2;

        while (
            self::query()
                ->where('workspace_id', $workspaceId)
                ->where('key', $candidate)
                ->when($excludeId, fn ($q) => $q->where('id', '!=', $excludeId))
                ->exists()
        ) {
            $candidate = $base . '_' . $i;
            $i++;
        }

        return $candidate;
    }

    public function workspace(): BelongsTo
    {
        return $this->belongsTo(Workspace::class);
    }

    /** Does this type carry a list of choices? */
    public function hasOptions(): bool
    {
        return in_array($this->type, self::TYPES_WITH_OPTIONS, true);
    }

    /** Does this type store an array of values on contacts.custom_fields? */
    public function isMultiValue(): bool
    {
        return $this->type === 'multi_select';
    }
}
