<?php
namespace EasyChat\View;

class OperatorFragments {

    public static function e($value) {
        return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
    }

    public function rows(array $operators) {
        $html = '';
        foreach ($operators as $operator) {
            $html .= $this->row($operator);
        }
        return $html;
    }

    public function row(array $operator) {
        $id = (int)($operator['id'] ?? 0);
        $name = (string)($operator['name'] ?? '');
        $username = (string)($operator['username'] ?? '');
        $image = (string)($operator['image'] ?? '');
        $avatar_url = $image !== '' && defined('HTTP_AVATARS') ? HTTP_AVATARS . $image : '';
        $role = (string)($operator['role'] ?? 'manager');
        $status = (int)($operator['status'] ?? 0);
        $is_online = (int)($operator['is_online'] ?? 0) === 1;
        $app_sessions = (int)($operator['app_sessions'] ?? 0);
        $sms_devices = is_array($operator['sms_devices'] ?? null) ? $operator['sms_devices'] : [];
        $app_devices = is_array($operator['app_devices'] ?? null) ? $operator['app_devices'] : [];
        $ai_mode = $this->aiMode((string)($operator['ai_suggestions_mode'] ?? 'inherit'));
        $show_in_widget_header = (int)($operator['show_in_widget_header'] ?? 1) === 1;

        return '<tr id="op-row-' . $id . '" class="' . self::e(HtmxFragmentRenderer::motionClass('operator-row')) . '" data-operator-id="' . $id . '">'
            . '<td class="ps-4 py-3" data-label="Аватар">' . $this->avatarCell($operator) . '</td>'
            . '<td class="py-3 fw-bold text-dark" data-label="Логин">' . self::e($username) . '</td>'
            . '<td class="py-3 text-secondary" data-label="Имя"><span class="operator-name">' . self::e($name) . '</span>'
            . $this->widgetVisibilityBadge($show_in_widget_header, $status) . '</td>'
            . '<td class="py-3" data-label="Роль">' . $this->roleBadge($role) . '</td>'
            . '<td class="py-3" data-label="Активность">' . $this->activityBadge($is_online) . '</td>'
            . '<td class="py-3" data-label="Устройства">' . $this->appBadge($id, $app_sessions, $app_devices, $sms_devices) . '</td>'
            . '<td class="py-3" data-label="AI">' . $this->aiBadge($ai_mode) . '</td>'
            . '<td class="py-3 text-secondary" style="font-family: monospace;" data-label="IP адрес">' . $this->ipCell((string)($operator['ip'] ?? '')) . '</td>'
            . '<td class="py-3" data-label="Аккаунт">' . $this->accountBadge($status) . '</td>'
            . '<td class="pe-4 py-3 text-end" data-label="Действия">' . $this->actions($operator, $ai_mode) . '</td>'
            . '</tr>';
    }

    public function alert($type, $message = '', $hidden = true) {
        $is_error = $type === 'error';
        $id = $is_error ? 'operator-error-alert' : 'operator-save-alert';
        $class = $is_error ? 'alert-danger' : 'alert-success';
        return '<div id="' . $id . '" class="operator-alert alert ' . $class . ' small' . ($hidden ? ' d-none' : '') . '" role="' . ($is_error ? 'alert' : 'status') . '">'
            . '<span>' . self::e($message) . '</span>'
            . '</div>';
    }

    public function modalAlert($message, $hidden = false) {
        return '<div id="modal-alert" class="alert alert-danger mt-3 p-2.5 small' . ($hidden ? ' d-none' : '') . '" style="border-radius: 8px;" role="alert">'
            . self::e($message)
            . '</div>';
    }

    public function avatarCell(array $operator) {
        $name = (string)($operator['name'] ?? '');
        $image = (string)($operator['image'] ?? '');
        $avatar_url = $image !== '' && defined('HTTP_AVATARS') ? HTTP_AVATARS . $image : '';
        return $this->avatar($name, $avatar_url);
    }

    private function avatar($name, $avatar_url) {
        if ($avatar_url !== '') {
            return '<img src="' . self::e($avatar_url) . '" alt="Avatar" class="rounded-circle border" style="width: 44px; height: 44px; object-fit: cover;">';
        }

        return '<div class="rounded-circle d-flex align-items-center justify-content-center bg-light text-secondary fw-semibold border" style="width: 44px; height: 44px; font-size: 14px;">'
            . self::e($this->upper($this->initials($name)))
            . '</div>';
    }

    private function initials($name) {
        $parts = preg_split('/\s+/', trim((string)$name));
        if (is_array($parts) && count($parts) >= 2) {
            return $this->firstSymbol($parts[0]) . $this->firstSymbol($parts[1]);
        }
        return $this->firstSymbol((string)$name);
    }

    private function firstSymbol($value) {
        $value = (string)$value;
        if ($value === '') {
            return '';
        }
        if (function_exists('mb_substr')) {
            return mb_substr($value, 0, 1, 'UTF-8');
        }
        if (preg_match('/^./us', $value, $matches)) {
            return $matches[0];
        }
        return substr($value, 0, 1);
    }

    private function upper($value) {
        $value = (string)$value;
        if (function_exists('mb_strtoupper')) {
            return mb_strtoupper($value, 'UTF-8');
        }
        return strtoupper($value);
    }

    private function roleBadge($role) {
        if ($role === 'admin') {
            return '<span class="badge bg-indigo-subtle text-indigo px-3 py-1.5 rounded-pill" style="background: rgba(99,102,241,0.1) !important; color: #6366f1 !important; font-size: 11px;">Администратор</span>';
        }
        return '<span class="badge bg-teal-subtle text-teal px-3 py-1.5 rounded-pill" style="background: rgba(20,184,166,0.1) !important; color: #0d9488 !important; font-size: 11px;">Оператор</span>';
    }

    private function activityBadge($is_online) {
        if ($is_online) {
            return '<span class="badge bg-emerald-subtle text-emerald rounded-pill px-2.5 py-1.5" style="background: rgba(16, 185, 129, 0.1) !important; color: #10b981 !important; font-size: 11px;">В сети</span>';
        }
        return '<span class="badge bg-secondary-subtle text-muted rounded-pill px-2.5 py-1.5" style="background: rgba(148, 163, 184, 0.1) !important; color: #64748b !important; font-size: 11px;">Офлайн</span>';
    }

    private function appBadge($manager_id, $active_count, array $app_devices = [], array $sms_devices = []) {
        $history_count = 0;
        $ready_sms = 0;
        foreach ($app_devices as $device) {
            if ((string)($device['status'] ?? '') !== 'active') $history_count++;
            if ((string)($device['sms_status'] ?? '') === 'ready') $ready_sms++;
        }
        if (!$app_devices) {
            foreach ($sms_devices as $device) {
                if ((string)($device['status'] ?? '') === 'ready') $ready_sms++;
            }
        }
        $label = $active_count > 0 ? 'Устройства: ' . (int)$active_count : 'Нет устройств';
        return '<button type="button" class="device-center-trigger ' . ($active_count > 0 ? 'is-active' : 'is-empty') . '"'
            . ' data-manager-id="' . (int)$manager_id . '" aria-label="Открыть устройства оператора">'
            . '<span class="device-center-trigger-icon"><i class="fa fa-mobile" aria-hidden="true"></i></span>'
            . '<span class="device-center-trigger-copy"><strong>' . self::e($label) . '</strong><small>'
            . ($ready_sms > 0 ? '<i class="fa fa-check-circle" aria-hidden="true"></i> SMS готово'
                : ($history_count > 0 ? 'Есть история подключений' : 'Подключите по QR'))
            . '</small></span><i class="fa fa-angle-right device-center-trigger-arrow" aria-hidden="true"></i></button>';
    }

    public function deviceCenter(array $manager, array $devices) {
        $active = [];
        $history = [];
        foreach ($devices as $device) {
            if ((string)($device['status'] ?? '') === 'active') $active[] = $device;
            else $history[] = $device;
        }
        $html = '<div class="device-center-heading"><span>' . self::e((string)($manager['name'] ?? 'Оператор')) . '</span>'
            . '<strong>' . count($active) . ' ' . $this->deviceWord(count($active)) . '</strong></div>';
        if (!$active) {
            $html .= '<div class="device-center-empty"><i class="fa fa-mobile" aria-hidden="true"></i>'
                . '<strong>Нет активных устройств</strong><span>Откройте QR подключения и отсканируйте его телефоном.</span></div>';
        } else {
            $html .= '<div class="device-center-list">';
            foreach ($active as $device) $html .= $this->deviceCard($device, false);
            $html .= '</div>';
        }
        if ($history) {
            $html .= '<details class="device-center-history"><summary><span><i class="fa fa-history"></i> История подключений</span>'
                . '<small>' . count($history) . '</small></summary><div class="device-center-list">';
            foreach ($history as $device) $html .= $this->deviceCard($device, true);
            $html .= '</div></details>';
        }
        return $html;
    }

    private function deviceCard(array $device, $history) {
        $device_id = (int)($device['device_id'] ?? 0);
        $sms_device_id = (int)($device['sms_device_id'] ?? 0);
        $title = trim((string)($device['display_name'] ?? ''));
        if ($title === '') $title = trim((string)($device['manufacturer'] ?? '') . ' ' . (string)($device['device_model'] ?? ''));
        if ($title === '') $title = (string)($device['device_label'] ?? 'Android');
        $subtitle = [];
        if ((int)($device['android_sdk'] ?? 0) > 0) $subtitle[] = 'Android API ' . (int)$device['android_sdk'];
        if (!empty($device['app_version_name'])) $subtitle[] = 'EasyChat ' . (string)$device['app_version_name'];
        $identity = (string)($device['identity_status'] ?? 'legacy');
        $sms_status = (string)($device['sms_status'] ?? 'not_requested');
        $inbound_status = (string)($device['inbound_status'] ?? 'not_requested');
        if (empty($device['admin_requested'])) $sms_status = 'not_requested';
        if (empty($device['inbound_admin_requested'])) $inbound_status = 'not_requested';
        $sms_ready = $sms_status === 'ready';
        $last_seen = $this->shortDate((string)($device['last_seen_at'] ?? ''));
        $html = '<article class="device-card' . ($history ? ' is-history' : '') . '" data-app-device-id="' . $device_id . '">'
            . '<div class="device-card-head"><span class="device-card-icon"><i class="fa fa-mobile" aria-hidden="true"></i></span><div class="device-card-identity"><strong>' . self::e($title) . '</strong>'
            . '<small>' . self::e(implode(' · ', $subtitle)) . '</small></div>'
            . '<span class="device-state ' . ($history ? 'is-history' : 'is-active') . '">' . ($history ? 'История' : 'Активно') . '</span></div>'
            . '<div class="device-card-signals" role="list"><span role="listitem" class="' . ($identity === 'verified' ? 'is-good' : 'is-warn') . '"><i class="fa '
            . ($identity === 'verified' ? 'fa-shield' : 'fa-exclamation-circle') . '"></i> '
            . ($identity === 'verified' ? 'Устройство подтверждено' : 'Legacy-идентификация') . '</span>'
            . '<span role="listitem" class="' . ($sms_ready ? 'is-good' : 'is-muted') . '"><i class="fa fa-paper-plane-o"></i> '
            . ($sms_ready ? 'SMS готово' : self::e($this->smsStatusLabel($sms_status))) . '</span>'
            . '<span role="listitem" class="' . ($inbound_status === 'ready' ? 'is-good' : 'is-muted') . '"><i class="fa fa-inbox"></i> '
            . ($inbound_status === 'ready' ? 'Приём SMS готов' : self::e($this->smsStatusLabel($inbound_status))) . '</span></div>'
            . '<div class="device-card-meta"><span><i class="fa fa-clock-o" aria-hidden="true"></i> Последняя активность</span>'
            . '<strong>' . self::e($last_seen !== '' ? $last_seen : 'Нет данных') . '</strong></div>';
        if (!$history) {
            $html .= '<div class="device-card-controls">'
                . ($sms_device_id > 0 ? '<div class="device-card-permissions"><span class="device-sms-action"><small><i class="fa fa-paper-plane-o" aria-hidden="true"></i> Отправка SMS</small>'
                    . $this->smsPermissionAction($sms_device_id, $sms_status, false) . '</span>'
                    . '<span class="device-sms-action"><small><i class="fa fa-inbox" aria-hidden="true"></i> Приём SMS</small>'
                    . $this->smsPermissionAction($sms_device_id, $inbound_status, true) . '</span></div>' : '')
                . '<div class="device-card-actions">'
                . ($sms_device_id > 0 ? '<button type="button" class="btn-device-icon btn-sms-diagnostics" data-device-id="' . $sms_device_id . '" title="Диагностика SMS"><i class="fa fa-stethoscope" aria-hidden="true"></i> Диагностика</button>' : '')
                . '<button type="button" class="btn-device-rename" data-device-id="' . $device_id . '" data-device-name="' . self::e($title) . '"><i class="fa fa-pencil" aria-hidden="true"></i> Переименовать</button>'
                . '<button type="button" class="btn-device-revoke" data-device-id="' . $device_id . '" data-device-name="' . self::e($title) . '"><i class="fa fa-ban" aria-hidden="true"></i> Отозвать доступ</button></div></div>';
        }
        return $html . '</article>';
    }

    private function deviceWord($count) {
        $count = abs((int)$count) % 100;
        $last = $count % 10;
        if ($count > 10 && $count < 20) return 'устройств';
        if ($last === 1) return 'устройство';
        if ($last > 1 && $last < 5) return 'устройства';
        return 'устройств';
    }

    private function smsReliabilityState(array $device) {
        $status = (string)($device['reliability_status'] ?? 'unknown');
        $checked = $this->shortDate((string)($device['reliability_checked_at'] ?? ''));
        if ($status === 'unknown' || $checked === '') {
            return [
                'label' => 'Фон: нет данных',
                'tone' => 'muted',
                'icon' => 'fa-question-circle',
                'detail' => 'Откройте новую версию EasyChat на телефоне для проверки',
            ];
        }
        $device_name = trim((string)($device['device_manufacturer'] ?? '') . ' ' . (string)($device['device_model'] ?? ''));
        $sdk = (int)($device['android_sdk'] ?? 0);
        $facts = [];
        if ($device_name !== '') $facts[] = $device_name;
        if ($sdk > 0) $facts[] = 'Android API ' . $sdk;
        if (!empty($device['background_restricted'])) $facts[] = 'фон ограничен Android';
        if (empty($device['battery_unrestricted'])) $facts[] = 'действует экономия батареи';
        if (empty($device['fcm_synced'])) $facts[] = 'push не подтвержден';
        if (empty($device['notifications_allowed'])) $facts[] = 'уведомления выключены';
        if (!empty($device['reliability_checked_at'])) $facts[] = 'проверено ' . $checked;
        $detail = $facts ? implode(' · ', $facts) : 'Откройте новую версию EasyChat на телефоне для проверки';
        if ($status === 'ready') return ['label' => 'Фон: готов', 'tone' => 'success', 'icon' => 'fa-shield', 'detail' => $detail];
        if ($status === 'warning') return ['label' => 'Фон: требует настройки', 'tone' => 'warning', 'icon' => 'fa-shield', 'detail' => $detail];
        if ($status === 'blocking') return ['label' => 'Фон: не готов', 'tone' => 'danger', 'icon' => 'fa-exclamation-triangle', 'detail' => $detail];
        return ['label' => 'Фон: нет данных', 'tone' => 'muted', 'icon' => 'fa-question-circle', 'detail' => $detail];
    }

    private function smsInboundHealthState(array $device) {
        $status = (string)($device['inbound_health_status'] ?? 'unknown');
        if (!in_array($status, ['ready', 'warning', 'error'], true)) {
            return [
                'label' => 'Входящие: нет данных',
                'tone' => 'muted',
                'icon' => 'fa-question-circle',
                'detail' => 'Откройте актуальную версию EasyChat на телефоне для проверки приема SMS',
            ];
        }

        $mode = (string)($device['inbound_sim_mode'] ?? 'default_sim');
        $pending = max(0, min(10000, (int)($device['inbound_pending_count'] ?? 0)));
        $facts = [$mode === 'all_sims' ? 'прием со всех активных SIM' : 'прием только с SIM по умолчанию'];
        $facts[] = $pending > 0 ? 'в очереди: ' . $pending : 'очередь пуста';

        $error_label = $this->smsInboundErrorLabel((string)($device['inbound_last_error_code'] ?? ''));
        if ($error_label !== '') {
            $facts[] = $error_label;
            $error_timestamp = strtotime((string)($device['inbound_last_error_at'] ?? ''));
            if ($error_timestamp) {
                $facts[] = 'сервер получил отчет ' . date('d.m.Y H:i', $error_timestamp);
            }
        } elseif ($status === 'error') {
            $facts[] = 'телефон сообщил о сбое приема SMS';
        }

        $detail = implode(' · ', $facts);
        if ($status === 'ready') return ['label' => 'Входящие: работают', 'tone' => 'success', 'icon' => 'fa-check-circle', 'detail' => $detail];
        if ($status === 'warning') return ['label' => 'Входящие: требуют внимания', 'tone' => 'warning', 'icon' => 'fa-exclamation-circle', 'detail' => $detail];
        return ['label' => 'Входящие: ошибка', 'tone' => 'danger', 'icon' => 'fa-exclamation-triangle', 'detail' => $detail];
    }

    private function smsInboundErrorLabel($code) {
        $labels = [
            'local_persistence_failed' => 'защищенная очередь недоступна',
            'local_decryption_failed' => 'защищенную очередь не удалось прочитать',
            'local_outbox_capacity_exceeded' => 'защищенная очередь переполнена, новое SMS не сохранено',
            'local_outbox_retention_pruned' => 'удалены просроченные ответы старше 30 дней',
            'inbound_filter_unavailable' => 'список SMS-диалогов ещё не получен',
            'inbound_filter_stale' => 'список SMS-диалогов устарел',
            'inbound_filter_invalid' => 'проверка списка SMS-диалогов не пройдена',
            'sms_inbound_permission_revoked' => 'приём SMS отключён администратором',
            'inbound_sim_filtered' => 'SMS пришло на невыбранную SIM',
            'inbound_sim_unknown' => 'Android не определил SIM входящего SMS',
            'inbound_default_sim_unavailable' => 'SIM по умолчанию недоступна',
            'upload_pending' => 'загрузка на сервер ожидает подключения',
            'invalid_payload' => 'Android отклонил некорректное SMS',
            'license_blocked' => 'прием остановлен лицензией',
            'session_changed' => 'SMS относится к другой сессии приложения',
        ];
        return $labels[(string)$code] ?? '';
    }

    private function smsStatusColor($status) {
        if ($status === 'ready') {
            return '#059669';
        }
        if ($status === 'pending_operator') {
            return '#d97706';
        }
        if (in_array($status, ['device_unavailable', 'permission_missing', 'denied'], true)) {
            return '#dc2626';
        }
        return '#64748b';
    }

    private function smsPermissionAction($device_id, $status, $inbound) {
        if ($device_id <= 0) {
            return '';
        }

        if (in_array($status, ['ready', 'pending_operator'], true)) {
            $class = $inbound ? 'btn-revoke-sms-inbound-permission' : 'btn-revoke-sms-permission';
            $label = $status === 'pending_operator'
                ? ($inbound ? 'Отменить прием' : 'Отменить')
                : ($inbound ? 'Отключить прием' : 'Отключить');
            $title = $inbound
                ? 'Отключить прием SMS-ответов с этого телефона'
                : 'Отключить отправку SMS с этого телефона';
            return '<button type="button" class="btn btn-link p-0 small text-danger ' . self::e($class) . '"'
                . ' data-device-id="' . (int)$device_id . '"'
                . ' data-sms-kind="' . ($inbound ? 'inbound' : 'send') . '"'
                . ' data-sms-label="' . self::e($title) . '"'
                . ' title="' . self::e($title) . '"'
                . ' style="font-size:11px; text-decoration:none;"><i class="fa fa-power-off me-1"></i>' . self::e($label) . '</button>';
        }

        $class = $inbound ? 'btn-request-sms-inbound-permission' : 'btn-request-sms-permission';
        $label = $inbound ? 'Запросить прием' : 'Запросить';
        return '<button type="button" class="btn btn-link p-0 small ' . self::e($class) . '"'
            . ' data-device-id="' . (int)$device_id . '"'
            . ' style="font-size:11px; text-decoration:none;">' . self::e($label) . '</button>';
    }

    private function smsStatusLabel($status) {
        switch ((string)$status) {
            case 'ready':
                return 'SMS разрешены';
            case 'pending_operator':
                return 'SMS ожидает ответа';
            case 'denied':
                return 'SMS отклонено';
            case 'permission_missing':
                return 'Нет разрешения SMS';
            case 'device_unavailable':
                return 'Телефон без SIM для SMS';
            case 'disabled_by_admin':
                return 'SMS отключены админом';
            default:
                return 'SMS не запрошено';
        }
    }

    private function smsInboundStatusLabel($status) {
        switch ((string)$status) {
            case 'ready':
                return 'Прием SMS разрешен';
            case 'pending_operator':
                return 'Прием ожидает ответа';
            case 'denied':
                return 'Прием SMS отклонен';
            case 'permission_missing':
                return 'Нет разрешения приема';
            case 'disabled_by_admin':
                return 'Прием отключен админом';
            default:
                return 'Прием SMS не запрошен';
        }
    }

    private function smsInboundLastSeenLabel($value) {
        $timestamp = strtotime(trim((string)$value));
        if (!$timestamp) {
            return '';
        }
        return 'Последний ответ доставлен ' . date('d.m.Y H:i', $timestamp);
    }

    public function smsDiagnostics(array $device, array $jobs) {
        $label = trim((string)($device['device_label'] ?? 'Android'));
        $reliability = $this->smsReliabilityState($device);
        $inbound_health = $this->smsInboundHealthState($device);
        $html = '<div class="sms-diagnostics-device">'
            . '<div class="sms-diagnostics-device-head"><span class="sms-diagnostics-device-icon"><i class="fa fa-mobile" aria-hidden="true"></i></span>'
            . '<div><strong>' . self::e($label !== '' ? $label : 'Android') . '</strong>'
            . '<span><i class="fa fa-lock" aria-hidden="true"></i> Номер и текст SMS в журнал не записываются</span></div></div>'
            . '<div class="sms-diagnostics-health">'
            . '<div class="sms-diagnostics-readiness is-' . self::e($reliability['tone']) . '">'
            . '<i class="fa ' . self::e($reliability['icon']) . '" aria-hidden="true"></i><div><strong>'
            . self::e($reliability['label']) . '</strong><span>' . self::e($reliability['detail']) . '</span></div></div>'
            . '<div class="sms-diagnostics-readiness is-' . self::e($inbound_health['tone']) . '">'
            . '<i class="fa ' . self::e($inbound_health['icon']) . '" aria-hidden="true"></i><div><strong>'
            . self::e($inbound_health['label']) . '</strong><span>' . self::e($inbound_health['detail']) . '</span></div></div></div>'
            . '</div>';
        if (!$jobs) {
            return $html . '<div class="sms-diagnostics-empty"><i class="fa fa-inbox" aria-hidden="true"></i><span>SMS-задач пока нет</span></div>';
        }

        $html .= '<div class="sms-diagnostics-jobs">';
        foreach ($jobs as $job) {
            $job_id = (int)($job['job_id'] ?? 0);
            $status = $this->smsJobStatus(
                (string)($job['status'] ?? ''),
                (string)($job['delivery_status'] ?? ''),
                (string)($job['last_stage'] ?? ''),
                (string)($job['last_stage_code'] ?? ''),
                (string)(!empty($job['reserved_at']) ? $job['reserved_at'] : ($job['date_added'] ?? ''))
            );
            $html .= '<section class="sms-diagnostic-job">'
                . '<div class="sms-diagnostic-job-head"><div><strong>SMS #' . $job_id . '</strong>'
                . '<span>Сообщение #' . (int)($job['message_id'] ?? 0) . ' · ' . self::e($this->shortDate((string)($job['date_added'] ?? ''))) . '</span></div>'
                . '<span class="sms-diagnostic-state is-' . self::e($status['tone']) . '">' . self::e($status['label']) . '</span></div>';
            $events = is_array($job['events'] ?? null) ? $job['events'] : [];
            if (!$events) {
                $html .= '<p class="sms-diagnostic-no-events">Подробных событий ещё нет. Задача создана старой версией приложения.</p>';
            } else {
                $html .= '<ol class="sms-diagnostic-timeline">';
                foreach ($events as $event) {
                    $stage = (string)($event['stage'] ?? '');
                    $level = (string)($event['level'] ?? 'info');
                    $context = is_array($event['context'] ?? null) ? $event['context'] : [];
                    $detail = $this->smsEventDetail($stage, (string)($event['code'] ?? ''), $context);
                    $html .= '<li class="is-' . self::e($level) . '"><span class="sms-diagnostic-dot" aria-hidden="true"></span>'
                        . '<div><strong>' . self::e($this->smsStageLabel($stage)) . '</strong>'
                        . ($detail !== '' ? '<span>' . self::e($detail) . '</span>' : '')
                        . '</div><time>' . self::e($this->shortTime((string)($event['date_added'] ?? ''))) . '</time></li>';
                }
                $html .= '</ol>';
            }
            $html .= '</section>';
        }
        return $html . '</div>';
    }

    private function smsJobStatus($status, $delivery_status, $last_stage = '', $last_stage_code = '', $wait_started_at = '') {
        if ($delivery_status === 'delivered') return ['label' => 'Доставлено', 'tone' => 'success'];
        if ($delivery_status === 'failed') return ['label' => 'Не доставлено', 'tone' => 'danger'];
        if ($last_stage_code === 'completion_pending_sent') return ['label' => 'Отправлено, синхронизация', 'tone' => 'warning'];
        if ($status === 'outcome_unknown' || $last_stage === 'send_outcome_unknown' || in_array($last_stage_code, ['send_outcome_unknown', 'outcome_unknown', 'completion_pending_outcome_unknown'], true)) return ['label' => 'Результат неизвестен', 'tone' => 'warning'];
        if (in_array($last_stage, ['push_failed', 'domain_mismatch', 'permission_missing', 'device_unavailable', 'license_blocked', 'modem_failed', 'carrier_delivery_failed'], true)) return ['label' => 'Ошибка', 'tone' => 'danger'];
        if ($status === 'sent') return ['label' => 'Передано оператору', 'tone' => 'success'];
        if (in_array($status, ['failed', 'permission_missing', 'sim_missing'], true)) return ['label' => 'Ошибка', 'tone' => 'danger'];
        $wait_started_ts = strtotime($wait_started_at);
        if (in_array($status, ['pending', 'reserved'], true) && $wait_started_ts && time() - $wait_started_ts >= 120) {
            return ['label' => 'Нет ответа телефона', 'tone' => 'warning'];
        }
        if ($status === 'reserved') return ['label' => 'На телефоне', 'tone' => 'warning'];
        return ['label' => 'Ожидает телефон', 'tone' => 'muted'];
    }

    private function smsStageLabel($stage) {
        $labels = [
            'job_created' => 'Задача создана',
            'push_accepted' => 'Firebase принял push',
            'push_failed' => 'Push не принят',
            'push_received' => 'Телефон получил push',
            'domain_mismatch' => 'Домен не совпал',
            'worker_started' => 'Обработчик Android запущен',
            'permission_missing' => 'Нет разрешения SEND_SMS',
            'device_unavailable' => 'Телефон не готов к SMS',
            'job_fetch_started' => 'Телефон запрашивает задачу',
            'job_fetch_failed' => 'Не удалось загрузить задачу',
            'job_not_available' => 'Задача уже обработана или недоступна',
            'job_reserved' => 'Задача выдана телефону',
            'license_check_started' => 'Проверяется лицензия',
            'license_blocked' => 'Отправка заблокирована лицензией',
            'license_check_failed' => 'Не удалось проверить лицензию',
            'sms_manager_started' => 'SMS передана Android',
            'modem_accepted' => 'Модем принял SMS',
            'modem_failed' => 'Android не отправил SMS',
            'send_outcome_unknown' => 'Результат отправки неизвестен',
            'completion_reported' => 'Backend получил результат',
            'completion_report_failed' => 'Результат ожидает синхронизации',
            'carrier_delivered' => 'Оператор подтвердил доставку',
            'carrier_delivery_failed' => 'Оператор сообщил о недоставке',
        ];
        return $labels[$stage] ?? 'Событие SMS';
    }

    private function smsEventDetail($stage, $code, array $context) {
        if ($stage === 'domain_mismatch') {
            return (string)($context['push_domain'] ?? '') . ' → ' . (string)($context['current_domain'] ?? '');
        }
        if ($stage === 'push_received') {
            $model = trim((string)($context['manufacturer'] ?? '') . ' ' . (string)($context['model'] ?? ''));
            return $model !== '' ? $model . ', приложение ' . (string)($context['app_version'] ?? '') : '';
        }
        if (isset($context['part_index'], $context['part_count'])) {
            return 'Часть ' . ((int)$context['part_index'] + 1) . ' из ' . (int)$context['part_count'];
        }
        $labels = [
            'send_sms_permission_missing' => 'Разрешение можно вернуть в настройках Android',
            'sim_missing' => 'Не выбрана активная SIM для SMS',
            'no_service' => 'Нет мобильной сети',
            'radio_off' => 'Радиомодуль выключен',
            'push_domain_mismatch' => 'Телефон авторизован на другом адресе установки',
            'trial_limit_exceeded' => 'Требуется активная лицензия',
            'send_result_timeout' => 'Android не вернул callback за 90 секунд',
            'generic_failure' => 'Системная служба SMS вернула общую ошибку',
            'delivery_confirmed' => 'Получатель подтвердился через сеть оператора',
            'completion_pending_sent' => 'SMS отправлена; backend получит результат после восстановления сети',
            'completion_pending_failed' => 'Ошибка отправки сохранена на телефоне и ожидает синхронизации',
            'send_outcome_unknown' => 'Автоматический повтор отключен, чтобы не отправить дубликат',
        ];
        return $labels[$code] ?? ($code !== '' ? $code : '');
    }

    private function shortDate($value) {
        $timestamp = strtotime($value);
        return $timestamp ? date('d.m.Y H:i:s', $timestamp) : 'время неизвестно';
    }

    private function shortTime($value) {
        $timestamp = strtotime($value);
        return $timestamp ? date('H:i:s', $timestamp) : '—';
    }

    private function aiMode($mode) {
        return in_array($mode, ['inherit', 'on', 'off'], true) ? $mode : 'inherit';
    }

    private function aiBadge($mode) {
        if ($mode === 'on') {
            return '<span class="badge rounded-pill px-2.5 py-1.5" style="background: rgba(99,102,241,0.1) !important; color: #4f46e5 !important; font-size: 11px;"><i class="fa fa-magic me-1"></i>Включено</span>';
        }
        if ($mode === 'off') {
            return '<span class="badge rounded-pill px-2.5 py-1.5" style="background: rgba(148,163,184,0.12) !important; color: #64748b !important; font-size: 11px;"><i class="fa fa-magic me-1"></i>Выключено</span>';
        }
        return '<span class="badge rounded-pill px-2.5 py-1.5" style="background: rgba(14,165,233,0.1) !important; color: #0284c7 !important; font-size: 11px;"><i class="fa fa-magic me-1"></i>Наследует</span>';
    }

    private function ipCell($ip) {
        return $ip !== '' ? self::e($ip) : '<span class="text-muted">-</span>';
    }

    private function accountBadge($status) {
        if ((int)$status === 1) {
            return '<span class="badge bg-success-subtle text-success px-3 py-1.5 rounded-pill" style="background: rgba(16,185,129,0.1) !important; color: #10b981 !important; font-size: 11px;">Активен</span>';
        }
        return '<span class="badge bg-danger-subtle text-danger px-3 py-1.5 rounded-pill" style="background: rgba(239,68,68,0.1) !important; color: #ef4444 !important; font-size: 11px;">Отключен</span>';
    }

    private function widgetVisibilityBadge($visible, $status) {
        if ((int)$status !== 1) {
            return '<span class="operator-widget-badge is-hidden"><i class="fa fa-eye-slash" aria-hidden="true"></i> Не показывается: отключён</span>';
        }
        if ($visible) {
            return '<span class="operator-widget-badge is-visible"><i class="fa fa-eye" aria-hidden="true"></i> В шапке виджета</span>';
        }
        return '<span class="operator-widget-badge is-hidden"><i class="fa fa-eye-slash" aria-hidden="true"></i> Скрыт из шапки</span>';
    }

    private function actions(array $operator, $ai_mode) {
        $id = (int)($operator['id'] ?? 0);
        $html = '<button type="button" class="btn btn-light btn-sm text-dark me-2 px-2.5 py-1.5 btn-view-qr"'
            . ' style="border-radius: 8px;" title="Вход по QR-коду"'
            . ' data-name="' . self::e($operator['name'] ?? '') . '"'
            . ' data-manager-id="' . $id . '">'
            . '<i class="fa fa-qrcode"></i>'
            . '</button>';

        $html .= '<button type="button" class="btn btn-light btn-sm text-dark me-2 px-2.5 py-1.5 btn-edit-operator"'
            . ' style="border-radius: 8px;"'
            . ' data-id="' . $id . '"'
            . ' data-username="' . self::e($operator['username'] ?? '') . '"'
            . ' data-name="' . self::e($operator['name'] ?? '') . '"'
            . ' data-status="' . (int)($operator['status'] ?? 0) . '"'
            . ' data-image="' . self::e($operator['image'] ?? '') . '"'
            . ' data-permission-delete-chats="' . (int)($operator['permission_delete_chats'] ?? 0) . '"'
            . ' data-permission-manage-chat-assignments="' . (int)($operator['permission_manage_chat_assignments'] ?? 0) . '"'
            . ' data-show-in-widget-header="' . ((int)($operator['show_in_widget_header'] ?? 1) === 1 ? '1' : '0') . '"'
            . ' data-ai-suggestions-mode="' . self::e($ai_mode) . '">'
            . '<i class="fa fa-pencil"></i>'
            . '</button>';

        if ($id !== 1) {
            $delete_values = json_encode(['id' => $id], JSON_UNESCAPED_SLASHES);
            $html .= '<button type="button" class="btn btn-light btn-sm text-danger px-2.5 py-1.5 btn-delete-operator"'
                . ' style="border-radius: 8px;"'
                . ' hx-post="index.php?route=operator/delete"'
                . ' hx-vals="' . self::e($delete_values ?: '{}') . '"'
                . ' hx-include="#operator-csrf"'
                . ' data-csrf-field="csrf_token"'
                . ' hx-target="#op-row-' . $id . '"'
                . ' hx-swap="outerHTML transition:true"'
                . ' hx-sync="#operators-table-body:replace"'
                . ' hx-confirm="Вы действительно хотите навсегда удалить этого оператора? Доступ к приложению и панели управления будет мгновенно заблокирован.">'
                . '<i class="fa fa-trash"></i>'
                . '</button>';
        }

        return $html;
    }
}
