<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\StoreEmailPackageFromContractsRequest;
use App\Http\Requests\StorePackageFromContractsRequest;
use App\Http\Requests\StorePackageRequest;
use App\Jobs\PackageItemEmailJob;
use App\Jobs\PackageItemSmsJob;
use App\Models\Contract;
use App\Models\Package;
use App\Models\PackageItem;
use App\Models\SmsTemplate;
use App\Services\Contact\EmailSelector;
use App\Services\Contact\PhoneSelector;
use App\Services\Sms\SmsService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Str;
use Inertia\Inertia;
use Inertia\Response;

class PackageController extends Controller
{
    public function landing(): Response
    {
        return Inertia::render('Packages/Index');
    }

    public function smsIndex(Request $request): Response
    {
        $perPage = $request->input('per_page') ?? 25;

        $packages = Package::query()
            ->where('type', Package::TYPE_SMS)
            ->latest('id')
            ->paginate($perPage);

        return Inertia::render('Packages/Sms/Index', [
            'packages' => $packages,
        ]);
    }

    public function emailIndex(Request $request): Response
    {
        $perPage = $request->input('per_page') ?? 25;

        $packages = Package::query()
            ->where('type', Package::TYPE_EMAIL)
            ->latest('id')
            ->paginate($perPage);

        return Inertia::render('Packages/Mail/Index', [
            'packages' => $packages,
        ]);
    }

    public function smsCreate(Request $request): Response
    {
        // Minimal lookups for create form (active only)
        $profiles = \App\Models\SmsProfile::query()
            ->where('active', true)
            ->orderBy('name')
            ->get(['id', 'name']);
        $senders = \App\Models\SmsSender::query()
            ->where('active', true)
            ->orderBy('sname')
            ->get(['id', 'profile_id', 'sname', 'phone_number']);
        $templates = \App\Models\SmsTemplate::query()
            ->orderBy('name')
            ->get(['id', 'name', 'content']);
        $segments = \App\Models\Segment::query()
            ->where('active', true)
            ->where('exclude', false)
            ->orderBy('name')
            ->get(['id', 'name']);
        // Provide a lightweight list of recent clients with person names for filtering
        $clients = \App\Models\Client::query()
            ->with(['person' => function ($q) {
                $q->select('id', 'uuid', 'full_name');
            }])
            ->latest('id')
            ->get(['id', 'uuid', 'person_id'])
            ->map(function ($c) {
                return [
                    'id' => $c->id,
                    'uuid' => $c->uuid,
                    'name' => $c->person?->full_name ?? ('Client #'.$c->id),
                ];
            })
            ->values();

        return Inertia::render('Packages/Sms/Create', [
            'profiles' => $profiles,
            'senders' => $senders,
            'templates' => $templates,
            'segments' => $segments,
            'clients' => $clients,
        ]);
    }

    public function emailCreate(): Response
    {
        $emailTemplates = \App\Models\EmailTemplate::query()
            ->where('active', true)
            ->where('client', false)
            ->orderBy('name')
            ->get(['id', 'name', 'subject_template', 'text_template', 'html_template'])
            ->map(fn ($t) => [
                'id' => $t->id,
                'name' => $t->name,
                'subject_template' => $t->subject_template,
                'text_template' => $t->text_template,
                'has_body_text' => (bool) preg_match('/{{\s*body_text\s*}}/', (string) $t->html_template),
            ])->values();
        $mailProfiles = \App\Models\MailProfile::query()
            ->where('active', true)
            ->orderBy('priority')
            ->get(['id', 'name']);
        $segments = \App\Models\Segment::query()
            ->where('active', true)
            ->where('exclude', false)
            ->orderBy('name')
            ->get(['id', 'name']);
        $clients = \App\Models\Client::query()
            ->with(['person' => function ($q) {
                $q->select('id', 'uuid', 'full_name');
            }])
            ->latest('id')
            ->get(['id', 'uuid', 'person_id'])
            ->map(function ($c) {
                return [
                    'id' => $c->id,
                    'uuid' => $c->uuid,
                    'name' => $c->person?->full_name ?? ('Client #'.$c->id),
                ];
            })
            ->values();

        return Inertia::render('Packages/Mail/Create', [
            'emailTemplates' => $emailTemplates,
            'mailProfiles' => $mailProfiles,
            'segments' => $segments,
            'clients' => $clients,
        ]);
    }

    public function smsShow(Package $package, SmsService $sms): Response
    {
        $items = $package->items()->latest('id')->paginate(25);

        // Preload contracts/accounts for current page items to compute per-item previews
        $contractIds = collect($items->items())
            ->map(fn ($it) => (array) ($it->target_json ?? []))
            ->map(fn ($t) => $t['contract_id'] ?? null)
            ->filter()
            ->unique()
            ->values();
        $contracts = $contractIds->isNotEmpty()
            ? Contract::query()->with('account.type')->whereIn('id', $contractIds)->get()->keyBy('id')
            : collect();

        // Attach rendered_preview to each item
        $collection = collect($items->items());
        $collection = $collection->transform(function ($it) use ($sms, $contracts) {
            $payload = (array) ($it->payload_json ?? []);
            $tgt = (array) ($it->target_json ?? []);
            $vars = (array) ($payload['variables'] ?? []);
            if (! empty($tgt['contract_id']) && $contracts->has($tgt['contract_id'])) {
                $c = $contracts->get($tgt['contract_id']);
                $vars['contract'] = [
                    'id' => $c->id,
                    'uuid' => $c->uuid,
                    'reference' => $c->reference,
                    'start_date' => (string) ($c->start_date ?? ''),
                    'end_date' => (string) ($c->end_date ?? ''),
                ];
                // Include contract.meta as flattened key-value pairs
                if (is_array($c->meta) && ! empty($c->meta)) {
                    $vars['contract']['meta'] = $this->flattenMeta($c->meta);
                }
                if ($c->account) {
                    $initialRaw = (string) $c->account->initial_amount;
                    $balanceRaw = (string) $c->account->balance_amount;
                    $vars['account'] = [
                        'id' => $c->account->id,
                        'reference' => $c->account->reference,
                        // Use EU formatted values for SMS previews
                        'initial_amount' => $sms->formatAmountEu($initialRaw),
                        'balance_amount' => $sms->formatAmountEu($balanceRaw),
                        // Also expose raw values
                        'initial_amount_raw' => $initialRaw,
                        'balance_amount_raw' => $balanceRaw,
                        'type' => $c->account->type?->name,
                    ];
                }
            }

            // Prefer recorded message from result_json if available (sent items)
            $result = (array) ($it->result_json ?? []);
            $rendered = $result['message'] ?? null;
            if (! $rendered) {
                $body = isset($payload['body']) ? trim((string) $payload['body']) : '';
                if ($body !== '') {
                    $rendered = $sms->renderContent($body, $vars);
                } elseif (! empty($payload['template_id'])) {
                    $tpl = \App\Models\SmsTemplate::find((int) $payload['template_id']);
                    if ($tpl) {
                        $rendered = $sms->renderContent($tpl->content, $vars);
                    }
                }
            }
            $it->rendered_preview = $rendered;

            return $it;
        });
        // Replace paginator collection
        if (method_exists($items, 'setCollection')) {
            $items->setCollection($collection);
        }

        // Build a preview of message content from the first item (shared payload across package)
        $preview = null;
        $firstItem = $package->items()->oldest('id')->first();
        if ($firstItem) {
            $payload = (array) ($firstItem->payload_json ?? []);
            $body = isset($payload['body']) ? trim((string) $payload['body']) : '';
            // Enrich variables with contract/account for preview if available
            $vars = (array) ($payload['variables'] ?? []);
            $tgt = (array) ($firstItem->target_json ?? []);
            if (! empty($tgt['contract_id'])) {
                $c = Contract::query()->with('account.type')->find($tgt['contract_id']);
                if ($c) {
                    $vars['contract'] = [
                        'id' => $c->id,
                        'uuid' => $c->uuid,
                        'reference' => $c->reference,
                        'start_date' => (string) ($c->start_date ?? ''),
                        'end_date' => (string) ($c->end_date ?? ''),
                    ];
                    // Include contract.meta as flattened key-value pairs
                    if (is_array($c->meta) && ! empty($c->meta)) {
                        $vars['contract']['meta'] = $this->flattenMeta($c->meta);
                    }
                    if ($c->account) {
                        $initialRaw = (string) $c->account->initial_amount;
                        $balanceRaw = (string) $c->account->balance_amount;
                        $vars['account'] = [
                            'id' => $c->account->id,
                            'reference' => $c->account->reference,
                            'initial_amount' => $sms->formatAmountEu($initialRaw),
                            'balance_amount' => $sms->formatAmountEu($balanceRaw),
                            'initial_amount_raw' => $initialRaw,
                            'balance_amount_raw' => $balanceRaw,
                            'type' => $c->account->type?->name,
                        ];
                    }
                }
            }
            if ($body !== '') {
                $preview = [
                    'source' => 'body',
                    'content' => $sms->renderContent($body, $vars),
                ];
            } elseif (! empty($payload['template_id'])) {
                /** @var SmsTemplate|null $tpl */
                $tpl = SmsTemplate::find((int) $payload['template_id']);
                if ($tpl) {
                    $content = $sms->renderContent($tpl->content, $vars);
                    $preview = [
                        'source' => 'template',
                        'template' => [
                            'id' => $tpl->id,
                            'name' => $tpl->name,
                        ],
                        'content' => $content,
                    ];
                }
            }
        }

        return Inertia::render('Packages/Sms/Show', [
            'package' => $package,
            'items' => $items,
            'preview' => $preview,
        ]);
    }

    public function emailShow(Package $package): Response
    {
        $items = $package->items()->latest('id')->paginate(25);

        return Inertia::render('Packages/Mail/Show', [
            'package' => $package,
            'items' => $items,
        ]);
    }

    public function store(StorePackageRequest $request): RedirectResponse
    {
        $data = $request->validated();

        $package = Package::query()->create([
            'uuid' => (string) Str::uuid(),
            'type' => $data['type'],
            'status' => Package::STATUS_DRAFT,
            'name' => $data['name'] ?? null,
            'description' => $data['description'] ?? null,
            'meta' => $data['meta'] ?? [],
            'created_by' => optional($request->user())->id,
        ]);

        dd($data['items']);

        $items = collect($data['items'])
            ->map(function (array $row) {
                return new PackageItem([
                    'status' => 'queued',
                    'target_json' => [
                        'number' => (string) $row['number'],
                        'phone_id' => $row['phone_id'] ?? null,
                    ],
                    'payload_json' => $row['payload'] ?? [],
                ]);
            });

        $package->items()->saveMany($items);
        $package->total_items = $items->count();
        $package->save();

        return back()->with('success', 'Package created');
    }

    public function dispatch(Package $package): RedirectResponse
    {
        if (! in_array($package->status, [Package::STATUS_DRAFT, Package::STATUS_FAILED], true)) {
            return back()->with('error', 'Package not in a dispatchable state.');
        }

        $jobs = $package->items()->whereIn('status', ['queued', 'failed'])->get()->map(function (PackageItem $item) use ($package) {
            if ($package->type === Package::TYPE_EMAIL) {
                return new PackageItemEmailJob($item->id);
            }

            return new PackageItemSmsJob($item->id);
        })->all();

        if (empty($jobs)) {
            return back()->with('error', 'No items to dispatch.');
        }

        $package->status = Package::STATUS_QUEUED;
        $package->save();

        Bus::batch($jobs)
            ->name('pkg:'.$package->id.' ('.$package->type.')')
            ->then(function () use ($package) {
                // If finished counters not set by items (e.g., empty), finalize
                $package->refresh();
                if (($package->sent_count + $package->failed_count) >= $package->total_items) {
                    $finalStatus = $package->failed_count > 0 ? Package::STATUS_FAILED : Package::STATUS_COMPLETED;
                    $package->status = $finalStatus;
                    $package->finished_at = now();
                    $package->save();
                } else {
                    $package->status = Package::STATUS_RUNNING;
                    $package->save();
                }
            })
            ->onQueue($package->type === Package::TYPE_EMAIL ? 'email' : 'sms')
            ->dispatch();

        return back()->with('success', 'Package dispatched');
    }

    public function cancel(Package $package): RedirectResponse
    {
        $package->status = Package::STATUS_CANCELED;
        $package->save();

        return back()->with('success', 'Package canceled');
    }

    public function destroy(Package $package): RedirectResponse
    {
        // Allow deletion only for drafts (not yet dispatched)
        if ($package->status !== Package::STATUS_DRAFT) {
            return back()->with('error', 'Package not in a deletable state.');
        }

        // Remove items first to avoid FK issues
        $package->items()->delete();
        $package->delete();

        return back()->with('success', 'Package deleted');
    }

    /**
     * List contracts for a given segment and include selected phone per person.
     */
    public function contracts(Request $request, PhoneSelector $selector): \Illuminate\Http\JsonResponse
    {
        $request->validate([
            'segment_id' => ['nullable', 'integer', 'exists:segments,id'],
            'q' => ['nullable', 'string'],
            'client_id' => ['nullable', 'integer', 'exists:clients,id'],
            'only_mobile' => ['nullable', 'boolean'],
            'only_validated' => ['nullable', 'boolean'],
            'start_date_from' => ['nullable', 'date'],
            'start_date_to' => ['nullable', 'date'],
            'promise_date_from' => ['nullable', 'date'],
            'promise_date_to' => ['nullable', 'date'],
        ]);

        $segmentId = $request->input('segment_id') ? (int) $request->input('segment_id') : null;

        $query = Contract::query()
            ->with([
                'clientCase.person.phones',
                'clientCase.client.person',
                'account',
                'segments:id,name',
            ])
            ->select('contracts.*')
            ->latest('contracts.id');

        // Optional segment filter
        if ($segmentId) {
            $query->join('contract_segment', function ($j) use ($segmentId) {
                $j->on('contract_segment.contract_id', '=', 'contracts.id')
                    ->where('contract_segment.segment_id', '=', $segmentId)
                    ->where('contract_segment.active', true);
            });
        } else {
            // Only include contracts that have at least one active, non-excluded segment
            $query->whereExists(fn ($exist) => $exist->select(\DB::raw(1))
                ->from('contract_segment')
                ->join('segments', 'segments.id', '=', 'contract_segment.segment_id')
                ->where('contract_segment.active', true)
                ->where('segments.exclude', false)
                ->whereColumn('contract_segment.contract_id', 'contracts.id')
            );
        }

        if ($q = trim((string) $request->input('q'))) {
            $query->where(function ($w) use ($q) {
                $w->where('contracts.reference', 'ILIKE', "%{$q}%");
            });
        }

        if ($clientId = $request->integer('client_id')) {
            $query->join('client_cases', 'client_cases.id', '=', 'contracts.client_case_id')
                ->where('client_cases.client_id', $clientId);
        }

        // Date range filters for start_date
        if ($startDateFrom = $request->input('start_date_from')) {
            $query->where('contracts.start_date', '>=', $startDateFrom);
        }

        if ($startDateTo = $request->input('start_date_to')) {
            $query->where('contracts.start_date', '<=', $startDateTo);
        }

        // Date range filters for account.promise_date
        $promiseDateFrom = $request->input('promise_date_from');
        $promiseDateTo = $request->input('promise_date_to');

        if ($promiseDateFrom || $promiseDateTo) {
            $query->whereHas('account', function ($q) use ($promiseDateFrom, $promiseDateTo) {
                if ($promiseDateFrom) {
                    $q->where('promise_date', '>=', $promiseDateFrom);
                }
                if ($promiseDateTo) {
                    $q->where('promise_date', '<=', $promiseDateTo);
                }
            });
        }

        // Optional phone filters
        if ($request->boolean('only_mobile') || $request->boolean('only_validated')) {
            $query->whereHas('clientCase.person.phones', function ($q) use ($request) {
                if ($request->boolean('only_mobile')) {
                    $q->where('person_phones.phone_type', 'mobile');
                }
                if ($request->boolean('only_validated')) {
                    $q->where('person_phones.validated', true);
                }
            });
        }

        $contracts = $query->limit(500)->get();

        $data = collect($contracts)->map(function (Contract $contract) use ($selector) {
            $person = $contract->clientCase?->person;
            $selected = $person ? $selector->selectForPerson($person) : ['phone' => null, 'reason' => 'no_person'];
            $phone = $selected['phone'];
            $clientPerson = $contract->clientCase?->client?->person;
            $segment = collect($contract->segments)->last();

            return [
                'id' => $contract->id,
                'uuid' => $contract->uuid,
                'reference' => $contract->reference,
                'start_date' => $contract->start_date,
                'promise_date' => $contract->account?->promise_date,
                'case' => [
                    'id' => $contract->clientCase?->id,
                    'uuid' => $contract->clientCase?->uuid,
                ],
                // Primer: the case person
                'person' => [
                    'id' => $person?->id,
                    'uuid' => $person?->uuid,
                    'full_name' => $person?->full_name,
                ],
                'segment' => $segment,
                // Stranka: the client person
                'client' => $clientPerson ? [
                    'id' => $contract->clientCase?->client?->id,
                    'uuid' => $contract->clientCase?->client?->uuid,
                    'name' => $clientPerson->full_name,
                ] : null,
                'selected_phone' => $phone ? [
                    'id' => $phone->id,
                    'number' => $phone->nu,
                    'validated' => $phone->validated,
                    'type' => $phone->phone_type?->value,
                    'description' => $phone->description,
                ] : null,
                'no_phone_reason' => $phone ? null : ($selected['reason'] ?? 'unknown'),
            ];
        });

        return response()->json([
            'data' => $data,
        ]);
    }

    /**
     * Create an SMS package from a list of contracts by selecting recipient phones.
     */
    public function storeFromContracts(StorePackageFromContractsRequest $request, PhoneSelector $selector): RedirectResponse
    {
        $data = $request->validated();

        // Load contracts with people, phones and account (for template placeholders)
        $contracts = Contract::query()
            ->with(['clientCase.person.phones', 'account.type'])
            ->whereIn('id', $data['contract_ids'])
            ->get();

        $items = [];
        $seen = collect(); // de-dup by phone_id or number
        $skipped = 0;
        foreach ($contracts as $contract) {
            $person = $contract->clientCase?->person;
            if (! $person) {
                $skipped++;

                continue;
            }
            $selected = $selector->selectForPerson($person);
            /** @var ?\App\Models\Person\PersonPhone $phone */
            $phone = $selected['phone'];
            if (! $phone) {
                $skipped++;

                continue;
            }
            $key = $phone->id ? 'id:'.$phone->id : 'num:'.$phone->nu;
            /*if ($seen->contains($key)) {
                // skip duplicates across multiple contracts/persons
                $skipped++;

                continue;
            }*/
            $seen->push($key);
            $items[] = [
                'number' => (string) $phone->nu,
                'phone_id' => $phone->id,
                'payload' => $data['payload'] ?? [],
                // Keep context for variable rendering during send
                'contract_id' => $contract->id,
                'account_id' => $contract->account?->id,
            ];
        }

        if (empty($items)) {
            return back()->with('error', 'No recipients found for selected contracts.');
        }

        $package = Package::query()->create([
            'uuid' => (string) Str::uuid(),
            'type' => $data['type'],
            'status' => Package::STATUS_DRAFT,
            'name' => $data['name'] ?? null,
            'description' => $data['description'] ?? null,
            'meta' => array_merge($data['meta'] ?? [], [
                'source' => 'contracts',
                'skipped' => $skipped,
            ]),
            'created_by' => optional($request->user())->id,
        ]);

        $packageItems = collect($items)->map(function (array $row) {
            return new PackageItem([
                'status' => 'queued',
                'target_json' => [
                    'number' => $row['number'],
                    'phone_id' => $row['phone_id'],
                    'contract_id' => $row['contract_id'] ?? null,
                    'account_id' => $row['account_id'] ?? null,
                ],
                'payload_json' => $row['payload'] ?? [],
            ]);
        });

        $package->items()->saveMany($packageItems);
        $package->total_items = $packageItems->count();
        $package->save();

        return back()->with('success', 'Package created from contracts');
    }

    /**
     * List contracts with selected email per person (for email packages).
     */
    public function contractsForEmail(Request $request, EmailSelector $selector): \Illuminate\Http\JsonResponse
    {
        $request->validate([
            'segment_id' => ['nullable', 'integer', 'exists:segments,id'],
            'q' => ['nullable', 'string'],
            'client_id' => ['nullable', 'integer', 'exists:clients,id'],
            'only_verified' => ['nullable', 'boolean'],
            'only_with_email' => ['nullable', 'boolean'],
            'start_date_from' => ['nullable', 'date'],
            'start_date_to' => ['nullable', 'date'],
            'promise_date_from' => ['nullable', 'date'],
            'promise_date_to' => ['nullable', 'date'],
        ]);

        $segmentId = $request->input('segment_id') ? (int) $request->input('segment_id') : null;

        $query = Contract::query()
            ->with([
                'clientCase.person.emails',
                'clientCase.client.person',
                'account',
                'segments:id,name',
            ])
            ->select('contracts.*')
            ->latest('contracts.id');

        if ($segmentId) {
            $query->join('contract_segment', function ($j) use ($segmentId) {
                $j->on('contract_segment.contract_id', '=', 'contracts.id')
                    ->where('contract_segment.segment_id', '=', $segmentId)
                    ->where('contract_segment.active', true);
            });
        } else {
            $query->whereExists(fn ($exist) => $exist->select(\DB::raw(1))
                ->from('contract_segment')
                ->join('segments', 'segments.id', '=', 'contract_segment.segment_id')
                ->where('contract_segment.active', true)
                ->where('segments.exclude', false)
                ->whereColumn('contract_segment.contract_id', 'contracts.id')
            );
        }

        if ($q = trim((string) $request->input('q'))) {
            $query->where('contracts.reference', 'ILIKE', "%{$q}%");
        }

        if ($clientId = $request->integer('client_id')) {
            $query->join('client_cases', 'client_cases.id', '=', 'contracts.client_case_id')
                ->where('client_cases.client_id', $clientId);
        }

        if ($startDateFrom = $request->input('start_date_from')) {
            $query->where('contracts.start_date', '>=', $startDateFrom);
        }

        if ($startDateTo = $request->input('start_date_to')) {
            $query->where('contracts.start_date', '<=', $startDateTo);
        }

        $promiseDateFrom = $request->input('promise_date_from');
        $promiseDateTo = $request->input('promise_date_to');

        if ($promiseDateFrom || $promiseDateTo) {
            $query->whereHas('account', function ($q) use ($promiseDateFrom, $promiseDateTo) {
                if ($promiseDateFrom) {
                    $q->where('promise_date', '>=', $promiseDateFrom);
                }
                if ($promiseDateTo) {
                    $q->where('promise_date', '<=', $promiseDateTo);
                }
            });
        }

        if ($request->boolean('only_verified')) {
            $query->whereHas('clientCase.person.emails', function ($q) {
                $q->where('is_active', true)->whereNotNull('verified_at');
            });
        }

        if ($request->boolean('only_with_email')) {
            $query->whereHas('clientCase.person.emails', function ($q) {
                $q->where('is_active', true);
            });
        }

        $contracts = $query->limit(500)->get();

        $data = collect($contracts)->map(function (Contract $contract) use ($selector) {
            $person = $contract->clientCase?->person;
            $selected = $person ? $selector->selectForPerson($person) : ['email' => null, 'reason' => 'no_person'];
            $email = $selected['email'];
            $clientPerson = $contract->clientCase?->client?->person;
            $segment = collect($contract->segments)->last();

            return [
                'id' => $contract->id,
                'uuid' => $contract->uuid,
                'reference' => $contract->reference,
                'start_date' => $contract->start_date,
                'promise_date' => $contract->account?->promise_date,
                'case' => [
                    'id' => $contract->clientCase?->id,
                    'uuid' => $contract->clientCase?->uuid,
                ],
                'person' => [
                    'id' => $person?->id,
                    'uuid' => $person?->uuid,
                    'full_name' => $person?->full_name,
                ],
                'segment' => $segment,
                'client' => $clientPerson ? [
                    'id' => $contract->clientCase?->client?->id,
                    'uuid' => $contract->clientCase?->client?->uuid,
                    'name' => $clientPerson->full_name,
                ] : null,
                'selected_email' => $email ? [
                    'id' => $email->id,
                    'value' => $email->value,
                    'is_primary' => $email->is_primary,
                    'verified' => $email->verified_at !== null,
                    'label' => $email->label,
                ] : null,
                'no_email_reason' => $email ? null : ($selected['reason'] ?? 'unknown'),
            ];
        });

        return response()->json(['data' => $data]);
    }

    /**
     * Create an email package from a list of contracts by selecting recipient emails.
     */
    public function storeEmailFromContracts(StoreEmailPackageFromContractsRequest $request, EmailSelector $selector): RedirectResponse
    {
        $data = $request->validated();

        $contracts = Contract::query()
            ->with(['clientCase.person', 'account.type'])
            ->whereIn('id', $data['contract_ids'])
            ->get();

        $items = [];
        $skipped = 0;
        foreach ($contracts as $contract) {
            $person = $contract->clientCase?->person;
            if (! $person) {
                $skipped++;

                continue;
            }
            $selected = $selector->selectForPerson($person);
            /** @var ?\App\Models\Email $email */
            $email = $selected['email'];
            if (! $email) {
                $skipped++;

                continue;
            }
            $items[] = [
                'email' => $email->value,
                'email_id' => $email->id,
                'payload' => $data['payload'] ?? [],
                'contract_id' => $contract->id,
                'account_id' => $contract->account?->id,
            ];
        }

        if (empty($items)) {
            return back()->with('error', 'No recipients found for selected contracts.');
        }

        $package = Package::query()->create([
            'uuid' => (string) Str::uuid(),
            'type' => Package::TYPE_EMAIL,
            'status' => Package::STATUS_DRAFT,
            'name' => $data['name'] ?? null,
            'description' => $data['description'] ?? null,
            'meta' => array_merge($data['meta'] ?? [], [
                'source' => 'contracts',
                'skipped' => $skipped,
            ]),
            'created_by' => optional($request->user())->id,
        ]);

        $packageItems = collect($items)->map(function (array $row) {
            return new PackageItem([
                'status' => 'queued',
                'target_json' => [
                    'email' => $row['email'],
                    'email_id' => $row['email_id'],
                    'contract_id' => $row['contract_id'] ?? null,
                    'account_id' => $row['account_id'] ?? null,
                ],
                'payload_json' => $row['payload'] ?? [],
            ]);
        });

        $package->items()->saveMany($packageItems);
        $package->total_items = $packageItems->count();
        $package->save();

        return back()->with('success', 'Email package created from contracts');
    }

    /**
     * Flatten nested meta structure into dot-notation key-value pairs.
     * Extracts 'value' from objects with {title, value, type} structure.
     * Also creates direct access aliases for nested fields (skipping numeric keys).
     */
    private function flattenMeta(array $meta, string $prefix = ''): array
    {
        $result = [];
        foreach ($meta as $key => $value) {
            $newKey = $prefix === '' ? $key : "{$prefix}.{$key}";

            if (is_array($value)) {
                // Check if it's a structured meta entry with 'value' field
                if (isset($value['value'])) {
                    $result[$newKey] = $value['value'];
                    // If parent key is numeric, also create direct alias without the number
                    if ($prefix !== '' && is_numeric($key)) {
                        $result[$key] = $value['value'];
                    }
                } else {
                    // Recursively flatten nested arrays
                    $nested = $this->flattenMeta($value, $newKey);
                    $result = array_merge($result, $nested);

                    // If current key is numeric, also flatten without it for easier access
                    if (is_numeric($key)) {
                        $directNested = $this->flattenMeta($value, $prefix);
                        foreach ($directNested as $dk => $dv) {
                            // Only add if not already set (prefer first occurrence)
                            if (! isset($result[$dk])) {
                                $result[$dk] = $dv;
                            }
                        }
                    }
                }
            } else {
                $result[$newKey] = $value;
            }
        }

        return $result;
    }
}
