HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/be.naniguide.com/app/Http/Controllers/Refactor/MailListController.php
<?php

namespace App\Http\Controllers\Refactor;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Library\Cache\AppCache;
use App\Jobs\RefreshCacheableJob;
use App\Model\MailList;
use App\Model\Subscriber;
use App\Model\Setting;
use App\Services\Plans\Entitlements\EntitlementKey;
use App\Services\Plans\Gates\EntitlementGate;
use App\Services\Refactor\MailListService;
use App\Services\SubscriberService;
use App\Library\Facades\Hook;
use Gate;

class MailListController extends Controller
{
    /**
     * Lists index page.
     */
    public function index(Request $request)
    {
        if (Gate::denies('list', MailList::class)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $lists = $customer->local()->mailLists;

        // Aggregate stats for performance card (real cached data)
        $totalSubscribers = 0;
        $openRateSum = 0;
        $clickRateSum = 0;
        $unsubRateSum = 0;
        $listsWithData = 0;

        foreach ($lists as $list) {
            $totalSubscribers += intval(AppCache::for($list)->read('SubscriberCount', 0));
            $or = floatval(AppCache::for($list)->read('UniqOpenRate', 0));
            $cr = floatval(AppCache::for($list)->read('ClickedRate', 0));
            $ur = floatval(AppCache::for($list)->read('UnsubscribeRate', 0));
            if ($or > 0 || $cr > 0) {
                $openRateSum += $or;
                $clickRateSum += $cr;
                $unsubRateSum += $ur;
                $listsWithData++;
            }
        }

        $avgOpenRate = $listsWithData > 0 ? $openRateSum / $listsWithData : 0;
        $avgClickRate = $listsWithData > 0 ? $clickRateSum / $listsWithData : 0;
        $avgUnsubRate = $listsWithData > 0 ? $unsubRateSum / $listsWithData : 0;

        // Sent count from campaigns (aggregate delivered from cache)
        $totalSent = 0;
        foreach ($customer->local()->campaigns()->where('status', 'done')->get() as $campaign) {
            $totalSent += intval(AppCache::for($campaign)->read('DeliveredCount', 0));
        }

        return view('refactor.pages.lists.index', [
            'customer' => $customer,
            'totalLists' => $lists->count(),
            'totalSubscribers' => $totalSubscribers,
            'totalSent' => $totalSent,
            'avgOpenRate' => $avgOpenRate,
            'avgClickRate' => $avgClickRate,
            'avgUnsubRate' => $avgUnsubRate,
        ]);
    }

    /**
     * AJAX endpoint — period-filtered performance stats.
     */
    public function perfStats(Request $request)
    {
        $days = intval($request->input('days', 365));
        $customer = $request->user()->customer;
        $from = now()->subDays($days);

        $campaigns = $customer->local()->campaigns()
            ->where('status', 'done')
            ->where('run_at', '>=', $from)
            ->get();

        $totalSent = 0;
        $totalOpened = 0;
        $totalClicked = 0;
        $totalUnsub = 0;

        foreach ($campaigns as $c) {
            $del = intval(AppCache::for($c)->read('DeliveredCount', 0));
            $totalSent += $del;
            $totalOpened += intval(AppCache::for($c)->read('UniqOpenCount', 0));
            $totalClicked += intval(AppCache::for($c)->read('ClickedRate', 0) * $del);
            $totalUnsub += intval(AppCache::for($c)->read('UnsubscribeRate', 0) * $del);
        }

        $openRate = $totalSent > 0 ? ($totalOpened / $totalSent) * 100 : 0;
        $clickRate = $totalSent > 0 ? ($totalClicked / $totalSent) * 100 : 0;
        $unsubRate = $totalSent > 0 ? ($totalUnsub / $totalSent) * 100 : 0;

        return response()->json([
            'sent' => number_format($totalSent),
            'delivery_rate' => $totalSent > 0 ? '99%' : '0%',
            'open_rate' => number_format($openRate, 1) . '%',
            'click_rate' => number_format($clickRate, 1) . '%',
            'unsub_rate' => number_format($unsubRate, 1) . '%',
        ]);
    }

    /**
     * AJAX paginated list partial.
     */
    public function listing(Request $request)
    {
        if (Gate::denies('list', MailList::class)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;

        $lists = $customer->local()->mailLists()
            ->search($request->keyword)
            ->orderBy(
                $request->sort_order ?? 'created_at',
                $request->sort_direction ?? 'desc'
            )
            ->paginate($request->per_page ?? 25);

        $settings = [
            'list.clone_for_others' => Setting::isYes('list.clone_for_others'),
        ];

        return view('refactor.pages.lists._list', [
            'lists' => $lists,
            'settings' => $settings,
        ]);
    }

    /**
     * Refresh cache for one mail list — dispatches RefreshCacheableJob.
     * No synchronous compute; UI shows toast and the user re-visits to see
     * fresh numbers. See docs/cache/README.md "Apply caching to a page".
     */
    public function refreshCache(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (Gate::denies('read', $list)) {
            return $this->notAuthorized();
        }

        $entry = $request->input('entry'); // null = refresh entire manifest
        RefreshCacheableJob::dispatch($list, $entry);

        return response()->json([
            'message' => trans('refactor/lists.cache.refresh_dispatched'),
        ], 202);
    }

    /**
     * Copy a mail list.
     */
    public function copy(Request $request)
    {
        $list = MailList::findByUid($request->copy_list_uid);

        if (Gate::denies('update', $list)) {
            if ($request->ajax()) {
                return response()->json(['message' => trans('refactor/common.error.not_authorized')], 403);
            }
            return $this->notAuthorized();
        }

        if ($request->isMethod('post')) {
            $validator = \Validator::make($request->all(), [
                'name' => 'required',
            ]);

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

            $customer = $request->user()->customer;
            $list->copy($request->name, $customer);

            return response()->json([
                'status' => 'success',
                'message' => trans('refactor/lists.flash.copied'),
            ]);
        }

        return view('refactor.pages.lists.copy', [
            'list' => $list,
        ]);
    }

    /**
     * Delete one or more mail lists.
     */
    public function delete(Request $request)
    {
        $uids = is_array($request->uids) ? $request->uids : explode(',', $request->uids);
        $lists = MailList::whereIn('uid', $uids)->get();
        $deleted = 0;

        foreach ($lists as $item) {
            if (Gate::allows('delete', $item)) {
                $item->delete();
                (new \App\ActivityLog\Activities\ListDeleted($item, $request->user()))->record();
                event(new \App\Events\MailListUpdated($item));
                $deleted++;
            }
        }

        return response()->json([
            'status' => 'success',
            'message' => trans('refactor/lists.flash.deleted_count', ['count' => $deleted]),
        ]);
    }

    /**
     * Show create list form.
     */
    public function create(Request $request)
    {
        if (Gate::denies('create', MailList::class)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $list = $customer->newMailList();

        // Defaults
        $list->subscribe_confirmation = Setting::get('list.subscribe_confirmation', 'yes');
        $list->send_welcome_email = Setting::get('list.send_welcome_email', 'no');
        $list->unsubscribe_notification = Setting::get('list.unsubscribe_notification', 'no');

        $contact = $customer->contact;
        if ($contact) {
            if (!empty($contact->email)) {
                $list->from_email = $contact->email;
            }
            $list->from_name = trim($contact->first_name . ' ' . $contact->last_name);
        }

        if (empty($list->from_email)) {
            $list->from_email = $request->user()->email;
        }

        return view('refactor.pages.lists.create', compact('list'));
    }

    /**
     * Store a new list.
     */
    public function store(Request $request, MailListService $service)
    {
        $customer = $request->user()->customer;

        if (Gate::denies('create', $customer->newMailList())) {
            return $this->notAuthorized();
        }

        $list = $service->createFor($customer, $request->only([
            'name', 'from_email', 'from_name',
            'subscribe_confirmation', 'send_welcome_email', 'unsubscribe_notification',
        ]));

        return redirect()->route('refactor.lists.overview', $list->uid)
            ->with('success', trans('refactor/lists.flash.created'));
    }

    /**
     * List overview — stats dashboard.
     */
    public function overview(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);

        if (!$list || Gate::denies('read', $list)) {
            return $this->notAuthorized();
        }

        event(new \App\Events\MailListUpdated($list));

        $stats = [
            'subscriber_count'   => intval(AppCache::for($list)->read('SubscriberCount', 0)),
            'open_rate'          => floatval(AppCache::for($list)->read('UniqOpenRate', 0)),
            'click_rate'         => floatval(AppCache::for($list)->read('ClickedRate', 0)),
            'subscribe_rate'     => floatval(AppCache::for($list)->read('SubscribeRate', 0)),
            'unsubscribe_rate'   => floatval(AppCache::for($list)->read('UnsubscribeRate', 0)),
            'subscribe_count'    => intval(AppCache::for($list)->read('SubscribeCount', 0)),
            'unsubscribe_count'  => intval(AppCache::for($list)->read('UnsubscribeCount', 0)),
            'unconfirmed_count'  => intval(AppCache::for($list)->read('UnconfirmedCount', 0)),
            'blacklisted_count'  => intval(AppCache::for($list)->read('BlacklistedCount', 0)),
        ];

        // Subscriber composition for donut chart
        $subscribedCount   = intval(AppCache::for($list)->read('SubscriberCount', 0))
                            - intval(AppCache::for($list)->read('UnsubscribeCount', 0))
                            - intval(AppCache::for($list)->read('UnconfirmedCount', 0))
                            - intval(AppCache::for($list)->read('BlacklistedCount', 0));
        $subscribedCount   = max(0, $subscribedCount);

        $composition = [
            ['name' => 'Subscribed',   'value' => $subscribedCount,                                    'color' => '#8ecfb0'],
            ['name' => 'Unsubscribed', 'value' => intval(AppCache::for($list)->read('UnsubscribeCount', 0)),     'color' => '#f4a69e'],
            ['name' => 'Unconfirmed',  'value' => intval(AppCache::for($list)->read('UnconfirmedCount', 0)),     'color' => '#f9d57e'],
            ['name' => 'Blacklisted',  'value' => intval(AppCache::for($list)->read('BlacklistedCount', 0)),     'color' => '#b0bec5'],
        ];
        $composition = array_filter($composition, fn($c) => $c['value'] > 0);
        $composition = array_values($composition);

        // Subscriber growth — last 6 months
        $growth = [];
        for ($i = 5; $i >= 0; $i--) {
            $month = now()->subMonths($i);
            $count = $list->subscribers()
                ->whereYear('created_at', $month->year)
                ->whereMonth('created_at', $month->month)
                ->count();
            $growth[] = [
                'month' => $month->format('M Y'),
                'count' => $count,
            ];
        }

        // Segment count
        $segmentCount = $list->segmentsCount();

        // Recent subscribers (last 5)
        $recentSubscribers = $list->subscribers()
            ->orderBy('created_at', 'desc')
            ->take(5)
            ->get();

        // Daily growth — last 30 days
        $dailyGrowth = [];
        for ($i = 29; $i >= 0; $i--) {
            $day = now()->subDays($i);
            $dailyGrowth[] = [
                'day'   => $day->format('M d'),
                'count' => $list->subscribers()
                    ->whereDate('created_at', $day->toDateString())
                    ->count(),
            ];
        }

        // Getting started checklist (dynamic based on actual data)
        $checklistItems = [
            [
                'id'    => 'created',
                'title' => 'List created',
                'desc'  => 'Your list is ready to receive contacts.',
                'time'  => null,
                'done'  => true,
                'panel_title' => 'Your list is set up!',
                'panel_desc'  => 'Your contact list is created and ready. Now let\'s bring it to life by adding subscribers.',
                'panel_cta'   => null,
                'panel_cta_url' => null,
            ],
            [
                'id'    => 'contacts',
                'title' => 'Add your contacts',
                'desc'  => 'Import subscribers or add them manually.',
                'time'  => '2 min',
                'done'  => $stats['subscriber_count'] > 0,
                'panel_title' => 'Add contacts to start sending emails',
                'panel_desc'  => 'Upload a CSV file, paste contacts, or add them one by one to start sending campaigns right away.',
                'panel_cta'   => 'Import contacts',
                'panel_cta_url' => route('refactor.lists.subscriber_import', ['uid' => $list->uid]),
            ],
            [
                'id'    => 'segment',
                'title' => 'Create a segment',
                'desc'  => 'Group contacts by shared traits for better targeting.',
                'time'  => '3 min',
                'done'  => $segmentCount > 0,
                'panel_title' => 'Reach the right people',
                'panel_desc'  => 'Segments let you group subscribers by location, activity, or custom fields — so you can send more relevant campaigns.',
                'panel_cta'   => 'Create segment',
                'panel_cta_url' => route('refactor.lists.segment_create', ['uid' => $list->uid]),
            ],
            [
                'id'    => 'form',
                'title' => 'Set up a signup form',
                'desc'  => 'Grow your list automatically from your website.',
                'time'  => '4 min',
                'done'  => false,
                'panel_title' => 'Grow your list on autopilot',
                'panel_desc'  => 'Embed a signup form on your website. Every visitor who signs up goes straight into this list — no manual work needed.',
                'panel_cta'   => 'Get form code',
                'panel_cta_url' => route('refactor.lists.embedded_form', ['uid' => $list->uid]),
            ],
        ];
        $checklistDone  = count(array_filter($checklistItems, fn($c) => $c['done']));
        $checklistTotal = count($checklistItems);
        $checklistPct   = round($checklistDone / $checklistTotal * 100);
        // First non-done item is the active panel
        $activeChecklistIdx = 0;
        foreach ($checklistItems as $idx => $item) {
            if (!$item['done']) { $activeChecklistIdx = $idx; break; }
        }

        return view('refactor.pages.lists.overview', compact(
            'list', 'stats', 'composition', 'growth',
            'segmentCount', 'recentSubscribers', 'dailyGrowth',
            'checklistItems', 'checklistDone', 'checklistTotal', 'checklistPct', 'activeChecklistIdx'
        ));
    }

    /**
     * Edit list settings form.
     */
    public function edit(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);

        if (!$list || Gate::denies('update', $list)) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.lists.edit', compact('list'));
    }

    /**
     * Update list settings.
     */
    public function update(Request $request, $uid, MailListService $service)
    {
        $list = MailList::findByUid($uid);

        if (!$list || Gate::denies('update', $list)) {
            return $this->notAuthorized();
        }

        $service->update($list, $request->only([
            'name', 'from_email', 'from_name',
            'subscribe_confirmation', 'send_welcome_email', 'unsubscribe_notification',
        ]));

        return redirect()->route('refactor.lists.overview', $list->uid)
            ->with('success', trans('refactor/lists.flash.updated'));
    }

    /**
     * Subscribers listing page.
     */
    public function subscribers(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);

        if (!$list || Gate::denies('read', $list)) {
            return $this->notAuthorized();
        }

        $fields  = $list->getFields->whereNotIn('tag', ['EMAIL']);
        $pluginColumns = $this->collectPluginSubscriberColumns($list);
        $pluginBulkActions = $this->collectPluginSubscriberBulkActions($list);
        $columns = $this->resolveSubscribersColumns($request->user(), $fields, $pluginColumns);

        // Data for popup forms
        $customer   = $request->user()->customer;
        $otherLists = $customer->local()->mailLists()
            ->where('id', '!=', $list->id)
            ->orderBy('name')
            ->get()
            ->map(fn($l) => [
                'uid'  => $l->uid,
                'name' => $l->name,
                'count' => AppCache::for($l)->read('SubscriberCount', 0),
                'date'  => $l->created_at ? $l->created_at->format('M Y') : '',
            ])->values();

        return view('refactor.pages.lists.subscribers', compact(
            'list', 'columns', 'fields', 'otherLists', 'pluginColumns', 'pluginBulkActions'
        ));
    }

    /**
     * Resolve the user's saved `subscribers_columns` setting, falling back
     * to defaults when empty and one-time merging plugin-contributed
     * default-visible columns for existing users who pre-date the plugin.
     *
     * The `subscribers_columns_messenger_seen` setting gates the one-time
     * merge — once flipped, the user's saved setting is honored as-is
     * (so explicit uncheck via the column picker is persisted).
     *
     * @param  \Illuminate\Database\Eloquent\Collection<int, \App\Model\Field>  $fields
     * @param  array<int, array{key: string, label: string, default_visible?: bool}>  $pluginColumns
     * @return array<int, string>
     */
    private function resolveSubscribersColumns($user, $fields, array $pluginColumns): array
    {
        $columns = $user->getSetting('subscribers_columns');
        if (! is_array($columns)) {
            $columns = [];
        }

        $pluginDefaults = array_values(array_filter(array_map(
            fn(array $col) => !empty($col['default_visible']) ? $col['key'] : null,
            $pluginColumns
        )));

        if (empty($columns)) {
            // New user — no saved columns. Default = first/last/created + plugin defaults.
            $defaultFieldUids = $fields->whereIn('tag', ['FIRST_NAME', 'LAST_NAME'])->pluck('uid')->toArray();
            return array_merge($defaultFieldUids, ['created_at'], $pluginDefaults);
        }

        // Existing user — one-time merge of plugin default-visible columns.
        if (! empty($pluginDefaults) && empty($user->getSetting('subscribers_columns_plugin_seen'))) {
            $hasPluginKey = false;
            foreach ($columns as $key) {
                if (is_string($key) && strpos($key, ':') !== false) {
                    $hasPluginKey = true;
                    break;
                }
            }
            if (! $hasPluginKey) {
                $columns = array_merge($columns, $pluginDefaults);
                $user->writeSetting('subscribers_columns', $columns);
            }
            $user->writeSetting('subscribers_columns_plugin_seen', '1');
        }

        return $columns;
    }

    /**
     * Collect plugin-contributed extra columns for the subscriber list.
     *
     * @return array<int, array{key: string, label: string, default_visible?: bool, width?: string}>
     */
    private function collectPluginSubscriberColumns(MailList $list): array
    {
        $columns = [];
        foreach (Hook::collect('subscribers.plugin_columns', [$list]) as $contribution) {
            if (! is_array($contribution)) {
                continue;
            }
            foreach ($contribution as $col) {
                if (is_array($col) && isset($col['key'], $col['label'])) {
                    $columns[] = $col;
                }
            }
        }
        return $columns;
    }

    /**
     * Collect plugin-contributed bulk-action dropdown items.
     *
     * @return array<int, array{id: string, label: string, icon?: string, url: string, confirm?: string}>
     */
    private function collectPluginSubscriberBulkActions(MailList $list): array
    {
        $actions = [];
        foreach (Hook::collect('subscribers.bulk_actions', [$list]) as $contribution) {
            if (! is_array($contribution)) {
                continue;
            }
            foreach ($contribution as $action) {
                if (is_array($action) && isset($action['id'], $action['label'], $action['url'])) {
                    $actions[] = $action;
                }
            }
        }
        return $actions;
    }

    /**
     * Resolve subscribers query — handles select_tool=all_items (re-query by filters)
     * vs normal mode (query by IDs).
     */
    private function resolveSubscribers(Request $request, MailList $list)
    {
        if ($request->select_tool === 'all_items') {
            $query = $list->subscribers()
                ->search($request->keyword, $list->fields);

            if ($request->status) {
                $query->where('status', $request->status);
            }
            if ($request->verification) {
                if ($request->verification === 'unverified') {
                    $query->whereNull('verification_status');
                } else {
                    $query->where('verification_status', $request->verification);
                }
            }

            return $query->orderBy($request->sort_order ?? 'created_at', $request->sort_direction ?? 'desc');
        }

        $ids = array_filter(explode(',', $request->ids ?? ''));
        return $list->subscribers()->whereIn('id', $ids);
    }

    /**
     * Subscriber bulk action: move to blacklist.
     */
    public function subscriberMoveToBlacklist(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('update', $list)) {
            return response()->json(['message' => 'Not authorized'], 403);
        }

        $subscribers = $this->resolveSubscribers($request, $list)->limit(100)->get();

        $job = new \App\Jobs\MoveToBlacklist($subscribers);
        safe_dispatch($job);

        return response()->json(['message' => trans('refactor/lists.subscribers.bulk_blacklist_queued', ['count' => $subscribers->count()])]);
    }

    /**
     * Subscriber bulk action: resend confirmation email.
     */
    public function subscriberResendConfirmation(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('update', $list)) {
            return response()->json(['message' => 'Not authorized'], 403);
        }

        $subscribers = $this->resolveSubscribers($request, $list)->get();
        $sent = 0;

        foreach ($subscribers as $sub) {
            if ($sub->status === 'unconfirmed') {
                $list->sendSubscriptionConfirmationEmail($sub);
                $sent++;
            }
        }

        return response()->json(['message' => $sent . ' confirmation email(s) sent']);
    }

    /**
     * Subscriber bulk action: copy to another list.
     */
    public function subscriberCopy(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('read', $list)) {
            return response()->json(['message' => 'Not authorized'], 403);
        }

        $targetList = MailList::findByUid($request->to_uid);
        if (!$targetList || Gate::denies('update', $targetList)) {
            return response()->json(['message' => 'Target list not found'], 404);
        }

        $subscribers = $this->resolveSubscribers($request, $list)->get();

        foreach ($subscribers as $sub) {
            $sub->copy($targetList);
        }

        event(new \App\Events\MailListUpdated($targetList));

        return response()->json(['message' => $subscribers->count() . ' subscriber(s) copied']);
    }

    /**
     * Subscriber bulk action: move to another list.
     */
    public function subscriberMove(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('update', $list)) {
            return response()->json(['message' => 'Not authorized'], 403);
        }

        $targetList = MailList::findByUid($request->to_uid);
        if (!$targetList || Gate::denies('update', $targetList)) {
            return response()->json(['message' => 'Target list not found'], 404);
        }

        $subscribers = $this->resolveSubscribers($request, $list)->get();

        foreach ($subscribers as $sub) {
            $sub->move($targetList);
        }

        event(new \App\Events\MailListUpdated($targetList));
        event(new \App\Events\MailListUpdated($list));

        return response()->json(['message' => $subscribers->count() . ' subscriber(s) moved']);
    }

    /**
     * Subscriber bulk action: add tags.
     */
    public function subscriberAddTag(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('update', $list)) {
            return response()->json(['message' => 'Not authorized'], 403);
        }

        $tags = array_filter(array_map('trim', explode(',', $request->tags ?? '')));
        if (empty($tags)) {
            return response()->json(['errors' => ['tags' => ['Please enter at least one tag.']]], 422);
        }

        $subscribers = $this->resolveSubscribers($request, $list)->get();
        foreach ($subscribers as $sub) {
            $sub->addTags($tags);
        }

        return response()->json(['message' => 'Tags added to ' . $subscribers->count() . ' subscriber(s)']);
    }

    /**
     * Subscriber bulk action: remove tags.
     */
    public function subscriberRemoveTag(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('update', $list)) {
            return response()->json(['message' => 'Not authorized'], 403);
        }

        $tags = array_filter(array_map('trim', explode(',', $request->tags ?? '')));
        if (empty($tags)) {
            return response()->json(['errors' => ['tags' => ['Please select at least one tag.']]], 422);
        }

        $subscribers = $this->resolveSubscribers($request, $list)->get();
        foreach ($subscribers as $sub) {
            $sub->removeTags($tags);
        }

        return response()->json(['message' => 'Tags removed from ' . $subscribers->count() . ' subscriber(s)']);
    }

    /**
     * Subscriber bulk action: assign values.
     */
    public function subscriberAssignValues(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('update', $list)) {
            return response()->json(['message' => 'Not authorized'], 403);
        }

        $fieldUid = $request->field_uid;
        $value    = $request->value ?? '';

        $field = $list->getFields->where('uid', $fieldUid)->first();
        if (!$field) {
            return response()->json(['errors' => ['field_uid' => ['Please select a field.']]], 422);
        }

        $subscribers = $this->resolveSubscribers($request, $list)->get();
        foreach ($subscribers as $sub) {
            $sub->setFieldValue($field, $value);
            $sub->save();
        }

        return response()->json(['message' => 'Value assigned to ' . $subscribers->count() . ' subscriber(s)']);
    }

    /**
     * Subscriber bulk action: bulk delete by email list.
     */
    public function subscriberBulkDelete(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('update', $list)) {
            return response()->json(['message' => 'Not authorized'], 403);
        }

        $emails = array_filter(array_map('trim', preg_split('/[\r\n,]+/', $request->emails ?? '')));

        if (empty($emails)) {
            return response()->json(['errors' => ['emails' => ['Please enter at least one email.']]], 422);
        }

        $deleted = $list->subscribers()
            ->whereIn('email', $emails)
            ->delete();

        return response()->json(['message' => $deleted . ' subscriber(s) deleted']);
    }

    /**
     * AJAX subscriber rows.
     */
    public function subscribersListing(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);

        if (!$list || Gate::denies('read', $list)) {
            return response(trans('refactor/common.error.not_authorized'), 403);
        }

        $query = $list->subscribers()
            ->search($request->keyword, $list->fields);

        // Status filter
        if ($request->status) {
            $query->where('status', $request->status);
        }

        // Verification filter
        if ($request->verification) {
            if ($request->verification === 'unverified') {
                $query->whereNull('verification_status');
            } else {
                $query->where('verification_status', $request->verification);
            }
        }

        $query = $query
            ->orderBy($request->sort_order ?? 'created_at', $request->sort_direction ?? 'desc');

        // Hook EVENT — plugins may eager-load or join extra data onto the query.
        Hook::fire('subscribers.list_query', [$query, $list]);

        $subscribers = $query->paginate($request->per_page ?? 25);

        $fields  = $list->getFields->whereNotIn('tag', ['EMAIL']);
        $pluginColumns = $this->collectPluginSubscriberColumns($list);
        $columns = $this->resolveSubscribersColumns($request->user(), $fields, $pluginColumns);

        // Plugins may attach per-page bulk data to the subscriber collection
        // (e.g., one-query bulk-load of opt-in status for the page's subscribers).
        Hook::fire('subscribers.list_decorate', [$subscribers, $list]);

        return view('refactor.pages.lists._subscriber', compact(
            'list', 'subscribers', 'fields', 'columns', 'pluginColumns'
        ));
    }

    /**
     * Segments listing page.
     */
    public function segments(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);

        if (!$list || Gate::denies('read', $list)) {
            return $this->notAuthorized();
        }

        if (Gate::denies('list', \App\Model\Segment::class)) {
            return $this->notAuthorized();
        }

        // Stats for creative dashboard
        $totalSubscribers = intval(AppCache::for($list)->read('SubscriberCount', 0));
        $segments = $list->segments()->orderBy('name')->get();
        $segmentCount = $segments->count();

        // Per-segment subscriber counts for visualization
        $segmentStats = $segments->map(function ($seg) {
            $count = $seg->subscribersCount();
            return [
                'uid'   => $seg->uid,
                'name'  => $seg->name,
                'count' => $count,
                'match' => $seg->match ?? 'all',
                'conditions' => $seg->segmentConditions()->count(),
            ];
        });

        // Coverage: what % of list is in at least one segment
        $maxSegSubs = $segmentStats->max('count') ?? 0;
        $coveragePct = $totalSubscribers > 0 ? min(100, round($maxSegSubs / $totalSubscribers * 100)) : 0;

        return view('refactor.pages.lists.segments', compact(
            'list', 'totalSubscribers', 'segmentCount', 'segmentStats', 'coveragePct'
        ));
    }

    /**
     * Audience overview — aggregate stats across all lists.
     */
    public function audienceOverview(Request $request)
    {
        $customer = $request->user()->customer;
        $lists    = $customer->local()->mailLists()->get();

        $totalSubscribers  = 0;
        $totalUnconfirmed  = 0;
        $totalUnsubscribed = 0;
        $totalBlacklisted  = 0;
        $listStats = [];

        foreach ($lists as $list) {
            $sub   = intval(AppCache::for($list)->read('SubscriberCount', 0));
            $unsub = intval(AppCache::for($list)->read('UnsubscribeCount', 0));
            $unconf= intval(AppCache::for($list)->read('UnconfirmedCount', 0));
            $black = intval(AppCache::for($list)->read('BlacklistedCount', 0));

            $totalSubscribers  += $sub;
            $totalUnsubscribed += $unsub;
            $totalUnconfirmed  += $unconf;
            $totalBlacklisted  += $black;

            $listStats[] = [
                'uid'         => $list->uid,
                'name'        => $list->name,
                'subscribers' => $sub,
                'open_rate'   => round(floatval(AppCache::for($list)->read('UniqOpenRate', 0)) * 100, 1),
                'click_rate'  => round(floatval(AppCache::for($list)->read('ClickedRate', 0)) * 100, 1),
            ];
        }

        // Sort top lists by subscriber count
        usort($listStats, fn($a, $b) => $b['subscribers'] <=> $a['subscribers']);
        $topLists = array_slice($listStats, 0, 10);

        // Growth — total new subscribers across all lists per month (last 6 months)
        $growth = [];
        for ($i = 5; $i >= 0; $i--) {
            $month = now()->subMonths($i);
            $total = 0;
            foreach ($lists as $list) {
                $total += $list->subscribers()
                    ->whereYear('created_at', $month->year)
                    ->whereMonth('created_at', $month->month)
                    ->count();
            }
            $growth[] = ['month' => $month->format('M Y'), 'count' => $total];
        }

        // Composition (aggregate)
        $activeCount = max(0, $totalSubscribers - $totalUnsubscribed - $totalUnconfirmed - $totalBlacklisted);
        $composition = array_filter([
            ['name' => 'Subscribed',   'value' => $activeCount,        'color' => '#8ecfb0'],
            ['name' => 'Unsubscribed', 'value' => $totalUnsubscribed,  'color' => '#f4a69e'],
            ['name' => 'Unconfirmed',  'value' => $totalUnconfirmed,   'color' => '#f9d57e'],
            ['name' => 'Blacklisted',  'value' => $totalBlacklisted,   'color' => '#b0bec5'],
        ], fn($c) => $c['value'] > 0);
        $composition = array_values($composition);

        // Daily growth — last 30 days (aggregate)
        $dailyGrowth = [];
        for ($i = 29; $i >= 0; $i--) {
            $day   = now()->subDays($i);
            $total = 0;
            foreach ($lists as $list) {
                $total += $list->subscribers()
                    ->whereDate('created_at', $day->toDateString())
                    ->count();
            }
            $dailyGrowth[] = ['day' => $day->format('M d'), 'count' => $total];
        }

        // Top list for quick import link
        $topList = $lists->sortByDesc(fn($l) => intval(AppCache::for($l)->read('SubscriberCount', 0)))->first();

        return view('refactor.pages.audience.overview', compact(
            'lists', 'totalSubscribers', 'topLists', 'growth', 'composition',
            'totalUnconfirmed', 'totalUnsubscribed', 'totalBlacklisted',
            'dailyGrowth', 'topList'
        ));
    }

    /**
     * AJAX segment rows.
     */
    public function segmentsListing(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);

        if (!$list || Gate::denies('read', $list)) {
            return response(trans('refactor/common.error.not_authorized'), 403);
        }

        $request->merge(['list_uid' => $uid]);
        $request->merge(['sort_order' => $request->sort_order ?? 'name']);
        $request->merge(['sort_direction' => $request->sort_direction ?? 'asc']);

        $segments = \App\Model\Segment::search($request)->paginate($request->per_page ?? 25);

        return view('refactor.pages.lists._segment', compact('list', 'segments'));
    }

    // ─── List Fields Management ─────────────────────────────────

    public function fields(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || \Gate::denies('update', $list)) {
            return $this->notAuthorized();
        }

        $fields = $list->getFields;

        if (isset($request->old()['fields'])) {
            $fields = $list->getFieldsFromParams($request->old());
        }

        return view('refactor.pages.lists.fields', [
            'list' => $list,
            'fields' => $fields,
        ]);
    }

    public function fieldsStore(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || \Gate::denies('update', $list)) {
            return $this->notAuthorized();
        }

        $validator = $list->updateOrCreateFieldsFromRequest($request);
        $fields = $list->getFieldsFromParams($request->all());

        if ($validator->fails()) {
            if ($request->ajax()) {
                return response()->json([
                    'status' => 'error',
                    'errors' => $validator->errors(),
                ], 422);
            }
            return redirect()->route('refactor.lists.fields', $uid)
                ->withErrors($validator)
                ->withInput();
        }

        return redirect()->route('refactor.lists.fields', $uid)
            ->with('alert-success', trans('refactor/fields.flash.saved'));
    }

    public function fieldSample(Request $request, $uid, $type)
    {
        $list = MailList::findByUid($uid);
        if (!$list || \Gate::denies('update', $list)) {
            return $this->notAuthorized();
        }

        return view('fields._form_samples', [
            'list' => $list,
            'type' => $type,
        ]);
    }

    public function fieldDelete(Request $request, $uid, $fieldUid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || \Gate::denies('update', $list)) {
            return $this->notAuthorized();
        }

        $field = \App\Model\Field::findByUid($fieldUid);
        if (!$field) {
            return response()->json(['message' => 'Field not found'], 404);
        }

        if ($field->tag == 'EMAIL') {
            return redirect()->route('refactor.lists.fields', $uid)
                ->with('alert-error', trans('refactor/fields.error.miss_email'));
        }

        $field->delete();

        return redirect()->route('refactor.lists.fields', $uid)
            ->with('alert-success', trans('refactor/fields.flash.saved'));
    }

    /**
     * Reorder fields via drag-and-drop (AJAX).
     */
    public function fieldsSort(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('update', $list)) {
            return response()->json(['message' => 'Unauthorized'], 403);
        }

        $uids = $request->input('uids', []);
        foreach ($uids as $index => $fieldUid) {
            // Scoped to this list — prevents cross-list reordering
            $list->fields()->where('uid', $fieldUid)->update(['sort_order' => $index]);
        }

        return response()->json(['status' => 'ok']);
    }

    // ─── Email Verification ─────────────────────────────────────

    public function verification(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || \Gate::denies('read', $list)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $sub = $customer->getCurrentActiveSubscription();
        $plan = $sub?->plan;

        // Check for active job
        $activeJob = $list->verificationJobs()->first();

        // Verification server options
        $verificationServers = collect();
        if (EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_EMAIL_VERIFICATION_SERVER)) {
            $verificationServers = $customer->emailVerificationServers()
                ->where('status', 'active')->get();
        }

        // Get verification stats
        $totalSubscribers = intval(AppCache::for($list)->read('SubscriberCount', 0));

        // Verification credits — both limit + remaining come from the snapshot
        // (Plan Versioning §10: never mix live plan grant with snapshot
        // remaining). The "↻" icon re-syncs from the live tracker.
        $verificationCreditsHas = false;
        $verificationCreditsRemaining = null;
        $verificationCreditsLimit = null;
        $verificationCreditsLastSynced = null;
        if ($sub) {
            $credits = app(\App\Services\Plans\Credits\CreditsService::class);
            $vck = \App\Services\Plans\Credits\CreditKey::VERIFY_CONTACT;
            $verificationCreditsHas       = $credits->hasSnapshotGrant($sub, $vck);
            // grantedFromSnapshot/remainingSnapshot now return int|null|false
            // (false = no state row → sub not granted). The hasSnapshotGrant
            // boolean is the canonical disambiguator for the view: when false,
            // the view must render "Not included" and ignore the limit/remaining
            // values entirely.
            $verificationCreditsLimit     = $credits->grantedFromSnapshot($sub, $vck);
            $verificationCreditsRemaining = $credits->remainingSnapshot($sub, $vck);
            $verificationCreditsLastSynced = \App\Model\SubscriptionCreditState::where('subscription_id', $sub->id)
                ->where('credit_key', $vck->value)
                ->value('last_synced_at');
        }

        return view('refactor.pages.lists.verification', [
            'list' => $list,
            'customer' => $customer,
            'plan' => $plan,
            'subscription' => $sub,
            'activeJob' => $activeJob,
            'verificationServers' => $verificationServers,
            'totalSubscribers' => $totalSubscribers,
            'verificationCreditsHas' => $verificationCreditsHas,
            'verificationCreditsRemaining' => $verificationCreditsRemaining,
            'verificationCreditsLimit' => $verificationCreditsLimit,
            'verificationCreditsLastSynced' => $verificationCreditsLastSynced,
        ]);
    }

    /* ───────────────────────────────────────────────────────
     |  Subscriber Create / Store
     * ─────────────────────────────────────────────────────── */

    public function subscriberCreate(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('read', $list)) {
            return $this->notAuthorized();
        }

        $subscriber = new Subscriber();
        $subscriber->mail_list_id = $list->id;

        // Old post values for repopulation
        $values = [];
        if (null !== $request->old()) {
            foreach ($request->old() as $key => $value) {
                if (is_array($value)) {
                    $values[str_replace('[]', '', $key)] = implode(',', $value);
                } else {
                    $values[$key] = $value;
                }
            }
        }

        return view('refactor.pages.lists.subscriber_create', [
            'list'       => $list,
            'subscriber' => $subscriber,
            'values'     => $values,
            'fields'     => $list->getFields,
        ]);
    }

    public function subscriberStore(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('read', $list)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;
        $subscriberService = app(SubscriberService::class);

        // Ownership + RBAC
        if (!$request->user()->can('addMoreSubscribers', [$list, 1])) {
            return back()->withErrors(['quota' => trans('refactor/subscribers.quota.customer_reached_desc')]);
        }

        // Plan state: customer-wide MAX_SUBSCRIBERS + per-list MAX_SUBSCRIBERS_PER_LIST.
        // Message distinguishes which cap was hit so the admin knows why.
        if (!\App\Services\Plans\Gates\QuotaGate::allows($customer, \App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS, amount: 1)) {
            return back()->withInput()->withErrors(['quota' => trans('refactor/subscribers.quota.customer_reached_desc')]);
        }
        if (!\App\Services\Plans\Gates\QuotaGate::allows($customer, \App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS_PER_LIST, list: $list, amount: 1)) {
            return back()->withInput()->withErrors(['quota' => trans('refactor/subscribers.quota.per_list_reached_desc')]);
        }

        // Validate & create subscriber
        [$validator, $subscriber] = $subscriberService->addByCustomer($list, $request, $customer);

        if (is_null($subscriber)) {
            return back()->withInput()->withErrors($validator);
        }

        $request->session()->flash('alert-success', trans('refactor/subscribers.flash.created'));
        return redirect()->route('refactor.lists.subscribers', $list->uid);
    }

    /* ───────────────────────────────────────────────────────
     |  Subscriber Edit / Update
     * ─────────────────────────────────────────────────────── */

    public function subscriberEdit(Request $request, $uid, $id)
    {
        $list = MailList::findByUid($uid);
        if (!$list) {
            return $this->notAuthorized();
        }

        $subscriber = Subscriber::find($id);
        if (!$subscriber || Gate::denies('update', $subscriber)) {
            return $this->notAuthorized();
        }

        // Populate values from subscriber fields
        $values = [];
        foreach ($list->getFields as $field) {
            $values[$field->tag] = $subscriber->getValueByField($field);
        }
        if (null !== $request->old()) {
            foreach ($request->old() as $key => $value) {
                if (is_array($value)) {
                    $values[str_replace('[]', '', $key)] = implode(',', $value);
                } else {
                    $values[$key] = $value;
                }
            }
        }

        // Verification servers for email verification section
        $customer = $request->user()->customer;
        $verificationServers = $customer->emailVerificationServers()
            ->where('status', 'active')->get()
            ->map(fn($s) => ['value' => $s->id, 'text' => $s->name])
            ->prepend(['value' => '', 'text' => trans('refactor/subscribers.verification.select_server')])
            ->toArray();

        return view('refactor.pages.lists.subscriber_edit', [
            'list'                => $list,
            'subscriber'          => $subscriber,
            'values'              => $values,
            'fields'              => $list->getFields,
            'verificationServers' => $verificationServers,
        ]);
    }

    /**
     * Compact "business card" partial for hover-popovers in report tables
     * (open_map / click_map "Recent activity"). Returns HTML rendered by
     * the lists.subscriber_card blade — small card with status, list, key
     * counts, and an Edit CTA. Read-only Gate so anyone with view access
     * can see it.
     */
    public function subscriberCard(Request $request, $uid, $id)
    {
        $list = MailList::findByUid($uid);
        if (!$list) {
            return response('', 404);
        }

        $subscriber = Subscriber::find($id);
        if (!$subscriber || Gate::denies('read', $subscriber)) {
            return response('', 403);
        }

        return view('refactor.pages.lists._subscriber_card', [
            'list'       => $list,
            'subscriber' => $subscriber,
        ]);
    }

    public function subscriberUpdate(Request $request, $uid, $id)
    {
        $list = MailList::findByUid($uid);
        $subscriber = Subscriber::find($id);

        if (!$subscriber || Gate::denies('update', $subscriber)) {
            return $this->notAuthorized();
        }

        $this->validate($request, $subscriber->getRules());

        // Upload avatar
        if ($request->hasFile('image') && $request->file('image')->isValid()) {
            $subscriber->uploadImage($request->file('image'));
        }
        if ($request->_remove_image === 'true') {
            $subscriber->removeImage();
        }

        // Update fields
        $subscriber->updateFields($request->all());

        event(new \App\Events\SubscriptionUpdated($subscriber));
        event(new \App\Events\MailListUpdated($subscriber->mailList));

        $request->session()->flash('alert-success', trans('refactor/subscribers.flash.updated'));
        return redirect()->route('refactor.lists.subscriber_edit', [$list->uid, $subscriber->id]);
    }

    /* ───────────────────────────────────────────────────────
     |  Subscriber Delete (single, from edit page)
     * ─────────────────────────────────────────────────────── */

    public function subscriberDelete(Request $request, $uid, $id)
    {
        $list = MailList::findByUid($uid);
        $subscriber = Subscriber::find($id);

        if (!$subscriber || Gate::denies('delete', $subscriber)) {
            if ($request->ajax()) {
                return response()->json(['message' => 'Unauthorized'], 403);
            }
            return $this->notAuthorized();
        }

        $subscriber->delete();

        if ($request->ajax()) {
            return response()->json(['message' => trans('refactor/subscribers.flash.deleted')]);
        }

        $request->session()->flash('alert-success', trans('refactor/subscribers.flash.deleted'));
        return redirect()->route('refactor.lists.subscribers', $list->uid);
    }

    /**
     * R11.2 — GDPR Article 17 right-to-delete entry point.
     *
     * Customer-side button on the subscriber edit page. Dispatches
     * `RemoveSubscriberFromAudiences` (the same job the email-unsubscribe
     * cascade uses) so this email is removed from every connected ad
     * audience for the customer. Returns 202 Accepted because the
     * platform-side delete happens asynchronously — the audit trail in
     * `ad_activity_logs` confirms completion per audience.
     */
    public function subscriberPurgeFromAds(Request $request, $uid, $id)
    {
        $list = MailList::findByUid($uid);
        if (!$list) {
            return $this->notAuthorized();
        }

        $subscriber = Subscriber::find($id);
        if (!$subscriber || Gate::denies('update', $subscriber)) {
            if ($request->ajax()) {
                return response()->json(['message' => 'Unauthorized'], 403);
            }
            return $this->notAuthorized();
        }

        $customerId = (int) $list->customer_id;
        $email = (string) $subscriber->email;

        \App\Jobs\Ads\RemoveSubscriberFromAudiences::dispatch($customerId, $email);

        \App\Services\Ads\AdActivityLogger::log(
            entity: $subscriber,
            action: 'gdpr.subscriber_purged_from_ads',
            status: 'info',
            title: "Subscriber {$email} purged from all ad audiences",
            description: 'Customer-triggered GDPR right-to-delete from subscriber detail page.',
        );

        if ($request->ajax() || $request->expectsJson()) {
            return response()->json([
                'status' => 'success',
                'message' => trans('refactor/subscribers.gdpr.purged_from_ads'),
            ], 202);
        }

        $request->session()->flash('alert-success', trans('refactor/subscribers.gdpr.purged_from_ads'));
        return redirect()->route('refactor.lists.subscriber_edit', [$list->uid, $subscriber->id]);
    }

    /* ───────────────────────────────────────────────────────
     |  Subscriber Tag Management (AJAX)
     * ─────────────────────────────────────────────────────── */

    public function subscriberUpdateTags(Request $request, $uid, $id)
    {
        $subscriber = Subscriber::find($id);
        if (!$subscriber || Gate::denies('update', $subscriber)) {
            return response()->json(['message' => 'Unauthorized'], 403);
        }

        $tags = array_filter(array_map('trim', explode(',', $request->tags ?? '')));
        $subscriber->updateTags($tags, merge: true);

        return response()->json([
            'message' => trans('refactor/subscribers.flash.tags_updated'),
            'tags'    => $subscriber->getTags(),
        ]);
    }

    public function subscriberRemoveSingleTag(Request $request, $uid, $id)
    {
        $subscriber = Subscriber::find($id);
        if (!$subscriber || Gate::denies('update', $subscriber)) {
            return response()->json(['message' => 'Unauthorized'], 403);
        }

        $tag = $request->tag;
        $tags = array_filter($subscriber->getTags(), fn($t) => $t !== $tag);
        $subscriber->updateTags($tags);

        return response()->json([
            'message' => trans('refactor/subscribers.flash.tag_removed'),
            'tags'    => $subscriber->getTags(),
        ]);
    }

    /* ───────────────────────────────────────────────────────
     |  Subscriber Email Verification (from edit page)
     * ─────────────────────────────────────────────────────── */

    public function subscriberStartVerification(Request $request, $uid, $id)
    {
        $subscriber = Subscriber::find($id);
        if (!$subscriber || Gate::denies('update', $subscriber)) {
            return response()->json(['message' => 'Unauthorized'], 403);
        }

        $serverId = $request->email_verification_server_id;
        $server = \App\Model\EmailVerificationServer::find($serverId);
        if (!$server) {
            return back()->withErrors(['email_verification_server_id' => trans('refactor/subscribers.verification.server_required')]);
        }

        $subscriber->verify($server);

        $request->session()->flash('alert-success', trans('refactor/subscribers.verification.started'));
        return redirect()->route('refactor.lists.subscriber_edit', [$uid, $id]);
    }

    public function subscriberResetVerification(Request $request, $uid, $id)
    {
        $subscriber = Subscriber::find($id);
        if (!$subscriber || Gate::denies('update', $subscriber)) {
            return response()->json(['message' => 'Unauthorized'], 403);
        }

        $subscriber->resetVerification();

        $request->session()->flash('alert-success', trans('refactor/subscribers.verification.reset'));
        return redirect()->route('refactor.lists.subscriber_edit', [$uid, $id]);
    }

    /* ───────────────────────────────────────────────────────
     |  Subscriber Export
     * ─────────────────────────────────────────────────────── */

    public function subscriberExport(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('export', $list)) {
            return $this->notAuthorized();
        }

        $segments = $list->segments()->get()->map(fn($s) => [
            'uid'  => $s->uid,
            'name' => $s->name,
        ]);

        // Check for existing export job
        $currentJobUid = null;
        $progressCheckUrl = null;
        $cancelUrl = null;
        $downloadUrl = null;

        $activeJob = $list->exportJobs()->whereIn('status', ['running', 'queued'])->first();
        if ($activeJob) {
            $currentJobUid    = $activeJob->uid;
            $progressCheckUrl = route('refactor.lists.export_progress', ['uid' => $list->uid, 'job_uid' => $activeJob->uid]);
            $cancelUrl        = route('refactor.lists.cancel_export', ['uid' => $list->uid, 'job_uid' => $activeJob->uid]);
            $downloadUrl      = route('refactor.lists.download_export', ['uid' => $list->uid, 'job_uid' => $activeJob->uid]);
        }

        return view('refactor.pages.lists.subscriber_export', [
            'list'             => $list,
            'segments'         => $segments,
            'subscriberCount'  => intval(AppCache::for($list)->read('SubscriberCount', 0)),
            'fieldCount'       => $list->fields()->count(),
            'currentJobUid'    => $currentJobUid,
            'progressCheckUrl' => $progressCheckUrl,
            'cancelUrl'        => $cancelUrl,
            'downloadUrl'      => $downloadUrl,
        ]);
    }

    /* ───────────────────────────────────────────────────────
     |  Subscriber Import
     * ─────────────────────────────────────────────────────── */

    public function subscriberImport(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('import', $list)) {
            return $this->notAuthorized();
        }

        $customer = \Auth::user()->customer;
        $maxSubscribers = $customer->maxSubscribers();
        // Compare against the CUSTOMER-wide subscriber count (MAX_SUBSCRIBERS is a
        // customer-level quota). Reading the per-list cache here produced a wrong
        // progress bar when the customer had multiple lists.
        $subscriberCount = intval(AppCache::for($customer->local())->read('SubscriberCount', 0));

        // Recent import jobs (last 5)
        $recentJobs = $list->importJobs()
            ->orderBy('updated_at', 'desc')
            ->limit(5)
            ->get();

        return view('refactor.pages.lists.subscriber_import', [
            'list' => $list,
            'subscriberCount' => $subscriberCount,
            'maxSubscribers' => $maxSubscribers,
            'recentJobs' => $recentJobs,
        ]);
    }

    /**
     * Subscriber timeline — activity feed
     */
    public function subscriberTimeline(Request $request, $uid, $id)
    {
        $list = MailList::findByUid($uid);
        $subscriber = \App\Model\Subscriber::find($id);

        if (!$list || !$subscriber || Gate::denies('read', $list)) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.lists.subscriber_timeline', [
            'list' => $list,
            'subscriber' => $subscriber,
        ]);
    }

    /**
     * Subscriber timeline — AJAX listing
     */
    public function subscriberTimelineListing(Request $request, $uid, $id)
    {
        $subscriber = \App\Model\Subscriber::find($id);

        if (!$subscriber) {
            return response()->json(['error' => 'Not found'], 404);
        }

        $timelines = $subscriber->timelines();

        if ($request->type) {
            if ($request->type == 'automation') {
                $timelines = $timelines->whereNotNull('activity');
            } else {
                $timelines = $timelines->whereType($request->type);
            }
        }

        $timelines = $timelines->orderBy('created_at', 'desc')
            ->paginate($request->per_page ?? 15);

        return view('refactor.pages.lists._subscriber_timeline_list', [
            'timelines' => $timelines,
            'subscriber' => $subscriber,
        ]);
    }

    /**
     * Import wizard — Step 1: Upload CSV (popup view)
     */
    public function importWizardUpload(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('import', $list)) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.lists.import.upload', [
            'list' => $list,
        ]);
    }

    /**
     * Import wizard — Step 2: Field mapping (popup view)
     */
    public function importWizardMapping(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('import', $list)) {
            return $this->notAuthorized();
        }

        $filepath = $request->filepath;

        try {
            list($headers, $total, $results) = \App\Library\Import\DriverRegistry::for($filepath)->read($filepath);
        } catch (\Throwable $e) {
            return response()->json([
                'message' => $e->getMessage(),
            ], 404);
        }

        return view('refactor.pages.lists.import.mapping', [
            'list' => $list,
            'headers' => $headers,
            'filepath' => $filepath,
        ]);
    }

    /**
     * Import wizard — Step 3: Duplicate-handling settings (popup view)
     */
    public function importWizardSettings(Request $request, $uid)
    {
        $request->merge(['list_uid' => $uid]);
        return app(\App\Http\Controllers\SubscriberController::class)->import2Settings($request);
    }

    /**
     * Import wizard — Step 4: Progress (popup view)
     */
    public function importWizardProgress(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        $customer = \Auth::user()->customer;
        if (!$list || Gate::denies('import', $list)) {
            return $this->notAuthorized();
        }

        // Check cronjob health
        $lastExecutedTimeUtc = \Carbon\Carbon::createFromTimestamp(\App\Model\Setting::get('cronjob_last_execution') ?? 0);
        $now = \Carbon\Carbon::now();
        $lastExecutedTimeDiffInMinutes = $now->diffInMinutes($lastExecutedTimeUtc, true);
        $threshold = 20;

        $cronjobWarning = null;
        if ($lastExecutedTimeDiffInMinutes > $threshold) {
            $cronjobWarning = $lastExecutedTimeUtc->timezone($customer->getTimezone());
        }

        return view('refactor.pages.lists.import.progress', [
            'list' => $list,
            'job_uid' => $request->job_uid,
            'cronjobWarning' => $cronjobWarning,
        ]);
    }

    /* ═══════════════════════════════════════════════════════
     |  SEGMENTS — Create / Edit / Delete / Subscribers
     * ═══════════════════════════════════════════════════════ */

    public function segmentCreate(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('read', $list)) {
            return $this->notAuthorized();
        }

        $segment = new \App\Model\Segment();
        $segment->mail_list_id = $list->id;
        $segment->customer_id = $list->customer_id;

        if (Gate::denies('create', $segment)) {
            return $this->notAuthorized();
        }

        // Repopulate old input
        if (isset($request->old()['conditions'])) {
            $segment->fill($request->old());
            $segment->segmentConditions = collect();
            foreach ($request->old()['conditions'] as $key => $item) {
                $condition = new \App\Model\SegmentCondition();
                $condition->customer_id = $segment->customer_id;
                $condition->uid = $key;
                $condition->fill($item);
                $segment->segmentConditions->push($condition);
            }
        }

        return view('refactor.pages.lists.segment_form', [
            'list'    => $list,
            'segment' => $segment,
            'mode'    => 'create',
        ]);
    }

    public function segmentStore(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        $segment = new \App\Model\Segment();
        $segment->mail_list_id = $list->id;
        $segment->customer_id = $list->customer_id;

        if (Gate::denies('create', $segment)) {
            return $this->notAuthorized();
        }

        $rules = \App\Model\Segment::$rules;
        $empty = false;

        if (isset($request->conditions)) {
            foreach ($request->conditions as $key => $param) {
                $rules['conditions.' . $key . '.field_id'] = 'required';
                $rules['conditions.' . $key . '.operator'] = 'required';
                if (!in_array($param['operator'] ?? '', ['blank', 'not_blank'])) {
                    $rules['conditions.' . $key . '.value'] = 'required';
                }
            }
        } else {
            $empty = true;
        }
        if ($empty) {
            $rules['segment_conditions_empty'] = 'required';
        }

        $this->validate($request, $rules);

        $segment->fill($request->all());
        $segment->save();
        $segment->updateConditions($request->conditions);
        $segment->updateCacheDelayed();
        (new \App\ActivityLog\Activities\SegmentCreated($segment, $request->user()))->record();

        $request->session()->flash('alert-success', trans('refactor/segments.flash.created'));
        return redirect()->route('refactor.lists.segments', $list->uid);
    }

    public function segmentEdit(Request $request, $uid, $seg_uid)
    {
        $list = MailList::findByUid($uid);
        $segment = \App\Model\Segment::findByUid($seg_uid);

        if (!$list || !$segment || Gate::denies('update', $segment)) {
            return $this->notAuthorized();
        }

        // Repopulate old input
        if (isset($request->old()['conditions'])) {
            $segment->fill($request->old());
            $segment->segmentConditions = collect();
            foreach ($request->old()['conditions'] as $key => $item) {
                $condition = new \App\Model\SegmentCondition();
                $condition->customer_id = $segment->customer_id;
                $condition->uid = $key;
                $condition->fill($item);
                $segment->segmentConditions->push($condition);
            }
        }

        return view('refactor.pages.lists.segment_form', [
            'list'    => $list,
            'segment' => $segment,
            'mode'    => 'edit',
        ]);
    }

    public function segmentUpdate(Request $request, $uid, $seg_uid)
    {
        $list = MailList::findByUid($uid);
        $segment = \App\Model\Segment::findByUid($seg_uid);

        if (!$segment || Gate::denies('update', $segment)) {
            return $this->notAuthorized();
        }

        $rules = \App\Model\Segment::$rules;
        $empty = false;

        if (isset($request->conditions)) {
            foreach ($request->conditions as $key => $param) {
                $rules['conditions.' . $key . '.field_id'] = 'required';
                $rules['conditions.' . $key . '.operator'] = 'required';
                if (!in_array($param['operator'] ?? '', ['blank', 'not_blank'])) {
                    $rules['conditions.' . $key . '.value'] = 'required';
                }
            }
        } else {
            $empty = true;
        }
        if ($empty) {
            $rules['segment_conditions_empty'] = 'required';
        }

        $this->validate($request, $rules);

        $segment->fill($request->all());
        $segment->save();
        $segment->updateConditions($request->conditions);
        $segment->updateCacheDelayed();
        (new \App\ActivityLog\Activities\SegmentUpdated($segment, $request->user()))->record();

        $request->session()->flash('alert-success', trans('refactor/segments.flash.updated'));
        return redirect()->route('refactor.lists.segments', $list->uid);
    }

    public function segmentDelete(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        $uids = is_array($request->uids) ? $request->uids : explode(',', $request->uids);

        $deleted = 0;
        foreach ($uids as $segUid) {
            $segment = \App\Model\Segment::findByUid($segUid);
            if ($segment && !Gate::denies('delete', $segment)) {
                $segment->delete();
                $deleted++;
            }
        }

        if ($request->ajax()) {
            return response()->json(['message' => $deleted . ' ' . trans('refactor/segments.flash.deleted')]);
        }
        $request->session()->flash('alert-success', $deleted . ' ' . trans('refactor/segments.flash.deleted'));
        return redirect()->route('refactor.lists.segments', $list->uid);
    }

    public function segmentSubscribers(Request $request, $uid, $seg_uid)
    {
        $list = MailList::findByUid($uid);
        $segment = \App\Model\Segment::findByUid($seg_uid);

        if (!$list || !$segment || Gate::denies('read', $list)) {
            return $this->notAuthorized();
        }

        $fields = $list->getFields->whereNotIn('tag', ['EMAIL']);
        $subCount = $segment->subscribers()->count();

        return view('refactor.pages.lists.segment_subscribers', [
            'list'     => $list,
            'segment'  => $segment,
            'fields'   => $fields,
            'subCount' => $subCount,
        ]);
    }

    public function segmentSubscribersListing(Request $request, $uid, $seg_uid)
    {
        $list = MailList::findByUid($uid);
        $segment = \App\Model\Segment::findByUid($seg_uid);

        if (!$list || !$segment || Gate::denies('read', $list)) {
            return response('', 403);
        }

        $query = $segment->subscribers();

        if ($request->keyword) {
            $query->search($request->keyword, $list->fields);
        }
        if ($request->status) {
            $query->where('status', $request->status);
        }

        $sortBy = $request->sort_order ?: 'created_at';
        $sortDir = $request->sort_direction ?: 'desc';
        $query->orderBy($sortBy, $sortDir);

        $subscribers = $query->paginate($request->per_page ?: 50);

        return view('refactor.pages.lists._segment_subscriber', [
            'list'        => $list,
            'segment'     => $segment,
            'subscribers' => $subscribers,
        ]);
    }

    /* ═══════════════════════════════════════════════════════
     |  FORMS / PAGES — List Templates
     * ═══════════════════════════════════════════════════════ */

    public function listTemplates(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('update', $list)) {
            return $this->notAuthorized();
        }

        $templates = $list->mailListTemplates()->orderBy('id', 'asc')->get();

        // Group by category
        $subscribe = $templates->filter(fn($t) => str_starts_with($t->alias, 'sign_up_') || $t->alias === 'subscribe_notification_for_list_owner');
        $unsubscribe = $templates->filter(fn($t) => str_starts_with($t->alias, 'unsubscribe_'));
        $profile = $templates->filter(fn($t) => str_starts_with($t->alias, 'profile_'));

        // Hide confirmation templates when double opt-in is off
        if (!$list->subscribe_confirmation) {
            $confirmationAliases = ['sign_up_confirmation_page', 'sign_up_confirmation_email'];
            $subscribe = $subscribe->reject(fn($t) => in_array($t->alias, $confirmationAliases));
        }

        return view('refactor.pages.lists.templates_index', [
            'list'        => $list,
            'templates'   => $templates,
            'subscribe'   => $subscribe,
            'unsubscribe' => $unsubscribe,
            'profile'     => $profile,
        ]);
    }

    public function listTemplateUpdate(Request $request, $uid, $alias)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('update', $list)) {
            return $this->notAuthorized();
        }

        $mailListTemplate = $list->getTemplate($alias);
        if (!$mailListTemplate) {
            abort(404);
        }

        // Check if this template type supports outside URL
        $supportsOutsideUrl = in_array($alias, [
            'sign_up_confirmation_page',
            'sign_up_thankyou_page',
            'unsubscribe_success_page',
            'profile_update_email_sent',
            'profile_update_success_page',
        ]);

        $useOutsideUrl = $list->getAttributes()[$alias . '_use_outside_url'] ?? false;
        $outsideUrl = $list->getAttributes()[$alias . '_outside_url'] ?? '';

        // Sign-up form URL (for sign_up_form template)
        $signUpUrl = null;
        if ($alias === 'sign_up_form') {
            $signUpUrl = action('Pub\MailListController@signUpForm', [
                'list_uid'     => $list->uid,
                'customer_uid' => $list->customer->uid,
            ]);
        }

        return view('refactor.pages.lists.template_update', [
            'list'               => $list,
            'mailListTemplate'   => $mailListTemplate,
            'alias'              => $alias,
            'supportsOutsideUrl' => $supportsOutsideUrl,
            'useOutsideUrl'      => $useOutsideUrl,
            'outsideUrl'         => $outsideUrl,
            'signUpUrl'          => $signUpUrl,
        ]);
    }

    /* ───────────────────────────────────────────────────────
     |  Embedded Form
     * ─────────────────────────────────────────────────────── */

    public function embeddedForm(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('read', $list)) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;

        // Handle POST — save embedded form options
        if ($request->isMethod('post')) {
            $list->setEmbeddedFormOptions($request->options);
            return redirect()->route('refactor.lists.embedded_form', $list->uid)
                ->with('alert-success', trans('refactor/templates_pages.embedded.options_saved'));
        }

        // Build embedded form URL (public subscribe endpoint)
        $formUrl = route('refactor.lists.embedded_form_subscribe', ['uid' => $list->uid]);

        // Determine which fields to show
        $onlyRequired = $list->getEmbeddedFormOption('only_required_fields') === 'yes';
        $showInvisible = $list->getEmbeddedFormOption('show_invisible') === 'yes';
        $fields = $list->getFields;
        if (!$showInvisible) {
            $fields = $fields->where('visible', true);
        }
        if ($onlyRequired) {
            $fields = $fields->where('required', true);
        }

        // Build embed code HTML
        $includeStylesheet = $list->getEmbeddedFormOption('stylesheet') === 'yes';
        $includeJs = $list->getEmbeddedFormOption('javascript') === 'yes';
        $formTitle = $list->getEmbeddedFormOption('form_title') ?: 'Subscribe';

        $embedCode = '';
        if ($includeStylesheet) {
            $embedCode .= '<style>' . "\n";
            $embedCode .= '.form-subscribe { max-width: 400px; font-family: -apple-system, sans-serif; }' . "\n";
            $embedCode .= '.form-subscribe .form-group { margin-bottom: 12px; }' . "\n";
            $embedCode .= '.form-subscribe label { display: block; margin-bottom: 4px; font-weight: 500; font-size: 14px; }' . "\n";
            $embedCode .= '.form-subscribe input[type="text"], .form-subscribe input[type="email"] { width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; box-sizing: border-box; }' . "\n";
            $embedCode .= '.form-subscribe button { padding: 10px 20px; background: #2C9C8B; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; }' . "\n";
            $embedCode .= '.form-subscribe button:hover { background: #248075; }' . "\n";
            $embedCode .= '</style>' . "\n";
        }

        $embedCode .= '<form method="POST" action="' . $formUrl . '" class="form-subscribe">' . "\n";
        if ($formTitle) {
            $embedCode .= '  <h3>' . e($formTitle) . '</h3>' . "\n";
        }
        $embedCode .= '  <input type="hidden" name="customer_uid" value="' . e($customer->uid) . '">' . "\n";
        foreach ($fields as $field) {
            $required = $field->required ? ' required' : '';
            $type = $field->tag === 'EMAIL' ? 'email' : 'text';
            $embedCode .= '  <div class="form-group">' . "\n";
            $embedCode .= '    <label>' . e($field->label) . ($field->required ? ' *' : '') . '</label>' . "\n";
            $embedCode .= '    <input type="' . $type . '" name="' . e($field->tag) . '" placeholder="' . e($field->label) . '"' . $required . '>' . "\n";
            $embedCode .= '  </div>' . "\n";
        }
        $embedCode .= '  <button type="submit">' . e(trans('refactor/templates_pages.embedded.subscribe_btn')) . '</button>' . "\n";
        $embedCode .= '</form>';

        if ($includeJs) {
            $embedCode .= "\n" . '<script>' . "\n";
            $embedCode .= 'document.querySelector(".form-subscribe").addEventListener("submit", function(e) {' . "\n";
            $embedCode .= '  e.preventDefault();' . "\n";
            $embedCode .= '  var form = this;' . "\n";
            $embedCode .= '  var btn = form.querySelector("button");' . "\n";
            $embedCode .= '  btn.disabled = true; btn.textContent = "Subscribing...";' . "\n";
            $embedCode .= '  fetch(form.action, { method: "POST", body: new FormData(form) })' . "\n";
            $embedCode .= '    .then(function(r) { return r.text(); })' . "\n";
            $embedCode .= '    .then(function() { btn.textContent = "Subscribed!"; form.reset(); setTimeout(function(){ btn.disabled = false; btn.textContent = "Subscribe"; }, 3000); })' . "\n";
            $embedCode .= '    .catch(function() { btn.disabled = false; btn.textContent = "Subscribe"; });' . "\n";
            $embedCode .= '});' . "\n";
            $embedCode .= '</script>';
        }

        // Sign-up form URL (public page)
        $signUpUrl = action('Pub\MailListController@signUpForm', [
            'list_uid'     => $list->uid,
            'customer_uid' => $customer->uid,
        ]);

        return view('refactor.pages.lists.embedded_form', [
            'list'       => $list,
            'customer'   => $customer,
            'formUrl'    => $formUrl,
            'signUpUrl'  => $signUpUrl,
            'fields'     => $fields,
            'embedCode'  => $embedCode,
        ]);
    }

    // ─── Subscriber proxy methods ───────────────────────────────

    public function subscriberSubscribe(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('update', $list)) {
            return response()->json(['message' => 'Not authorized'], 403);
        }

        $customer = $request->user()->customer;
        $subscribers = $this->resolveSubscribers($request, $list)->get();
        $count = 0;

        foreach ($subscribers as $subscriber) {
            if (Gate::allows('subscribe', $subscriber)) {
                $subscriber->subscribe([
                    'message_id' => null,
                    'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '#unknown',
                ]);
                (new \App\ActivityLog\Activities\SubscriberSubscribed($subscriber, $request->user()))->record();
                \App\Model\Timeline::recordSubscribedByCustomer($subscriber, $customer);
                $count++;
            }
        }

        return response()->json(['message' => $count . ' subscriber(s) subscribed']);
    }

    public function subscriberUnsubscribe(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('update', $list)) {
            return response()->json(['message' => 'Not authorized'], 403);
        }

        $customer = $request->user()->customer;
        $subscribers = $this->resolveSubscribers($request, $list)->get();
        $count = 0;

        foreach ($subscribers as $subscriber) {
            if (Gate::allows('unsubscribe', $subscriber)) {
                $subscriber->unsubscribe([
                    'message_id' => null,
                    'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '#unknown',
                ]);
                (new \App\ActivityLog\Activities\SubscriberUnsubscribed($subscriber, $request->user()))->record();
                \App\Model\Timeline::recordUnsubscribedByCustomer($subscriber, $customer);
                $count++;
            }
        }

        return response()->json(['message' => $count . ' subscriber(s) unsubscribed']);
    }

    /**
     * Subscriber bulk action: delete selected by IDs (or all matching filters).
     */
    public function subscriberDeleteSelected(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);
        if (!$list || Gate::denies('update', $list)) {
            return response()->json(['message' => 'Not authorized'], 403);
        }

        $subscribers = $this->resolveSubscribers($request, $list)->get();
        $count = 0;

        foreach ($subscribers as $subscriber) {
            if (Gate::allows('delete', $subscriber)) {
                $subscriber->delete();
                $count++;
            }
        }

        return response()->json(['message' => $count . ' subscriber(s) deleted']);
    }

    public function subscriberSaveColumns(Request $request)
    {
        return app(\App\Http\Controllers\SubscriberController::class)->saveColumnsSettings($request);
    }

    public function import2Upload(Request $request, $uid)
    {
        $request->merge(['list_uid' => $uid]);
        return app(\App\Http\Controllers\SubscriberController::class)->import2Upload($request);
    }

    public function import2Validate(Request $request, $uid)
    {
        $request->merge(['list_uid' => $uid]);
        return app(\App\Http\Controllers\SubscriberController::class)->import2Validate($request);
    }

    public function import2Run(Request $request, $uid)
    {
        $request->merge(['list_uid' => $uid]);
        return app(\App\Http\Controllers\SubscriberController::class)->import2Run($request);
    }

    public function import2ProgressContent(Request $request, $uid)
    {
        $request->merge(['list_uid' => $uid]);
        return app(\App\Http\Controllers\SubscriberController::class)->import2ProgressContent($request);
    }

    public function cancelImport(Request $request, $job_uid)
    {
        $request->merge(['job_uid' => $job_uid]);
        return app(\App\Http\Controllers\SubscriberController::class)->cancelImport($request);
    }

    public function downloadImportLog(Request $request, $job_uid)
    {
        $request->merge(['job_uid' => $job_uid]);
        return app(\App\Http\Controllers\SubscriberController::class)->downloadImportLog($request);
    }

    public function dispatchExportJob(Request $request, $uid)
    {
        $request->merge(['list_uid' => $uid]);

        $oldController = app(\App\Http\Controllers\SubscriberController::class);
        $response = $oldController->dispatchExportJob($request);

        // Rewrite URLs from old routes to refactor routes
        $data = $response->getData(true);
        $list = \App\Model\MailList::findByUid($uid);
        $jobUid = $data['currentJobUid'] ?? $data['currentExportJobUid'] ?? null;

        if ($jobUid) {
            $data['currentExportJobUid'] = $jobUid;
            $data['progressCheckUrl'] = route('refactor.lists.export_progress', ['uid' => $list->uid, 'job_uid' => $jobUid]);
            $data['cancelUrl'] = route('refactor.lists.cancel_export', ['uid' => $list->uid, 'job_uid' => $jobUid]);
            $data['downloadUrl'] = route('refactor.lists.download_export', ['uid' => $list->uid, 'job_uid' => $jobUid]);
        }

        return response()->json($data);
    }

    public function exportProgress(Request $request, $uid, $job_uid)
    {
        $request->merge(['job_uid' => $job_uid]);
        return app(\App\Http\Controllers\SubscriberController::class)->exportProgress($request);
    }

    public function cancelExport(Request $request, $uid, $job_uid)
    {
        $request->merge(['job_uid' => $job_uid]);
        return app(\App\Http\Controllers\SubscriberController::class)->cancelExport($request);
    }

    public function downloadExportedFile(Request $request, $uid, $job_uid)
    {
        $request->merge(['job_uid' => $job_uid]);
        return app(\App\Http\Controllers\SubscriberController::class)->downloadExportedFile($request);
    }

    public function embeddedFormSubscribe(Request $request, $uid)
    {
        $request->merge(['uid' => $uid]);

        $subscriberService = app(SubscriberService::class);
        $customerUid = $request->customer_uid;
        $customer = \App\Model\Customer::findByUid($customerUid);

        if (is_null($customer)) {
            throw new \Exception('Cannot find customer');
        } else {
            $customer->setUserDbConnection();
        }

        $list = \App\Model\MailList::findByUid($request->uid);

        if (\App\Model\Setting::isYes('embedded_form_recaptcha')) {
            if ($request->hasCaptcha) {
                // Fail-open: any vendor rejection still lets the request through
                // but dispatches an admin notification. See
                // App\Library\Tool::verifyCaptchaOrAlert.
                $success = \App\Library\Tool::verifyCaptchaOrAlert($request, 'embedded-form');
            } else {
                return view('lists.embedded_form_captcha', [
                    'list' => $list,
                ]);
            }
        } else {
            $success = true;
        }

        if ($list->customer && $list->customer->language) {
            \App::setLocale($list->customer->language->code);
            \Carbon\Carbon::setLocale($list->customer->language->code);
        }

        if (!$list) {
            return view('somethingWentWrong', ['message' => trans('messages.embedded_form.list_not_exsit')]);
        }

        if (!$success) {
            $url = $request->session()->pull('form_url');
            $errs = [trans('messages.invalid_captcha')];

            return view('lists.embedded_form_captcha_invalid', [
                'errors' => collect($errs),
                'list' => $list,
                'back_link' => $url,
            ]);
        }

        try {
            [$validator, $subscriber] = $subscriberService->optInFromEmbeddedForm($list, $request);
        } catch (\Exception $ex) {
            return view('somethingWentWrong', ['message' => $ex->getMessage()]);
        }

        if ($validator->fails()) {
            $url = $request->session()->pull('form_url');
            $errs = [];

            foreach ($validator->errors()->toArray() as $key => $error) {
                $field = $list->getFieldByTag($key);
                $errs[] = $error[0];
            }

            if (strpos($url, '?') !== false) {
                $url = $url . '&' . implode('&', $errs);
            } else {
                $url = $url . '?' . implode('&', $errs);
            }

            return view('lists.embedded_form_errors', [
                'errors' => collect($errs),
                'list' => $list,
                'back_link' => $url,
            ]);
        }

        if ($request->redirect_url) {
            return redirect()->away($request->redirect_url);
        } elseif ($list->subscribe_confirmation && !$subscriber->isSubscribed()) {
            return redirect()->action('Pub\MailListController@signUpThankyouPage', ['list_uid' => $list->uid, 'subscriber_id' => $subscriber->id, 'customer_uid' => $list->customer->uid]);
        } else {
            return redirect()->away($subscriberService->generateSignedConfirmationUrl($subscriber));
        }
    }

    // ─── List management proxy methods ──────────────────────────

    public function saveOutsideUrls(Request $request, $uid)
    {
        $list = MailList::findByUid($uid);

        if (Gate::denies('update', $list)) {
            return $this->notAuthorized();
        }

        if ($request->has('sign_up_confirmation_page_use_outside_url')) {
            $list->sign_up_confirmation_page_use_outside_url = $request->sign_up_confirmation_page_use_outside_url;
            $list->sign_up_confirmation_page_outside_url = $request->sign_up_confirmation_page_outside_url;
        }
        if ($request->has('sign_up_thankyou_page_use_outside_url')) {
            $list->sign_up_thankyou_page_use_outside_url = $request->sign_up_thankyou_page_use_outside_url;
            $list->sign_up_thankyou_page_outside_url = $request->sign_up_thankyou_page_outside_url;
        }
        if ($request->has('unsubscribe_success_page_use_outside_url')) {
            $list->unsubscribe_success_page_use_outside_url = $request->unsubscribe_success_page_use_outside_url;
            $list->unsubscribe_success_page_outside_url = $request->unsubscribe_success_page_outside_url;
        }
        if ($request->has('profile_update_email_sent_use_outside_url')) {
            $list->profile_update_email_sent_use_outside_url = $request->profile_update_email_sent_use_outside_url;
            $list->profile_update_email_sent_outside_url = $request->profile_update_email_sent_outside_url;
        }
        if ($request->has('profile_update_success_page_use_outside_url')) {
            $list->profile_update_success_page_use_outside_url = $request->profile_update_success_page_use_outside_url;
            $list->profile_update_success_page_outside_url = $request->profile_update_success_page_outside_url;
        }

        $list->save();

        return redirect()->back();
    }

    public function resetVerification(Request $request, $uid)
    {
        $list = \App\Model\MailList::findByUid($uid);
        if (!$list || \Gate::denies('update', $list)) {
            return $this->notAuthorized();
        }

        $list->resetVerification();

        if ($request->ajax()) {
            return response()->json([
                'status' => 'success',
                'message' => trans('refactor/verification.control.reset_success'),
            ]);
        }

        return redirect()->route('refactor.lists.verification', $uid);
    }

    public function startVerification(Request $request, $uid)
    {
        $request->merge(['uid' => $uid]);

        $list = MailList::findByUid($request->uid);
        $customer = $request->user()->customer;

        if (!EntitlementGate::allows($customer, EntitlementKey::HAS_OWN_EMAIL_VERIFICATION_SERVER)) {
            $servers = $customer->getCurrentActiveSubscription()->plan->getEmailVerificationServers();
            if ($servers->isEmpty()) {
                return response()->json([
                    'status' => 'error',
                    'message' => trans('messages.email_verification_server.not_found'),
                ], 400);
            }
            $server = $servers->random();
        } else {
            $server = \App\Model\EmailVerificationServer::findByUid($request->email_verification_server_id);
        }

        $subscription = $customer->getCurrentActiveSubscription();

        $driver = $server->driver();

        if ($driver instanceof \App\Verification\Capabilities\SupportsBulkVerify) {
            $job = $list->dispatchBulkVerificationJob($server, $subscription);
        } else {
            $job = $list->dispatchVerificationJob($server, $subscription);
        }

        return response()->json([
            'currentJobUid' => $job->uid,
            'cancelUrl' => route('refactor.lists.stop_verification', ['uid' => $list->uid, 'job_uid' => $job->uid]),
            'progressCheckUrl' => route('refactor.lists.verification_progress', ['uid' => $list->uid, 'job_uid' => $job->uid]),
        ]);
    }

    public function stopVerification(Request $request, $uid, $job_uid)
    {
        $list = MailList::findByUid($uid);
        $job = $list->verificationJobs()->where('uid', $job_uid)->first();

        if (!is_null($job)) {
            $job->cancel();
        }

        return response()->json();
    }

    public function verificationProgress(Request $request, $uid, $job_uid)
    {
        $list = MailList::findByUid($uid);
        $job = $list->verificationJobs()->where('uid', $job_uid)->first();

        if (is_null($job)) {
            throw new \Exception(sprintf('Verification job #%s does not exist', $job_uid));
        }

        $progress = [
            'percentage' => round($list->getVerifiedSubscribersPercentage() * 100, 2),
            'verified' => $list->getVerifiedSubscribersCount(),
            'total' => $list->getSubscribersCount(),
            'status' => $job->status,
            'error' => $job->error,
        ];

        return response()->json($progress);
    }

    // ─── Segment proxy methods ──────────────────────────────────

    public function segmentConditionValue(Request $request)
    {
        $field = \App\Model\Field::findByUid($request->field_uid);

        return view('refactor.pages.lists._condition_value_control', [
            'operator' => $request->operator,
            'field_uid' => $request->field_uid,
            'field' => $field,
            'index' => $request->index,
            'value' => $request->value,
        ]);
    }

    public function segmentSampleCondition(Request $request, $uid)
    {
        $request->merge(['list_uid' => $uid]);

        return app(\App\Http\Controllers\SegmentController::class)->sample_condition($request);
    }

    // ─── MailListTemplate proxy methods ─────────────────────────

    public function templatePreview(Request $request, $uid)
    {
        return app(\App\Http\Controllers\MailListTemplateController::class)->preview($request, $uid);
    }

    public function templateBuilder(Request $request, $uid)
    {
        // POST (save content) — delegate to old controller update
        if ($request->isMethod('post')) {
            $mailListTemplate = \App\Model\MailListTemplate::findByUid($uid);
            $list = $mailListTemplate->mailList;
            return app(\App\Http\Controllers\MailListTemplateController::class)->update($request, $list->uid, $mailListTemplate->alias);
        }

        // GET — render builder with refactor routes
        $mailListTemplate = \App\Model\MailListTemplate::findByUid($uid);
        $list = $mailListTemplate->mailList;

        if (Gate::denies('update', $list)) {
            return $this->notAuthorized();
        }

        // Visual builder cannot edit uploaded raw HTML (json is a placeholder).
        // Force user to reset to default first.
        if ($mailListTemplate->template?->isUploaded()) {
            $request->session()->flash('alert-error', trans('refactor/templates_pages.custom_html.builder_blocked'));
            return redirect()->route('refactor.lists.template_update', [$list->uid, $mailListTemplate->alias]);
        }

        return view('mail_list_templates.builder', [
            'list' => $list,
            'mailListTemplate' => $mailListTemplate,
            'templates' => collect([]),
            '_refactorSaveUrl'   => route('refactor.lists.template_builder', $mailListTemplate->uid),
            '_refactorCancelUrl' => route('refactor.lists.template_update', [$list->uid, $mailListTemplate->alias]),
        ]);
    }

    public function templateRestoreDefault(Request $request, $uid)
    {
        $mailListTemplate = \App\Model\MailListTemplate::findByUid($uid);

        if (Gate::denies('update', $mailListTemplate->mailList)) {
            return $this->notAuthorized();
        }

        $mailListTemplate->restoreSystemTemplate();

        $redirectUrl = route('refactor.lists.template_update', [$mailListTemplate->mailList->uid, $mailListTemplate->alias]);

        if ($request->ajax()) {
            return response()->json([
                'status'   => 'success',
                'message'  => trans('messages.page.reset.success'),
                'redirect' => $redirectUrl,
            ]);
        }

        $request->session()->flash('alert-success', trans('messages.page.reset.success'));
        return redirect($redirectUrl);
    }

    /**
     * Paste raw HTML as the list template's content. GET renders the dual-mode
     * (TinyMCE / CodeMirror) editor; POST persists the HTML by either updating
     * the existing uploaded Template or creating a new one via TemplateService.
     */
    public function templateCustomHtml(Request $request, $uid)
    {
        $mailListTemplate = \App\Model\MailListTemplate::findByUid($uid);
        $list = $mailListTemplate->mailList;

        if (Gate::denies('update', $list)) {
            return $this->notAuthorized();
        }

        if ($request->isMethod('post')) {
            $this->validate($request, ['html' => 'required|string']);

            if ($mailListTemplate->template?->isUploaded()) {
                $mailListTemplate->template->updateContent(
                    \App\Model\Template::DEFAULT_BUILDER_JSON,
                    $request->html,
                );
            } else {
                \App\Services\TemplateService::for($mailListTemplate)
                    ->setCustomHtml($request->html, $mailListTemplate->alias);
            }

            if ($request->ajax()) {
                return response()->json([
                    'status'   => 'success',
                    'message'  => trans('refactor/templates_pages.custom_html.saved'),
                    'redirect' => route('refactor.lists.template_update', [$list->uid, $mailListTemplate->alias]),
                ]);
            }

            return redirect()->route('refactor.lists.template_update', [$list->uid, $mailListTemplate->alias]);
        }

        $currentHtml = $mailListTemplate->template?->isUploaded()
            ? ($mailListTemplate->template->content ?? '')
            : '';

        return view('refactor.pages.lists.template_custom_html', [
            'list'             => $list,
            'mailListTemplate' => $mailListTemplate,
            'alias'            => $mailListTemplate->alias,
            'currentHtml'      => $currentHtml,
        ]);
    }
}