<?php

namespace App\Http\Controllers\Api\Mobile;

use App\Http\Controllers\Controller;
use App\Models\Contact;
use App\Models\Conversation;
use App\Models\Deal;
use App\Models\Tag;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

/**
 * Mobile-app contact endpoints.
 *
 * All routes are bearer-token authenticated and scoped to the user's
 * active workspace. Responses are shaped specifically for the mobile UI
 * (Contacts tab + contact detail screen + autocomplete).
 */
class ContactsController extends Controller
{
    /**
     * GET /api/v1/mobile/contacts/search
     * Lightweight search for the compose "To:" autocomplete chip.
     */
    public function search(Request $request): JsonResponse
    {
        $workspaceId = $request->user()->active_workspace_id;
        if (!$workspaceId) {
            return response()->json(['data' => []]);
        }

        $q = trim((string) $request->query('q', ''));
        $limit = (int) min(max((int) $request->query('limit', 10), 1), 50);

        $query = Contact::where('workspace_id', $workspaceId)
            ->orderByDesc('updated_at');

        if ($q !== '') {
            $like = '%' . $q . '%';
            $query->where(function ($w) use ($like) {
                $w->where('first_name', 'like', $like)
                  ->orWhere('last_name', 'like', $like)
                  ->orWhere('email', 'like', $like)
                  ->orWhere('phone', 'like', $like)
                  ->orWhere('company', 'like', $like);
            });
        }

        $rows = $query->limit($limit)->get(['id', 'first_name', 'last_name', 'email', 'phone', 'company', 'avatar_path']);

        return response()->json([
            'data' => $rows->map(fn ($c) => [
                'id'         => $c->id,
                'name'       => $c->full_name ?: $c->email,
                'email'      => $c->email,
                'phone'      => $c->phone,
                'company'    => $c->company,
                'avatar_url' => $c->avatar_path ? asset('storage/' . $c->avatar_path) : null,
                'initials'   => $this->initials($c->full_name ?: $c->email ?: '?'),
            ])->values()->all(),
        ]);
    }

    /**
     * GET /api/v1/mobile/contacts/frequent
     * Top N contacts by recent conversation activity (compose screen).
     */
    public function frequent(Request $request): JsonResponse
    {
        $workspaceId = $request->user()->active_workspace_id;
        $limit = (int) min(max((int) $request->query('limit', 8), 1), 20);

        $rows = Conversation::where('workspace_id', $workspaceId)
            ->whereNotNull('contact_id')
            ->select('contact_id', DB::raw('MAX(last_message_at) as last_at'), DB::raw('COUNT(*) as cnt'))
            ->groupBy('contact_id')
            ->orderByDesc('last_at')
            ->limit($limit)
            ->with(['contact:id,first_name,last_name,email,phone,avatar_path,company'])
            ->get();

        return response()->json([
            'data' => $rows->map(function ($r) {
                $c = $r->contact;
                if (!$c) return null;
                return [
                    'id'         => $c->id,
                    'name'       => $c->full_name ?: $c->email,
                    'email'      => $c->email,
                    'phone'      => $c->phone,
                    'company'    => $c->company,
                    'avatar_url' => $c->avatar_path ? asset('storage/' . $c->avatar_path) : null,
                    'initials'   => $this->initials($c->full_name ?: $c->email ?: '?'),
                    'thread_count' => (int) $r->cnt,
                ];
            })->filter()->values()->all(),
        ]);
    }

    /**
     * GET /api/v1/mobile/contacts
     * Paginated contact list for the Contacts tab.
     */
    public function index(Request $request): JsonResponse
    {
        $workspaceId = $request->user()->active_workspace_id;
        if (!$workspaceId) {
            return response()->json([
                'data' => [],
                'meta' => $this->emptyMeta(),
                'summary' => ['active_count' => 0, 'unsubscribed_count' => 0, 'total_count' => 0],
                'segments' => $this->buildSegments(0),
            ]);
        }

        // Pagination removed at client request — Flutter team wants the
        // whole address book in one call. Hard-cap protects against
        // pathological workspaces; if a workspace exceeds it the cap kicks
        // in and `meta.capped` flips to true so the app can show a
        // "showing first N" hint.
        $MAX_ROWS = 5000;
        $status  = (string) $request->query('status', 'active');
        $segment = (string) $request->query('segment', 'all');

        $base = Contact::where('workspace_id', $workspaceId)
            ->with(['tags', 'lists:id,name']);

        // Status filter (default: active only — matches the screenshot's "2,481 active" count)
        if ($status !== 'all') {
            $base->where('status', $status);
        }

        // Apply segment filter (mutually exclusive with tag_id / group_id)
        if ($segment !== 'all') {
            $this->applySegment($base, $segment, $workspaceId);
        } elseif ($tagId = $request->query('tag_id')) {
            $base->whereHas('tags', fn ($q) => $q->where('tags.id', $tagId));
        } elseif ($groupId = $request->query('group_id')) {
            $base->whereHas('lists', fn ($q) => $q->where('contact_lists.id', $groupId));
        }

        // Temperature filter (lead_score buckets)
        if ($temp = $request->query('temperature')) {
            match ($temp) {
                'hot'  => $base->where('lead_score', '>=', 80),
                'warm' => $base->whereBetween('lead_score', [50, 79]),
                'cold' => $base->where('lead_score', '<', 50),
                default => null,
            };
        }

        // Search (matches web ContactList: name/email/phone/company)
        if ($search = trim((string) $request->query('search', ''))) {
            $like = '%' . $search . '%';
            $base->where(function ($w) use ($like) {
                $w->where('first_name', 'like', $like)
                  ->orWhere('last_name', 'like', $like)
                  ->orWhere('email', 'like', $like)
                  ->orWhere('phone', 'like', $like)
                  ->orWhere('company', 'like', $like);
            });
        }

        // Sort
        $allowedSorts = ['first_name', 'last_name', 'email', 'company', 'lead_score', 'last_contacted_at', 'created_at'];
        $sortBy  = in_array($request->query('sort_by'), $allowedSorts, true) ? $request->query('sort_by') : 'first_name';
        $sortDir = $request->query('sort_dir') === 'desc' ? 'desc' : 'asc';
        $base->orderBy($sortBy, $sortDir);

        $rows = $base->limit($MAX_ROWS)->get();
        $groupAlpha = $request->boolean('group_alpha');

        // Workspace-wide counts for the summary line and chip counts
        // (computed from the unfiltered workspace, not the current filtered query)
        $statusCounts = Contact::where('workspace_id', $workspaceId)
            ->select('status', DB::raw('COUNT(*) as c'))
            ->groupBy('status')
            ->pluck('c', 'status')
            ->toArray();

        $totalCount = array_sum($statusCounts);
        $activeCount = (int) ($statusCounts['active'] ?? 0);
        $unsubscribedCount = (int) ($statusCounts['unsubscribed'] ?? 0);

        return response()->json([
            'data' => $rows->map(fn (Contact $c) => $this->shapeListRow($c, $groupAlpha))->all(),
            'summary' => [
                'active_count'       => $activeCount,
                'unsubscribed_count' => $unsubscribedCount,
                'total_count'        => $totalCount,
            ],
            'segments' => $this->buildSegments($workspaceId),
        ]);
    }

    /**
     * GET /api/v1/mobile/contacts/{id}
     * Full contact detail screen.
     */
    public function show(Request $request, int $id): JsonResponse
    {
        $workspaceId = $request->user()->active_workspace_id;
        $convLimit  = (int) min(max((int) $request->query('conversations_limit', 5), 1), 20);
        $dealLimit  = (int) min(max((int) $request->query('deals_limit', 5), 1), 20);

        $contact = Contact::where('workspace_id', $workspaceId)
            ->with(['tags', 'lists:id,name,description'])
            ->find($id);

        if (!$contact) {
            return response()->json(['message' => 'Contact not found.'], 404);
        }

        // Recent conversations
        $conversations = Conversation::where('workspace_id', $workspaceId)
            ->where('contact_id', $contact->id)
            ->orderByDesc('last_message_at')
            ->orderByDesc('id')
            ->limit($convLimit)
            ->get(['id', 'subject', 'channel', 'status', 'messages_count', 'last_message_at']);

        $convCounts = Conversation::where('workspace_id', $workspaceId)
            ->where('contact_id', $contact->id)
            ->select('status', DB::raw('COUNT(*) as c'))
            ->groupBy('status')
            ->pluck('c', 'status')
            ->toArray();

        // Active deals + totals
        $deals = Deal::where('workspace_id', $workspaceId)
            ->where('contact_id', $contact->id)
            ->where('status', 'open')
            ->with('dealStage:id,name,win_probability')
            ->orderBy('expected_close_date')
            ->limit($dealLimit)
            ->get();

        $dealAggregates = Deal::where('workspace_id', $workspaceId)
            ->where('contact_id', $contact->id)
            ->selectRaw('COUNT(*) as total, SUM(CASE WHEN status = ? THEN value ELSE 0 END) as open_value, SUM(value) as total_value', ['open'])
            ->first();

        // Customer-since: year of first won deal, fallback to contact creation year
        $firstWon = Deal::where('workspace_id', $workspaceId)
            ->where('contact_id', $contact->id)
            ->where('status', 'won')
            ->orderBy('won_at')
            ->value('won_at');
        $customerSince = $firstWon ? $firstWon->format('Y') : null;

        // Total emails sent (outbound messages on this contact's conversations)
        $totalEmailsSent = (int) DB::table('messages')
            ->join('conversations', 'messages.conversation_id', '=', 'conversations.id')
            ->where('conversations.workspace_id', $workspaceId)
            ->where('conversations.contact_id', $contact->id)
            ->where('messages.direction', 'outbound')
            ->count();

        $temperature = $this->temperatureFor((int) $contact->lead_score);
        $primaryCurrency = strtoupper((string) ($deals->first()->currency ?? 'USD'));

        return response()->json([
            'contact' => [
                'id'                  => $contact->id,
                'uuid'                => $contact->uuid,
                'name'                => $contact->full_name ?: ($contact->email ?? 'Unknown'),
                'first_name'          => $contact->first_name,
                'last_name'           => $contact->last_name,
                'initials'            => $this->initials($contact->full_name ?: $contact->email ?: '?'),
                'subtitle'            => $this->subtitleFor($contact),
                'email'               => $contact->email,
                'phone'               => $contact->phone,
                'company'             => $contact->company,
                'job_title'           => $contact->job_title,
                'country'             => $contact->country,
                'country_code'        => $this->countryCode($contact->country),
                'country_flag'        => $this->countryFlag($this->countryCode($contact->country)),
                'city'                => $contact->city,
                'timezone'            => $contact->timezone,
                'avatar_url'          => $contact->avatar_path ? asset('storage/' . $contact->avatar_path) : null,
                'avatar_color'        => $this->avatarColor($contact->id),
                'lead_score'          => (int) $contact->lead_score,
                'temperature'         => $temperature,
                'status'              => $contact->status,
                'is_online'           => $this->isOnline($contact->last_seen_at),
                'last_seen_at'        => $contact->last_seen_at?->toIso8601String(),
                'last_seen_human'     => $contact->last_seen_at ? $contact->last_seen_at->diffForHumans() : null,
                'last_contacted_at'   => $contact->last_contacted_at?->toIso8601String(),
                'customer_since'      => $customerSince,
                'created_at'          => $contact->created_at?->toIso8601String(),
            ],
            'badges' => $this->buildBadges($contact, $temperature, $customerSince),
            'quick_actions' => [
                'email' => ['enabled' => !empty($contact->email), 'value' => $contact->email],
                'chat'  => ['enabled' => !empty($contact->phone), 'channel' => $this->preferredChatChannel($workspaceId, $contact->id), 'value' => $contact->phone],
                'call'  => ['enabled' => !empty($contact->phone), 'value' => $contact->phone],
                'note'  => ['enabled' => true],
            ],
            'tags' => $contact->tags->map(fn (Tag $t) => [
                'id' => $t->id, 'name' => $t->name, 'color' => $t->color,
            ])->values()->all(),
            'groups' => $contact->lists->map(fn ($g) => [
                'id' => $g->id, 'name' => $g->name, 'description' => $g->description,
            ])->values()->all(),
            'conversations' => [
                'open_count'   => (int) ($convCounts['open'] ?? 0),
                'closed_count' => (int) ($convCounts['closed'] ?? 0),
                'data' => $conversations->map(fn (Conversation $c) => [
                    'id'                 => $c->id,
                    'subject'            => $c->subject ?: '(no subject)',
                    'channel'            => $c->channel,
                    'status'             => $c->status,
                    'last_message_at'    => $c->last_message_at?->toIso8601String(),
                    'last_message_human' => $c->last_message_at ? $c->last_message_at->diffForHumans() : null,
                    'messages_count'     => (int) $c->messages_count,
                ])->values()->all(),
            ],
            'deals' => [
                'active_count' => (int) ($dealAggregates->total ?? 0),
                'total_value'  => (float) ($dealAggregates->open_value ?? 0),
                'currency'     => $primaryCurrency,
                'data' => $deals->map(function (Deal $d) {
                    $stage = $d->dealStage;
                    $prob = (int) ($stage->win_probability ?? 0);
                    return [
                        'id'                   => $d->id,
                        'title'                => $d->title,
                        'value'                => (float) $d->value,
                        'currency'             => strtoupper($d->currency ?: 'USD'),
                        'stage' => $stage ? [
                            'id'              => $stage->id,
                            'name'            => $stage->name,
                            'win_probability' => $prob,
                        ] : null,
                        'progress_percent'     => $prob,
                        'expected_close_date'  => $d->expected_close_date?->format('Y-m-d'),
                        'expected_close_human' => $d->expected_close_date ? 'Closes ' . $d->expected_close_date->format('M j') : null,
                        'status'               => $d->status,
                    ];
                })->values()->all(),
            ],
            'stats' => [
                'total_conversations' => (int) array_sum($convCounts),
                'open_conversations'  => (int) ($convCounts['open'] ?? 0),
                'total_deals'         => (int) ($dealAggregates->total ?? 0),
                'total_deals_value'   => (float) ($dealAggregates->total_value ?? 0),
                'total_emails_sent'   => $totalEmailsSent,
                'first_contacted_at'  => $contact->created_at?->toIso8601String(),
            ],
        ]);
    }

    /**
     * POST /api/v1/mobile/contacts/{id}/action
     * Allowed actions: unsubscribe, resubscribe, delete, restore.
     */
    public function action(Request $request, int $id): JsonResponse
    {
        $workspaceId = $request->user()->active_workspace_id;

        $contact = Contact::where('workspace_id', $workspaceId)
            ->withTrashed()
            ->find($id);

        if (!$contact) {
            return response()->json(['message' => 'Contact not found.'], 404);
        }

        $action = (string) $request->input('action');

        switch ($action) {
            case 'unsubscribe':
                $contact->update([
                    'status' => 'unsubscribed',
                    'unsubscribed_at' => now(),
                    'unsubscribe_reason' => (string) $request->input('reason', 'manual'),
                ]);
                break;

            case 'resubscribe':
                $contact->update([
                    'status' => 'active',
                    'unsubscribed_at' => null,
                    'unsubscribe_reason' => null,
                ]);
                break;

            case 'delete':
                $contact->delete();
                return response()->json(['message' => 'Contact deleted.']);

            case 'restore':
                if ($contact->trashed()) {
                    $contact->restore();
                }
                break;

            default:
                return response()->json([
                    'message' => 'Invalid action.',
                    'errors' => ['action' => ['Allowed: unsubscribe, resubscribe, delete, restore.']],
                ], 422);
        }

        return $this->show($request, $contact->id);
    }

    /**
     * POST /api/v1/mobile/contacts
     */
    public function store(Request $request): JsonResponse
    {
        $workspaceId = $request->user()->active_workspace_id;

        $validator = Validator::make($request->all(), [
            'first_name'  => 'required|string|max:255',
            'last_name'   => 'nullable|string|max:255',
            'email'       => 'required|email|max:255',
            'phone'       => 'nullable|string|max:50',
            'company'     => 'nullable|string|max:255',
            'job_title'   => 'nullable|string|max:255',
            'city'        => 'nullable|string|max:255',
            'country'     => 'nullable|string|max:100',
            'timezone'    => 'nullable|string|max:100',
            'lead_score'  => 'nullable|integer|min:0|max:100',
            'tag_ids'     => 'nullable|array',
            'tag_ids.*'   => ['integer', Rule::exists('tags', 'id')->where('workspace_id', $workspaceId)],
            'group_ids'   => 'nullable|array',
            'group_ids.*' => ['integer', Rule::exists('contact_lists', 'id')->where('workspace_id', $workspaceId)],
        ]);

        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }

        $exists = Contact::where('workspace_id', $workspaceId)
            ->where('email', $request->input('email'))
            ->exists();

        if ($exists) {
            return response()->json([
                'message' => 'A contact with this email already exists in this workspace.',
                'errors' => ['email' => ['A contact with this email already exists in this workspace.']],
            ], 422);
        }

        $payload = $validator->validated();
        unset($payload['tag_ids'], $payload['group_ids']);

        $contact = Contact::create([
            'workspace_id' => $workspaceId,
            ...$payload,
        ]);

        if ($request->has('tag_ids')) {
            $contact->tags()->sync($request->input('tag_ids'));
        }
        if ($request->has('group_ids')) {
            $contact->lists()->sync($this->groupSyncPayload($request->input('group_ids')));
            $this->refreshGroupCounts($request->input('group_ids'));
        }

        return $this->show($request, $contact->id)->setStatusCode(201);
    }

    /**
     * PUT /api/v1/mobile/contacts/{id}
     */
    public function update(Request $request, int $id): JsonResponse
    {
        $workspaceId = $request->user()->active_workspace_id;

        $contact = Contact::where('workspace_id', $workspaceId)->find($id);
        if (!$contact) {
            return response()->json(['message' => 'Contact not found.'], 404);
        }

        $validator = Validator::make($request->all(), [
            'first_name'  => 'sometimes|required|string|max:255',
            'last_name'   => 'nullable|string|max:255',
            'email'       => 'sometimes|required|email|max:255',
            'phone'       => 'nullable|string|max:50',
            'company'     => 'nullable|string|max:255',
            'job_title'   => 'nullable|string|max:255',
            'city'        => 'nullable|string|max:255',
            'country'     => 'nullable|string|max:100',
            'timezone'    => 'nullable|string|max:100',
            'lead_score'  => 'nullable|integer|min:0|max:100',
            'status'      => 'nullable|string|in:active,unsubscribed,bounced,spam',
            'tag_ids'     => 'nullable|array',
            'tag_ids.*'   => ['integer', Rule::exists('tags', 'id')->where('workspace_id', $workspaceId)],
            'group_ids'   => 'nullable|array',
            'group_ids.*' => ['integer', Rule::exists('contact_lists', 'id')->where('workspace_id', $workspaceId)],
        ]);

        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }

        // Email uniqueness (only if email was changed)
        if ($request->has('email') && $request->input('email') !== $contact->email) {
            $taken = Contact::where('workspace_id', $workspaceId)
                ->where('email', $request->input('email'))
                ->where('id', '!=', $contact->id)
                ->exists();
            if ($taken) {
                return response()->json([
                    'message' => 'A contact with this email already exists in this workspace.',
                    'errors' => ['email' => ['A contact with this email already exists in this workspace.']],
                ], 422);
            }
        }

        $payload = $validator->validated();
        unset($payload['tag_ids'], $payload['group_ids']);
        $contact->update($payload);

        if ($request->has('tag_ids')) {
            $contact->tags()->sync($request->input('tag_ids'));
        }
        if ($request->has('group_ids')) {
            $previous = $contact->lists()->pluck('contact_lists.id')->all();
            $contact->lists()->sync($this->groupSyncPayload($request->input('group_ids')));
            $this->refreshGroupCounts(array_unique(array_merge($previous, $request->input('group_ids'))));
        }

        return $this->show($request, $contact->id);
    }

    /**
     * DELETE /api/v1/mobile/contacts/{id}
     */
    public function destroy(Request $request, int $id): JsonResponse
    {
        $workspaceId = $request->user()->active_workspace_id;

        $contact = Contact::where('workspace_id', $workspaceId)->find($id);
        if (!$contact) {
            return response()->json(['message' => 'Contact not found.'], 404);
        }

        $contact->delete();
        return response()->json(['message' => 'Contact deleted.']);
    }

    // ── Helpers ──────────────────────────────────────────────────────

    private function shapeListRow(Contact $c, bool $includeAlpha): array
    {
        $name = $c->full_name ?: ($c->email ?? 'Unknown');

        return [
            'id'                 => $c->id,
            'uuid'               => $c->uuid,
            'name'               => $name,
            'first_name'         => $c->first_name,
            'initials'           => $this->initials($name),
            'email'              => $c->email,
            'phone'              => $c->phone,
            'company'            => $c->company,
            'job_title'          => $c->job_title,
            'avatar_url'         => $c->avatar_path ? asset('storage/' . $c->avatar_path) : null,
            'avatar_color'       => $this->avatarColor($c->id),
            'lead_score'         => (int) $c->lead_score,
            'temperature'        => $this->temperatureFor((int) $c->lead_score),
            'status'             => $c->status,
            'is_online'          => $this->isOnline($c->last_seen_at),
            'last_seen_at'       => $c->last_seen_at?->toIso8601String(),
            'last_contacted_at'  => $c->last_contacted_at?->toIso8601String(),
            'created_at'         => $c->created_at?->toIso8601String(),
            'alpha'              => $includeAlpha ? strtoupper(mb_substr($name, 0, 1)) : null,
            'tags' => $c->tags->map(fn (Tag $t) => [
                'id' => $t->id, 'name' => $t->name, 'color' => $t->color,
            ])->values()->all(),
            'groups' => $c->relationLoaded('lists')
                ? $c->lists->map(fn ($g) => ['id' => $g->id, 'name' => $g->name])->values()->all()
                : [],
        ];
    }

    private function buildSegments(int $workspaceId): array
    {
        if (!$workspaceId) {
            return [
                ['key' => 'all',        'label' => 'All',        'count' => 0],
                ['key' => 'vip',        'label' => 'VIP',        'count' => 0],
                ['key' => 'hot_leads',  'label' => 'Hot leads',  'count' => 0],
                ['key' => 'customers',  'label' => 'Customers',  'count' => 0],
                ['key' => 'newsletter', "label" => 'Newsletter', 'count' => 0],
            ];
        }

        $allCount = (int) Contact::where('workspace_id', $workspaceId)->where('status', 'active')->count();
        $vipCount = (int) Contact::where('workspace_id', $workspaceId)
            ->where('status', 'active')
            ->whereHas('tags', fn ($q) => $q->where('workspace_id', $workspaceId)->whereRaw('LOWER(name) = ?', ['vip']))
            ->count();
        $hotCount = (int) Contact::where('workspace_id', $workspaceId)
            ->where('status', 'active')
            ->where('lead_score', '>=', 80)
            ->count();
        $customerCount = (int) Contact::where('workspace_id', $workspaceId)
            ->where('status', 'active')
            ->whereHas('deals', fn ($q) => $q->where('status', 'won'))
            ->count();
        $newsletterCount = (int) Contact::where('workspace_id', $workspaceId)
            ->where('status', 'active')
            ->whereHas('tags', fn ($q) => $q->where('workspace_id', $workspaceId)->whereRaw('LOWER(name) = ?', ['newsletter']))
            ->count();

        return [
            ['key' => 'all',        'label' => 'All',        'count' => $allCount],
            ['key' => 'vip',        'label' => 'VIP',        'count' => $vipCount],
            ['key' => 'hot_leads',  'label' => 'Hot leads',  'count' => $hotCount],
            ['key' => 'customers',  'label' => 'Customers',  'count' => $customerCount],
            ['key' => 'newsletter', 'label' => 'Newsletter', 'count' => $newsletterCount],
        ];
    }

    private function applySegment($query, string $segment, int $workspaceId): void
    {
        match ($segment) {
            'vip' => $query->whereHas('tags', fn ($q) => $q->where('workspace_id', $workspaceId)->whereRaw('LOWER(name) = ?', ['vip'])),
            'hot_leads' => $query->where('lead_score', '>=', 80),
            'customers' => $query->whereHas('deals', fn ($q) => $q->where('status', 'won')),
            'newsletter' => $query->whereHas('tags', fn ($q) => $q->where('workspace_id', $workspaceId)->whereRaw('LOWER(name) = ?', ['newsletter'])),
            default => null,
        };
    }

    private function buildBadges(Contact $contact, string $temperature, ?string $customerSince): array
    {
        $badges = [];

        if ($temperature === 'hot') {
            $badges[] = ['type' => 'hot_lead', 'label' => 'Hot lead', 'color' => 'danger', 'icon' => 'flame'];
        } elseif ($temperature === 'warm') {
            $badges[] = ['type' => 'warm_lead', 'label' => 'Warm lead', 'color' => 'warning', 'icon' => null];
        }

        if ($contact->lead_score > 0) {
            $badges[] = ['type' => 'lead_score', 'label' => 'Lead score ' . (int) $contact->lead_score, 'color' => 'neutral', 'icon' => null];
        }

        if ($customerSince) {
            $badges[] = ['type' => 'customer_since', 'label' => 'Customer since ' . $customerSince, 'color' => 'neutral', 'icon' => null];
        }

        return $badges;
    }

    private function preferredChatChannel(int $workspaceId, int $contactId): string
    {
        $whatsapp = Conversation::where('workspace_id', $workspaceId)
            ->where('contact_id', $contactId)
            ->where('channel', 'whatsapp')
            ->exists();
        return $whatsapp ? 'whatsapp' : 'sms';
    }

    private function temperatureFor(int $score): string
    {
        return match (true) {
            $score >= 80 => 'hot',
            $score >= 50 => 'warm',
            default      => 'cold',
        };
    }

    private function isOnline(?\Carbon\Carbon $lastSeenAt): bool
    {
        return $lastSeenAt instanceof \Carbon\Carbon && $lastSeenAt->gt(now()->subMinutes(5));
    }

    private function subtitleFor(Contact $c): ?string
    {
        $parts = array_filter([$c->job_title, $c->company]);
        return $parts ? implode(' · ', $parts) : null;
    }

    /**
     * Deterministic per-contact avatar color for the initials circle.
     * Hashes the contact id to one of a fixed palette so the color is
     * stable across requests.
     */
    private function avatarColor(int $id): string
    {
        $palette = ['#3b82f6', '#22c55e', '#ef4444', '#f59e0b', '#8b5cf6', '#ec4899', '#14b8a6', '#f97316'];
        return $palette[$id % count($palette)];
    }

    /**
     * Best-effort country code resolution. Accepts either an ISO-2 code
     * directly ("US") or a full country name ("United States") and returns
     * the ISO-2 code, or null if unknown.
     */
    private function countryCode(?string $country): ?string
    {
        if (!$country) return null;
        $country = trim($country);
        if (strlen($country) === 2) {
            return strtoupper($country);
        }

        // Common-name → ISO-2 fallback table for the most likely values.
        // Anything not in here returns null and the flag emoji is omitted.
        static $map = [
            'united states' => 'US', 'usa' => 'US', 'america' => 'US',
            'united kingdom' => 'GB', 'uk' => 'GB', 'great britain' => 'GB',
            'france' => 'FR', 'germany' => 'DE', 'spain' => 'ES', 'italy' => 'IT',
            'netherlands' => 'NL', 'belgium' => 'BE', 'portugal' => 'PT',
            'canada' => 'CA', 'mexico' => 'MX', 'brazil' => 'BR', 'argentina' => 'AR',
            'india' => 'IN', 'china' => 'CN', 'japan' => 'JP', 'south korea' => 'KR',
            'australia' => 'AU', 'new zealand' => 'NZ', 'singapore' => 'SG',
            'indonesia' => 'ID', 'philippines' => 'PH', 'vietnam' => 'VN',
            'turkey' => 'TR', 'russia' => 'RU', 'ukraine' => 'UA',
            'south africa' => 'ZA', 'egypt' => 'EG', 'nigeria' => 'NG',
            'uae' => 'AE', 'united arab emirates' => 'AE', 'saudi arabia' => 'SA',
            'switzerland' => 'CH', 'sweden' => 'SE', 'norway' => 'NO',
            'denmark' => 'DK', 'finland' => 'FI', 'ireland' => 'IE',
            'poland' => 'PL', 'czech republic' => 'CZ', 'austria' => 'AT',
        ];

        return $map[strtolower($country)] ?? null;
    }

    /**
     * Convert an ISO-2 country code to its emoji flag (regional indicator
     * symbols). Returns null if no code is provided.
     */
    private function countryFlag(?string $iso2): ?string
    {
        if (!$iso2 || strlen($iso2) !== 2) return null;
        $iso2 = strtoupper($iso2);
        $flag = '';
        for ($i = 0; $i < 2; $i++) {
            $flag .= mb_chr(0x1F1E6 + (ord($iso2[$i]) - ord('A')));
        }
        return $flag;
    }

    private function initials(?string $name): string
    {
        if (!$name) return '?';
        $parts = preg_split('/\s+/', trim($name));
        return strtoupper(substr($parts[0] ?? '', 0, 1) . substr($parts[1] ?? '', 0, 1)) ?: '?';
    }

    private function emptyMeta(): array
    {
        return ['current_page' => 1, 'per_page' => 25, 'total' => 0, 'has_more' => false];
    }

    /**
     * Build the sync() payload for the Contact↔ContactList pivot.
     * The pivot's `added_at` column is set on insert.
     */
    private function groupSyncPayload(array $groupIds): array
    {
        $now = now();
        $payload = [];
        foreach ($groupIds as $gid) {
            $payload[(int) $gid] = ['added_at' => $now];
        }
        return $payload;
    }

    /**
     * Recalculate the cached `contacts_count` on each affected ContactList
     * after add/remove. Only touches the lists we actually changed.
     */
    private function refreshGroupCounts(array $groupIds): void
    {
        if (empty($groupIds)) return;
        \App\Models\ContactList::whereIn('id', $groupIds)->get()
            ->each->refreshContactsCount();
    }
}
