<?php
// gestionar_cuentas.php - operación profesional segura
require_once __DIR__ . '/init.php';
restringir_modulo_superadmin('Cuentas');

$id_admin_actual = (int) ($_SESSION['admin']['uid'] ?? 0);

function afenixx_account_validate_date(string $value): bool
{
    $date = DateTimeImmutable::createFromFormat('Y-m-d', $value);
    return $date !== false && $date->format('Y-m-d') === $value;
}

function afenixx_account_normalize_date(string $value): ?string
{
    $value = trim($value);
    if ($value === '') return null;
    foreach (['Y-m-d', 'd/m/Y'] as $format) {
        $date = DateTimeImmutable::createFromFormat('!' . $format, $value);
        $errors = DateTimeImmutable::getLastErrors();
        if ($date !== false && ($errors === false || ((int) $errors['warning_count'] === 0 && (int) $errors['error_count'] === 0))) {
            return $date->format('Y-m-d');
        }
    }
    return null;
}

function afenixx_account_clean_ip_reference(string $value): ?string
{
    $value = trim((string) preg_replace('/[\x00-\x1F\x7F]+/u', ' ', $value));
    $value = trim((string) preg_replace('/\s+/u', ' ', $value));
    if ($value === '') return null;
    if (mb_strlen($value, 'UTF-8') > 100) {
        throw new InvalidArgumentException('El campo IP / país no puede superar 100 caracteres.');
    }
    return $value;
}

/**
 * Formatos admitidos en carga masiva:
 * correo@dominio.com
 * correo@dominio.com | 2026-12-31
 * correo@dominio.com | 2026-12-31 | PE
 * correo@dominio.com | | Perú
 */
function afenixx_account_parse_bulk_line(string $line, ?string $defaultIp = null): array
{
    $line = trim($line);
    if ($line === '') return ['valid' => false];

    $emailRaw = '';
    $dateRaw = '';
    $ipRaw = '';

    if (str_contains($line, '|')) {
        $parts = array_map('trim', explode('|', $line, 3));
        [$emailRaw, $dateRaw, $ipRaw] = array_pad($parts, 3, '');
    } else {
        $normalized = trim((string) preg_replace('/[;,\t]+/u', ' ', $line));
        if (!preg_match('/^(\S+)(?:\s+(\d{4}-\d{2}-\d{2}|\d{2}\/\d{2}\/\d{4}))?(?:\s+(.+))?$/u', $normalized, $matches)) {
            return ['valid' => false];
        }
        $emailRaw = trim((string) ($matches[1] ?? ''));
        $dateRaw = trim((string) ($matches[2] ?? ''));
        $ipRaw = trim((string) ($matches[3] ?? ''));
    }

    $email = filter_var($emailRaw, FILTER_VALIDATE_EMAIL);
    if (!$email) return ['valid' => false];

    $renewalDate = null;
    if ($dateRaw !== '') {
        $renewalDate = afenixx_account_normalize_date($dateRaw);
        if ($renewalDate === null) return ['valid' => false, 'invalid_date' => true];
    }

    try {
        $ip = afenixx_account_clean_ip_reference($ipRaw !== '' ? $ipRaw : (string) ($defaultIp ?? ''));
    } catch (InvalidArgumentException) {
        return ['valid' => false];
    }

    return [
        'valid' => true,
        'email' => $email,
        'renewal_date' => $renewalDate,
        'ip' => $ip,
    ];
}

function afenixx_account_assert_domain(PDO $pdo, int $domainId, int $adminId): void
{
    $stmt = $pdo->prepare('SELECT COUNT(*) FROM dominios WHERE id = ? AND id_admin_propietario = ?');
    $stmt->execute([$domainId, $adminId]);
    if (!(int) $stmt->fetchColumn()) {
        throw new InvalidArgumentException('El dominio seleccionado no pertenece a tu administración.');
    }
}

function afenixx_account_assert_client(PDO $pdo, ?int $clientId, int $adminId): void
{
    if (!$clientId) return;
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM usuarios WHERE id = ? AND id_admin_propietario = ? AND id_rol IN (SELECT id FROM roles WHERE nombre_rol IN ('Revendedor','Cliente Final'))");
    $stmt->execute([$clientId, $adminId]);
    if (!(int) $stmt->fetchColumn()) {
        throw new InvalidArgumentException('El cliente seleccionado no pertenece a tu administración.');
    }
}

function afenixx_account_capacity(PDO $pdo, int $adminId): array
{
    $stmt = $pdo->prepare(
        "SELECT p.limite_cuentas
         FROM suscripciones s
         JOIN planes p ON p.id = s.id_plan
         WHERE s.id_admin = ? AND s.estado IN ('activa', 'prueba')
         ORDER BY s.fecha_fin DESC, s.id DESC
         LIMIT 1"
    );
    $stmt->execute([$adminId]);
    $rawLimit = $stmt->fetchColumn();
    $limit = ($rawLimit === false || $rawLimit === null || (int) $rawLimit <= 0)
        ? null
        : (int) $rawLimit;

    $count = $pdo->prepare('SELECT COUNT(*) FROM correos_servicios WHERE id_admin_propietario = ?');
    $count->execute([$adminId]);

    return ['limit' => $limit, 'used' => (int) $count->fetchColumn()];
}

function afenixx_account_assert_capacity(array $capacity, int $newAccounts): void
{
    $limit = $capacity['limit'] ?? null;
    if ($limit === null || $newAccounts <= 0) return;

    $used = (int) ($capacity['used'] ?? 0);
    if ($used + $newAccounts > (int) $limit) {
        $available = max(0, (int) $limit - $used);
        throw new RuntimeException(
            "Tu plan permite registrar hasta {$limit} cuentas de streaming. Actualmente tienes {$used} y dispones de {$available} espacio(s)."
        );
    }
}

function afenixx_account_read_txt_upload(string $field, int $maxBytes = 1048576): array
{
    if (!isset($_FILES[$field]) || (int) $_FILES[$field]['error'] === UPLOAD_ERR_NO_FILE) return [];
    $file = $_FILES[$field];
    if ((int) $file['error'] !== UPLOAD_ERR_OK) throw new RuntimeException('No se pudo recibir el archivo TXT.');
    if ((int) $file['size'] > $maxBytes) throw new RuntimeException('El archivo TXT no debe superar 1 MB.');
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = (string) $finfo->file($file['tmp_name']);
    if (!in_array($mime, ['text/plain', 'application/octet-stream'], true)) throw new RuntimeException('El archivo debe ser un TXT válido.');
    $content = file_get_contents($file['tmp_name']);
    if ($content === false) throw new RuntimeException('No se pudo leer el archivo TXT.');
    return preg_split('/\r\n|\r|\n/', $content, -1, PREG_SPLIT_NO_EMPTY) ?: [];
}

function afenixx_account_selected_ids(mixed $raw, int $max = 500): array
{
    if (!is_array($raw)) return [];
    $ids = array_values(array_unique(array_filter(array_map('intval', $raw), static fn (int $id): bool => $id > 0)));
    if (count($ids) > $max) throw new InvalidArgumentException("Solo puedes procesar hasta {$max} cuentas por operación.");
    return $ids;
}

function afenixx_account_get_renewal_preference(PDO $pdo, int $adminId): array
{
    $default = ['modalidad' => 'mes_calendario', 'cantidad_dias' => 30];

    try {
        $stmt = $pdo->prepare(
            'SELECT modalidad, cantidad_dias
             FROM configuracion_renovacion_admin
             WHERE id_admin = ?
             LIMIT 1'
        );
        $stmt->execute([$adminId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$row) {
            return $default;
        }

        $mode = in_array((string) ($row['modalidad'] ?? ''), ['mes_calendario', 'dias_fijos'], true)
            ? (string) $row['modalidad']
            : 'mes_calendario';
        $days = in_array((int) ($row['cantidad_dias'] ?? 30), [30, 31], true)
            ? (int) $row['cantidad_dias']
            : 30;

        return ['modalidad' => $mode, 'cantidad_dias' => $days];
    } catch (PDOException $e) {
        error_log('No se pudo leer la configuración de renovación: ' . $e->getMessage());
        return $default;
    }
}

function afenixx_account_next_renewal_date(?string $currentDate, array $preference): array
{
    $today = new DateTimeImmutable('today');
    $current = null;

    if ($currentDate !== null && afenixx_account_validate_date($currentDate)) {
        $current = new DateTimeImmutable($currentDate);
    }

    $mode = (string) ($preference['modalidad'] ?? 'mes_calendario');
    $days = in_array((int) ($preference['cantidad_dias'] ?? 30), [30, 31], true)
        ? (int) $preference['cantidad_dias']
        : 30;

    if ($mode === 'dias_fijos') {
        // En días fijos, una cuenta vencida parte de hoy; una vigente parte de su fecha guardada.
        $base = ($current instanceof DateTimeImmutable && $current >= $today)
            ? $current
            : $today;
        $newDate = $base->modify('+' . $days . ' days');
    } else {
        // En mes calendario siempre se suma un mes a la fecha registrada, aunque ya esté vencida.
        // Ejemplo: 20/07 -> 20/08. Si no existe fecha, se toma hoy como base.
        $base = $current instanceof DateTimeImmutable ? $current : $today;
        $targetMonth = $base->modify('first day of next month');
        $targetDay = min((int) $base->format('d'), (int) $targetMonth->format('t'));
        $newDate = $targetMonth->setDate(
            (int) $targetMonth->format('Y'),
            (int) $targetMonth->format('m'),
            $targetDay
        );
        $mode = 'mes_calendario';
    }

    return [
        'previous' => $current?->format('Y-m-d'),
        'base' => $base->format('Y-m-d'),
        'new' => $newDate->format('Y-m-d'),
        'modalidad' => $mode,
        'cantidad_dias' => $mode === 'dias_fijos' ? $days : null,
    ];
}

function afenixx_account_platform_logo_url(
    mixed $value
): string {
    $url = trim((string) $value);

    if ($url === '') {
        return '';
    }

    if (
        preg_match(
            '~^(?:https?:)?//|^data:|^/~i',
            $url
        )
    ) {
        return $url;
    }

    $url = preg_replace(
        '~^(?:\./|\.\./)+~',
        '',
        $url
    ) ?? $url;

    return '../' . ltrim($url, '/');
}


function afenixx_account_renewal_data(array $row): array
{
    $renewalDate = trim((string) ($row['fecha_renovacion'] ?? ''));
    $client = trim((string) ($row['cliente_nombre'] ?? '')) ?: 'cliente';
    $platform = trim((string) ($row['plataforma_nombre'] ?? '')) ?: 'tu plataforma';
    $account = trim((string) ($row['correo'] ?? ''));

    if ($renewalDate === '' || !afenixx_account_validate_date($renewalDate)) {
        return [
            'key' => 'no_date',
            'label' => 'Sin fecha',
            'detail' => 'Pendiente de definir',
            'badge' => 'af-badge-neutral',
            'message' => '',
        ];
    }

    $renewal = new DateTimeImmutable($renewalDate);
    $today = new DateTimeImmutable('today');
    $days = (int) $today->diff($renewal)->format('%r%a');
    $date = $renewal->format('d/m/Y');

    if ($days < 0) {
        $key = 'expired';
        $label = 'Vencida';
        $detail = abs($days) === 1 ? 'Venció ayer' : 'Venció hace ' . abs($days) . ' días';
        $badge = 'af-badge-danger';
        $statusLine = '🔴 *Estado:* Vencida (' . $detail . ')';
        $closing = 'Tu acceso se encuentra vencido. Escríbenos para confirmar la renovación y restablecer la continuidad del servicio.';
    } elseif ($days === 0) {
        $key = 'today';
        $label = 'Vence hoy';
        $detail = 'Renovación inmediata';
        $badge = 'af-badge-danger';
        $statusLine = '🟠 *Estado:* Vence hoy';
        $closing = 'Tu acceso vence hoy. Confírmanos la renovación para mantener el servicio activo sin interrupciones.';
    } elseif ($days <= 3) {
        $key = 'soon3';
        $label = 'Urgente';
        $detail = $days === 1 ? 'Vence mañana' : "Vence en {$days} días";
        $badge = 'af-badge-warning';
        $statusLine = '🟡 *Estado:* ' . $detail;
        $closing = 'La fecha está muy próxima. Puedes confirmar la renovación desde ahora para evitar interrupciones.';
    } elseif ($days <= 7) {
        $key = 'soon7';
        $label = 'Por vencer';
        $detail = "Vence en {$days} días";
        $badge = 'af-badge-warning';
        $statusLine = '🟨 *Estado:* ' . $detail;
        $closing = 'Puedes confirmar la renovación con anticipación para dejarla preparada.';
    } elseif ($days <= 15) {
        $key = 'soon15';
        $label = 'Próxima';
        $detail = "Vence en {$days} días";
        $badge = 'af-badge-neutral';
        $statusLine = '🔵 *Estado:* ' . $detail;
        $closing = 'Te enviamos este recordatorio con anticipación para que puedas organizar la renovación.';
    } elseif ($days <= 30) {
        $key = 'soon30';
        $label = 'Programada';
        $detail = "Vence en {$days} días";
        $badge = 'af-badge-success';
        $statusLine = '🟢 *Estado:* ' . $detail;
        $closing = 'La renovación está programada dentro de los próximos 30 días.';
    } else {
        $key = 'future';
        $label = 'Vigente';
        $detail = "Vence en {$days} días";
        $badge = 'af-badge-success';
        $statusLine = '🟢 *Estado:* ' . $detail;
        $closing = 'Este es un recordatorio preventivo de la próxima fecha de renovación.';
    }

    $message = "🔔 *RECORDATORIO DE RENOVACIÓN*\n\n"
        . "Hola *{$client}*, esperamos que te encuentres bien.\n\n"
        . "📺 *Servicio:* {$platform}\n"
        . "📧 *Cuenta:* {$account}\n"
        . "📅 *Fecha de renovación:* {$date}\n"
        . $statusLine . "\n\n"
        . $closing . "\n\n"
        . "✅ *Para renovar:* responde a este mensaje y te ayudaremos de inmediato.\n\n"
        . "Gracias por confiar en nuestro servicio. 🙌";

    return compact('key', 'label', 'detail', 'badge', 'message');
}

$isExport = isset($_GET['action']) && $_GET['action'] === 'export_excel';
$isFetch = isset($_GET['fetch_edit_data']);
$isPost = $_SERVER['REQUEST_METHOD'] === 'POST';

if ($isExport || $isFetch || $isPost) {
    if (!tiene_permiso('gestionar_propias_cuentas')) {
        if (Security::expectsJson()) Security::json(['success' => false, 'message' => 'Acceso denegado.'], 403);
        header('Location: index.php?status=error&msg=' . urlencode('Acceso denegado.'));
        exit;
    }

    if ($isPost) {
        Security::headers(Security::expectsJson());
        Security::requireCsrf();
    }

    try {
        if (($_POST['action'] ?? '') === 'regenerate_account_id') {
            $id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT);
            if (!$id) throw new InvalidArgumentException('ID de cuenta no válido.');
            $newCode = strtoupper(substr(bin2hex(random_bytes(6)), 0, 10));
            $stmt = $pdo->prepare('UPDATE correos_servicios SET codigo_acceso_cuenta = ? WHERE id = ? AND id_admin_propietario = ?');
            $stmt->execute([$newCode, $id, $id_admin_actual]);
            if ($stmt->rowCount() < 1) throw new RuntimeException('La cuenta no existe o no tienes permiso para modificarla.');
            Security::json(['success' => true, 'message' => 'ID de cuenta regenerado.', 'codigo' => $newCode]);
        }

        if (($_POST['action'] ?? '') === 'delete_account') {
            $id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT);
            if (!$id) throw new InvalidArgumentException('ID de cuenta no válido.');
            $pdo->beginTransaction();
            $check = $pdo->prepare('SELECT id FROM correos_servicios WHERE id = ? AND id_admin_propietario = ? FOR UPDATE');
            $check->execute([$id, $id_admin_actual]);
            if (!$check->fetchColumn()) throw new RuntimeException('La cuenta no existe o no tienes permiso para eliminarla.');
            $pdo->prepare('DELETE FROM asociaciones WHERE id_correo_servicio = ? AND id_admin_propietario = ?')->execute([$id, $id_admin_actual]);
            $pdo->prepare('DELETE FROM correos_servicios WHERE id = ? AND id_admin_propietario = ?')->execute([$id, $id_admin_actual]);
            $pdo->commit();
            Security::json(['success' => true, 'message' => 'Cuenta eliminada con éxito.']);
        }

        if (($_POST['action'] ?? '') === 'save_renewal_config') {
            $mode = (string) ($_POST['modalidad'] ?? '');
            $days = (int) ($_POST['cantidad_dias'] ?? 30);

            if (!in_array($mode, ['mes_calendario', 'dias_fijos'], true)) {
                throw new InvalidArgumentException('Selecciona una modalidad de renovación válida.');
            }
            if (!in_array($days, [30, 31], true)) {
                throw new InvalidArgumentException('La renovación por días solo admite 30 o 31 días.');
            }

            $stmt = $pdo->prepare(
                'INSERT INTO configuracion_renovacion_admin
                    (id_admin, modalidad, cantidad_dias, fecha_actualizacion)
                 VALUES (?, ?, ?, NOW())
                 ON DUPLICATE KEY UPDATE
                    modalidad = VALUES(modalidad),
                    cantidad_dias = VALUES(cantidad_dias),
                    fecha_actualizacion = NOW()'
            );
            $stmt->execute([$id_admin_actual, $mode, $days]);

            Security::json([
                'success' => true,
                'message' => $mode === 'mes_calendario'
                    ? 'La renovación se configuró por mes calendario.'
                    : "La renovación se configuró por {$days} días.",
            ]);
        }

        if (($_POST['action'] ?? '') === 'renew_account') {
            $id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT);
            if (!$id) throw new InvalidArgumentException('ID de cuenta no válido.');

            $preference = afenixx_account_get_renewal_preference($pdo, $id_admin_actual);
            $pdo->beginTransaction();

            $stmt = $pdo->prepare(
                'SELECT id, correo, fecha_renovacion
                 FROM correos_servicios
                 WHERE id = ? AND id_admin_propietario = ?
                 FOR UPDATE'
            );
            $stmt->execute([$id, $id_admin_actual]);
            $account = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$account) {
                throw new RuntimeException('La cuenta no existe o no tienes permiso para renovarla.');
            }

            $calculation = afenixx_account_next_renewal_date(
                !empty($account['fecha_renovacion']) ? (string) $account['fecha_renovacion'] : null,
                $preference
            );

            $update = $pdo->prepare(
                'UPDATE correos_servicios
                 SET fecha_renovacion = ?
                 WHERE id = ? AND id_admin_propietario = ?'
            );
            $update->execute([$calculation['new'], $id, $id_admin_actual]);

            $history = $pdo->prepare(
                'INSERT INTO historial_renovaciones_cuenta
                    (id_admin, id_cuenta, fecha_anterior, fecha_base, fecha_nueva, modalidad, cantidad_dias, fecha_registro)
                 VALUES (?, ?, ?, ?, ?, ?, ?, NOW())'
            );
            $history->execute([
                $id_admin_actual,
                $id,
                $calculation['previous'],
                $calculation['base'],
                $calculation['new'],
                $calculation['modalidad'],
                $calculation['cantidad_dias'],
            ]);

            $pdo->commit();

            Security::json([
                'success' => true,
                'message' => 'Cuenta renovada correctamente.',
                'correo' => (string) $account['correo'],
                'fecha_anterior' => $calculation['previous'],
                'fecha_nueva' => $calculation['new'],
                'fecha_nueva_texto' => date('d/m/Y', strtotime($calculation['new'])),
            ]);
        }

        if ($isExport) {
            $platformId = filter_input(INPUT_GET, 'plataforma_filtro', FILTER_VALIDATE_INT);
            $clientId = filter_input(INPUT_GET, 'cliente_filtro', FILTER_VALIDATE_INT);
            $sql = "SELECT cs.correo, cs.codigo_acceso_cuenta, cs.ip, p.nombre AS plataforma_nombre, u.nombre_usuario AS cliente_nombre,
                           cs.fecha_renovacion, cs.fecha_creacion, d.nombre_dominio AS dominio_asociado
                    FROM correos_servicios cs
                    JOIN plataformas p ON cs.id_plataforma = p.id
                    LEFT JOIN dominios d ON cs.id_dominio = d.id
                    LEFT JOIN asociaciones assoc ON cs.id = assoc.id_correo_servicio AND assoc.id_admin_propietario = cs.id_admin_propietario
                    LEFT JOIN usuarios u ON assoc.id_usuario = u.id
                    WHERE cs.id_admin_propietario = ?";
            $params = [$id_admin_actual];
            if ($platformId) { $sql .= ' AND cs.id_plataforma = ?'; $params[] = $platformId; }
            if ($clientId) { $sql .= ' AND assoc.id_usuario = ?'; $params[] = $clientId; }
            $sql .= ' GROUP BY cs.id ORDER BY p.nombre ASC, cs.correo ASC';
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

            header('Content-Type: text/csv; charset=utf-8');
            header('Content-Disposition: attachment; filename=reporte-cuentas-' . date('Y-m-d') . '.csv');
            $out = fopen('php://output', 'w');
            if ($out === false) throw new RuntimeException('No se pudo generar el archivo CSV.');
            fputs($out, "\xEF\xBB\xBF");
            fputcsv($out, ['Correo', 'ID Acceso', 'IP / País', 'Plataforma', 'Cliente Asignado', 'Dominio', 'Fecha Renovación', 'Fecha Creación'], ';');
            foreach ($rows as $row) {
                fputcsv($out, [
                    $row['correo'],
                    $row['codigo_acceso_cuenta'] ?? 'N/A',
                    $row['ip'] ?? '',
                    $row['plataforma_nombre'],
                    $row['cliente_nombre'] ?? 'Sin Asignar',
                    $row['dominio_asociado'] ?? 'N/A',
                    !empty($row['fecha_renovacion']) ? date('d/m/Y', strtotime($row['fecha_renovacion'])) : '',
                    !empty($row['fecha_creacion']) ? date('d/m/Y', strtotime($row['fecha_creacion'])) : '',
                ], ';');
            }
            fclose($out);
            exit;
        }

        if ($isFetch) {
            $id = filter_input(INPUT_GET, 'fetch_edit_data', FILTER_VALIDATE_INT);
            if (!$id) Security::json(['success' => false, 'message' => 'ID inválido.'], 422);
            $stmt = $pdo->prepare("SELECT cs.id, cs.correo, cs.id_plataforma, cs.id_dominio, cs.fecha_renovacion, cs.ip,
                                          a.id_usuario AS id_cliente_asociado
                                   FROM correos_servicios cs
                                   LEFT JOIN asociaciones a ON a.id_correo_servicio = cs.id AND a.id_admin_propietario = cs.id_admin_propietario
                                   WHERE cs.id = ? AND cs.id_admin_propietario = ? LIMIT 1");
            $stmt->execute([$id, $id_admin_actual]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$row) Security::json(['success' => false, 'message' => 'Cuenta no encontrada.'], 404);
            $row['fecha_renovacion'] = !empty($row['fecha_renovacion']) ? date('Y-m-d', strtotime($row['fecha_renovacion'])) : '';
            $row['id_cliente_asociado'] = $row['id_cliente_asociado'] ?? '';
            Security::json(['success' => true, 'data' => $row]);
        }

        if (isset($_POST['guardar_cuentas_masivo_ajax'])) {
            $platformId = filter_input(INPUT_POST, 'id_plataforma', FILTER_VALIDATE_INT);
            $clientId = filter_input(INPUT_POST, 'id_cliente', FILTER_VALIDATE_INT) ?: null;
            $domainId = filter_input(INPUT_POST, 'id_dominio', FILTER_VALIDATE_INT);
            $defaultIp = afenixx_account_clean_ip_reference((string) ($_POST['ip'] ?? ''));
            if (!$platformId || !$domainId) throw new InvalidArgumentException('Plataforma y dominio son obligatorios.');
            afenixx_account_assert_domain($pdo, $domainId, $id_admin_actual);
            afenixx_account_assert_client($pdo, $clientId, $id_admin_actual);

            $lines = [];
            $manual = trim((string) ($_POST['cuentas_manual'] ?? ''));
            if ($manual !== '') $lines = preg_split('/\r\n|\r|\n/', $manual, -1, PREG_SPLIT_NO_EMPTY) ?: [];
            $lines = array_merge($lines, afenixx_account_read_txt_upload('archivo_txt'));
            $lines = array_values(array_unique(array_map('trim', $lines)));
            if (!$lines) throw new InvalidArgumentException('Debes pegar datos o subir un archivo TXT.');
            if (count($lines) > 1000) throw new InvalidArgumentException('La carga masiva admite hasta 1,000 líneas por operación.');

            $pdo->beginTransaction();
            $capacity = afenixx_account_capacity($pdo, $id_admin_actual);
            $added = $duplicates = $invalid = $missingDate = 0;
            foreach ($lines as $line) {
                $parsed = afenixx_account_parse_bulk_line($line, $defaultIp);
                if (empty($parsed['valid'])) { $invalid++; continue; }

                $email = (string) $parsed['email'];
                $renewalDate = $parsed['renewal_date'];
                $ipReference = $parsed['ip'];
                if ($clientId && $renewalDate === null) {
                    $missingDate++;
                    $invalid++;
                    continue;
                }

                $check = $pdo->prepare('SELECT id FROM correos_servicios WHERE correo = ? AND id_plataforma = ? AND id_admin_propietario = ?');
                $check->execute([$email, $platformId, $id_admin_actual]);
                $existingId = (int) $check->fetchColumn();
                if ($existingId > 0) {
                    $duplicates++;
                    $pdo->prepare('UPDATE correos_servicios SET id_dominio = ?, fecha_renovacion = ?, ip = ? WHERE id = ? AND id_admin_propietario = ?')
                        ->execute([$domainId, $renewalDate, $ipReference, $existingId, $id_admin_actual]);
                    $pdo->prepare('DELETE FROM asociaciones WHERE id_correo_servicio = ? AND id_admin_propietario = ?')
                        ->execute([$existingId, $id_admin_actual]);
                    if ($clientId) {
                        $pdo->prepare('INSERT INTO asociaciones (id_usuario, id_admin_propietario, correo, id_plataforma, id_correo_servicio) VALUES (?, ?, ?, ?, ?)')
                            ->execute([$clientId, $id_admin_actual, $email, $platformId, $existingId]);
                    }
                    continue;
                }

                afenixx_account_assert_capacity($capacity, $added + 1);
                $code = strtoupper(substr(bin2hex(random_bytes(5)), 0, 10));
                $stmt = $pdo->prepare('INSERT INTO correos_servicios (correo, id_plataforma, id_dominio, fecha_renovacion, id_admin_propietario, fecha_creacion, message_id, codigo_acceso_cuenta, ip) VALUES (?, ?, ?, ?, ?, NOW(), NULL, ?, ?)');
                $stmt->execute([$email, $platformId, $domainId, $renewalDate, $id_admin_actual, $code, $ipReference]);
                $accountId = (int) $pdo->lastInsertId();
                if ($clientId) {
                    $pdo->prepare('INSERT INTO asociaciones (id_usuario, id_admin_propietario, correo, id_plataforma, id_correo_servicio) VALUES (?, ?, ?, ?, ?)')
                        ->execute([$clientId, $id_admin_actual, $email, $platformId, $accountId]);
                }
                $added++;
            }
            $pdo->commit();

            $dateNote = $missingDate > 0
                ? "<br>Sin fecha para cliente asignado: {$missingDate}"
                : '';
            Security::json([
                'success' => true,
                'title' => 'Carga finalizada',
                'message' => "Proceso completado:<br>Agregadas: {$added}<br>Duplicadas/actualizadas: {$duplicates}<br>Inválidas: {$invalid}{$dateNote}.",
            ]);
        }

        if ($isPost && isset($_POST['guardar_cuentas']) && !isset($_POST['action'])) {
            $id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT);
            $platformId = filter_input(INPUT_POST, 'id_plataforma', FILTER_VALIDATE_INT);
            $clientId = filter_input(INPUT_POST, 'id_cliente', FILTER_VALIDATE_INT) ?: null;
            $domainId = filter_input(INPUT_POST, 'id_dominio', FILTER_VALIDATE_INT);
            $renewalDateRaw = trim((string) ($_POST['fecha_renovacion'] ?? ''));
            $renewalDate = $renewalDateRaw === '' ? null : afenixx_account_normalize_date($renewalDateRaw);
            $ipReference = afenixx_account_clean_ip_reference((string) ($_POST['ip'] ?? ''));
            $accountsRaw = trim((string) ($_POST['cuentas_manual'] ?? ''));

            if (!$platformId || !$domainId) {
                throw new InvalidArgumentException('Plataforma y dominio son obligatorios.');
            }
            if ($renewalDateRaw !== '' && $renewalDate === null) {
                throw new InvalidArgumentException('La fecha de renovación no es válida.');
            }
            if ($clientId && $renewalDate === null) {
                throw new InvalidArgumentException('La fecha de renovación es obligatoria cuando asignas la cuenta a un cliente.');
            }
            afenixx_account_assert_domain($pdo, $domainId, $id_admin_actual);
            afenixx_account_assert_client($pdo, $clientId, $id_admin_actual);

            $pdo->beginTransaction();
            $capacity = afenixx_account_capacity($pdo, $id_admin_actual);
            if ($id) {
                $email = filter_var($accountsRaw, FILTER_VALIDATE_EMAIL);
                if (!$email) throw new InvalidArgumentException('Ingresa un correo válido para editar la cuenta.');
                $owner = $pdo->prepare('SELECT COUNT(*) FROM correos_servicios WHERE id = ? AND id_admin_propietario = ?');
                $owner->execute([$id, $id_admin_actual]);
                if (!(int) $owner->fetchColumn()) throw new RuntimeException('La cuenta no existe o no tienes permiso para editarla.');
                $duplicate = $pdo->prepare('SELECT COUNT(*) FROM correos_servicios WHERE correo = ? AND id_plataforma = ? AND id_admin_propietario = ? AND id <> ?');
                $duplicate->execute([$email, $platformId, $id_admin_actual, $id]);
                if ((int) $duplicate->fetchColumn() > 0) throw new InvalidArgumentException('Ya existe una cuenta con ese correo en esta plataforma.');
                $pdo->prepare('UPDATE correos_servicios SET correo = ?, id_plataforma = ?, id_dominio = ?, fecha_renovacion = ?, ip = ? WHERE id = ? AND id_admin_propietario = ?')
                    ->execute([$email, $platformId, $domainId, $renewalDate, $ipReference, $id, $id_admin_actual]);
                $pdo->prepare('DELETE FROM asociaciones WHERE id_correo_servicio = ? AND id_admin_propietario = ?')->execute([$id, $id_admin_actual]);
                if ($clientId) {
                    $pdo->prepare('INSERT INTO asociaciones (id_usuario, id_admin_propietario, correo, id_plataforma, id_correo_servicio) VALUES (?, ?, ?, ?, ?)')
                        ->execute([$clientId, $id_admin_actual, $email, $platformId, $id]);
                }
                $message = 'Cuenta actualizada con éxito.';
            } else {
                $emails = preg_split('/[\s,;|\r\n]+/', $accountsRaw, -1, PREG_SPLIT_NO_EMPTY) ?: [];
                foreach (afenixx_account_read_txt_upload('archivo_txt') as $line) {
                    $emails = array_merge($emails, preg_split('/[\s,;|\r\n]+/', $line, -1, PREG_SPLIT_NO_EMPTY) ?: []);
                }
                $emails = array_values(array_unique(array_map('trim', $emails)));
                if (!$emails) throw new InvalidArgumentException('Debes ingresar al menos una cuenta o subir un archivo TXT.');
                if (count($emails) > 1000) throw new InvalidArgumentException('Solo puedes agregar hasta 1,000 cuentas por operación.');

                $added = 0;
                $duplicates = 0;
                foreach ($emails as $rawEmail) {
                    $email = filter_var($rawEmail, FILTER_VALIDATE_EMAIL);
                    if (!$email) continue;
                    $check = $pdo->prepare('SELECT COUNT(*) FROM correos_servicios WHERE correo = ? AND id_plataforma = ? AND id_admin_propietario = ?');
                    $check->execute([$email, $platformId, $id_admin_actual]);
                    if ((int) $check->fetchColumn() > 0) { $duplicates++; continue; }
                    afenixx_account_assert_capacity($capacity, $added + 1);
                    $code = strtoupper(substr(bin2hex(random_bytes(5)), 0, 10));
                    $stmt = $pdo->prepare('INSERT INTO correos_servicios (correo, id_plataforma, id_dominio, fecha_renovacion, id_admin_propietario, fecha_creacion, message_id, codigo_acceso_cuenta, ip) VALUES (?, ?, ?, ?, ?, NOW(), NULL, ?, ?)');
                    $stmt->execute([$email, $platformId, $domainId, $renewalDate, $id_admin_actual, $code, $ipReference]);
                    $accountId = (int) $pdo->lastInsertId();
                    if ($clientId) {
                        $pdo->prepare('INSERT INTO asociaciones (id_usuario, id_admin_propietario, correo, id_plataforma, id_correo_servicio) VALUES (?, ?, ?, ?, ?)')
                            ->execute([$clientId, $id_admin_actual, $email, $platformId, $accountId]);
                    }
                    $added++;
                }
                if ($added < 1) throw new InvalidArgumentException('No se encontró ninguna cuenta válida para agregar.');
                $message = $added === 1 ? '1 cuenta fue agregada.' : "{$added} cuentas fueron agregadas.";
                if ($duplicates > 0) $message .= " {$duplicates} duplicadas fueron omitidas.";
            }
            $pdo->commit();
            header('Location: gestionar_cuentas.php?status=success&msg=' . urlencode($message));
            exit;
        }

        if ($isPost && isset($_POST['action'])) {
            $action = (string) $_POST['action'];
            $selectedIds = afenixx_account_selected_ids($_POST['selected_cuentas'] ?? []);
            if (!$selectedIds) throw new InvalidArgumentException('No se seleccionaron cuentas.');
            $placeholders = implode(',', array_fill(0, count($selectedIds), '?'));
            $ownership = $pdo->prepare("SELECT COUNT(*) FROM correos_servicios WHERE id IN ({$placeholders}) AND id_admin_propietario = ?");
            $ownership->execute(array_merge($selectedIds, [$id_admin_actual]));
            if ((int) $ownership->fetchColumn() !== count($selectedIds)) throw new RuntimeException('Una o más cuentas no pertenecen a tu administración.');

            $pdo->beginTransaction();
            if ($action === 'delete_selected') {
                $pdo->prepare("DELETE FROM asociaciones WHERE id_correo_servicio IN ({$placeholders}) AND id_admin_propietario = ?")
                    ->execute(array_merge($selectedIds, [$id_admin_actual]));
                $stmt = $pdo->prepare("DELETE FROM correos_servicios WHERE id IN ({$placeholders}) AND id_admin_propietario = ?");
                $stmt->execute(array_merge($selectedIds, [$id_admin_actual]));
                $message = $stmt->rowCount() . ' cuentas eliminadas con éxito.';
            } elseif ($action === 'change_renewal_date') {
                $newDate = trim((string) ($_POST['batch_fecha_renovacion'] ?? ''));
                if (!afenixx_account_validate_date($newDate)) throw new InvalidArgumentException('Selecciona una fecha de renovación válida.');
                $stmt = $pdo->prepare("UPDATE correos_servicios SET fecha_renovacion = ? WHERE id IN ({$placeholders}) AND id_admin_propietario = ?");
                $stmt->execute(array_merge([$newDate], $selectedIds, [$id_admin_actual]));
                $message = 'Fecha actualizada para ' . $stmt->rowCount() . ' cuentas.';
            } elseif ($action === 'renew_selected') {
                $preference = afenixx_account_get_renewal_preference($pdo, $id_admin_actual);
                $read = $pdo->prepare(
                    "SELECT id, fecha_renovacion
                     FROM correos_servicios
                     WHERE id IN ({$placeholders}) AND id_admin_propietario = ?
                     FOR UPDATE"
                );
                $read->execute(array_merge($selectedIds, [$id_admin_actual]));
                $accountsToRenew = $read->fetchAll(PDO::FETCH_ASSOC);

                $update = $pdo->prepare(
                    'UPDATE correos_servicios SET fecha_renovacion = ?
                     WHERE id = ? AND id_admin_propietario = ?'
                );
                $history = $pdo->prepare(
                    'INSERT INTO historial_renovaciones_cuenta
                        (id_admin, id_cuenta, fecha_anterior, fecha_base, fecha_nueva, modalidad, cantidad_dias, fecha_registro)
                     VALUES (?, ?, ?, ?, ?, ?, ?, NOW())'
                );

                foreach ($accountsToRenew as $accountToRenew) {
                    $calculation = afenixx_account_next_renewal_date(
                        !empty($accountToRenew['fecha_renovacion']) ? (string) $accountToRenew['fecha_renovacion'] : null,
                        $preference
                    );
                    $update->execute([
                        $calculation['new'],
                        (int) $accountToRenew['id'],
                        $id_admin_actual,
                    ]);
                    $history->execute([
                        $id_admin_actual,
                        (int) $accountToRenew['id'],
                        $calculation['previous'],
                        $calculation['base'],
                        $calculation['new'],
                        $calculation['modalidad'],
                        $calculation['cantidad_dias'],
                    ]);
                }

                $message = count($accountsToRenew) . ' cuentas renovadas con la configuración actual.';
            } elseif ($action === 'change_client' || $action === 'change_client_only') {
                $newClientId = filter_input(INPUT_POST, 'batch_id_cliente', FILTER_VALIDATE_INT);
                if (!$newClientId) throw new InvalidArgumentException('Selecciona un cliente.');
                afenixx_account_assert_client($pdo, $newClientId, $id_admin_actual);
                $newDate = trim((string) ($_POST['batch_fecha_renovacion_cliente'] ?? ''));
                if ($newDate !== '' && !afenixx_account_validate_date($newDate)) throw new InvalidArgumentException('La nueva fecha no es válida.');
                if ($newDate !== '') {
                    $pdo->prepare("UPDATE correos_servicios SET fecha_renovacion = ? WHERE id IN ({$placeholders}) AND id_admin_propietario = ?")
                        ->execute(array_merge([$newDate], $selectedIds, [$id_admin_actual]));
                }
                $pdo->prepare("DELETE FROM asociaciones WHERE id_correo_servicio IN ({$placeholders}) AND id_admin_propietario = ?")
                    ->execute(array_merge($selectedIds, [$id_admin_actual]));
                $insert = $pdo->prepare("INSERT INTO asociaciones (id_usuario, id_admin_propietario, correo, id_plataforma, id_correo_servicio)
                                         SELECT ?, cs.id_admin_propietario, cs.correo, cs.id_plataforma, cs.id
                                         FROM correos_servicios cs
                                         WHERE cs.id IN ({$placeholders}) AND cs.id_admin_propietario = ?");
                $insert->execute(array_merge([$newClientId], $selectedIds, [$id_admin_actual]));
                $message = 'Cliente actualizado para ' . count($selectedIds) . ' cuentas.' . ($newDate !== '' ? ' También se actualizó la fecha.' : '');
            } else {
                throw new InvalidArgumentException('Acción no válida.');
            }
            $pdo->commit();
            Security::json(['success' => true, 'message' => $message]);
        }
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        error_log('Error en gestionar_cuentas: ' . $e->getMessage());
        $message = ($e instanceof InvalidArgumentException || $e instanceof RuntimeException)
            ? $e->getMessage()
            : 'No se pudo completar la operación.';
        if (Security::expectsJson()) Security::json(['success' => false, 'message' => $message], 422);
        header('Location: gestionar_cuentas.php?status=error&msg=' . urlencode($message));
        exit;
    }
}

// =======================================================================
// INICIO DE RENDERIZADO HTML
// =======================================================================
ob_start();
$titulo_pagina = 'Gestionar Cuentas';

if (!tiene_permiso('gestionar_propias_cuentas')) {
    header("Location: index.php?status=error&msg=" . urlencode("No tienes permiso para ver esta página."));
    exit;
}

$id_admin_actual = $_SESSION['admin']['uid'] ?? null;
$flash = ''; $flash_status = '';
if(isset($_GET['status'], $_GET['msg'])) { $flash = urldecode($_GET['msg']); $flash_status = $_GET['status']; }

$plataformas = $pdo->query("SELECT id, nombre FROM plataformas ORDER BY nombre")->fetchAll(PDO::FETCH_ASSOC);
$stmt_clients = $pdo->prepare("SELECT id, nombre_usuario FROM usuarios WHERE id_admin_propietario = ? ORDER BY nombre_usuario");
$stmt_clients->execute([$id_admin_actual]);
$clientes_dropdown = $stmt_clients->fetchAll(PDO::FETCH_ASSOC);
$stmt_dom = $pdo->prepare("SELECT id, nombre_dominio FROM dominios WHERE id_admin_propietario = ? ORDER BY nombre_dominio");
$stmt_dom->execute([$id_admin_actual]);
$dominios_disponibles = $stmt_dom->fetchAll(PDO::FETCH_ASSOC);

$renewalPreference = afenixx_account_get_renewal_preference($pdo, (int) $id_admin_actual);
$renewalPreferenceLabel = $renewalPreference['modalidad'] === 'dias_fijos'
    ? 'Cada ' . (int) $renewalPreference['cantidad_dias'] . ' días'
    : 'Un mes calendario';

$plataforma_filtro_id = null;
$cliente_filtro_id = null;
$filtro_vencidas = false; // Los filtros visuales unificados se aplican en el listado del navegador.

// CONSULTA MODIFICADA: Selecciona codigo_acceso_cuenta
$sql_cuentas = "SELECT cs.id, cs.correo, cs.codigo_acceso_cuenta, cs.ip, cs.id_plataforma,
                       assoc.id_usuario AS cliente_id, p.nombre AS plataforma_nombre, p.imagen_url AS plataforma_imagen,
                       u.nombre_usuario AS cliente_nombre, u.telefono AS cliente_telefono,
                       cs.fecha_creacion, cs.fecha_renovacion, d.nombre_dominio AS dominio_asociado
                FROM correos_servicios cs
                JOIN plataformas p ON cs.id_plataforma = p.id
                LEFT JOIN dominios d ON cs.id_dominio = d.id
                LEFT JOIN asociaciones assoc ON cs.id = assoc.id_correo_servicio
                LEFT JOIN usuarios u ON assoc.id_usuario = u.id
                WHERE cs.id_admin_propietario = ?";
$params = [$id_admin_actual];
if ($plataforma_filtro_id) { $sql_cuentas .= " AND cs.id_plataforma = ?"; $params[] = $plataforma_filtro_id; }
if ($cliente_filtro_id)    { $sql_cuentas .= " AND assoc.id_usuario = ?";  $params[] = $cliente_filtro_id; }
if ($filtro_vencidas)      { $sql_cuentas .= " AND cs.fecha_renovacion <= CURDATE()"; }
$sql_cuentas .= " GROUP BY cs.id ORDER BY p.nombre ASC, cs.correo ASC";
$st = $pdo->prepare($sql_cuentas);
$st->execute($params);
$cuentas = $st->fetchAll(PDO::FETCH_ASSOC);

$today = strtotime(date('Y-m-d'));
$expiredCount = 0; $dueSoonCount = 0; $unassignedCount = 0;
foreach ($cuentas as $row) {
    $renewal = !empty($row['fecha_renovacion']) ? strtotime((string) $row['fecha_renovacion']) : null;
    if ($renewal !== null && $renewal < $today) { $expiredCount++; }
    elseif ($renewal !== null && $renewal <= strtotime('+30 days', $today)) { $dueSoonCount++; }
    if (empty($row['cliente_id'])) { $unassignedCount++; }
}
$accountCapacity = afenixx_account_capacity($pdo, (int) $id_admin_actual);
$accountLimitLabel = $accountCapacity['limit'] === null
    ? 'Límite del plan: ilimitado'
    : 'Límite del plan: ' . number_format((int) $accountCapacity['limit']);
require_once 'admin_header.php';
?>
<style id="af-v8-account-platforms">
.af-platform-cell{display:inline-flex;align-items:center;gap:9px;min-width:0}.af-platform-cell strong{font-size:11px;color:#172033}.af-platform-logo{width:34px;height:34px;display:grid;place-items:center;flex:0 0 34px;overflow:hidden;border:1px solid #dfe7f1;border-radius:10px;background:#fff;box-shadow:0 5px 12px rgba(15,23,42,.07)}.af-platform-logo img{width:100%;height:100%;padding:3px;object-fit:contain}.af-platform-logo i{place-items:center;color:#dc2633;font-size:13px}
</style>
<div class="af-page">
    <header class="af-heading"><div class="af-heading-main"><span class="af-heading-icon"><i class="fas fa-envelope-open-text"></i></span><div><p class="af-eyebrow">Operación de accesos</p><h1>Cuentas</h1><p>Administra correos de plataformas, ID de acceso, clientes y fechas de renovación desde un solo listado.</p></div></div><div class="af-heading-actions"><button type="button" id="openAccountModal" class="af-btn af-btn-primary"><i class="fas fa-plus"></i> Nueva cuenta</button><button type="button" id="openBulkUploadModal" class="af-btn af-btn-success"><i class="fas fa-file-import"></i> Carga masiva</button><button type="button" id="exportAccounts" class="af-btn af-btn-secondary"><i class="fas fa-file-csv"></i> Exportar</button></div></header>
    <?php if ($flash !== ''): ?><div class="af-alert <?= $flash_status === 'success' ? 'af-alert-success' : 'af-alert-danger' ?>"><i class="fas <?= $flash_status === 'success' ? 'fa-circle-check' : 'fa-circle-exclamation' ?>"></i><div><?= htmlspecialchars($flash) ?></div></div><?php endif; ?>
    <section class="af-stats"><article class="af-stat"><span class="af-stat-icon"><i class="fas fa-envelope"></i></span><div><p class="af-stat-label">Cuentas de streaming</p><p class="af-stat-value"><?= number_format(count($cuentas)) ?></p><div class="af-stat-note"><?= htmlspecialchars($accountLimitLabel) ?></div></div></article><article class="af-stat"><span class="af-stat-icon"><i class="fas fa-circle-exclamation"></i></span><div><p class="af-stat-label">Vencidas</p><p class="af-stat-value"><?= number_format($expiredCount) ?></p><div class="af-stat-note">Requieren renovación</div></div></article><article class="af-stat"><span class="af-stat-icon"><i class="fas fa-calendar-day"></i></span><div><p class="af-stat-label">Próximas 30 días</p><p class="af-stat-value"><?= number_format($dueSoonCount) ?></p><div class="af-stat-note">Seguimiento preventivo</div></div></article><article class="af-stat"><span class="af-stat-icon"><i class="fas fa-user-slash"></i></span><div><p class="af-stat-label">Sin cliente</p><p class="af-stat-value"><?= number_format($unassignedCount) ?></p><div class="af-stat-note">Pendientes de asignación</div></div></article></section>
    <section class="af-panel">
        <div class="af-panel-head"><div class="af-panel-title"><span class="af-panel-title-icon"><i class="fas fa-list-check"></i></span><div><h2>Registros de cuentas</h2><p>Los filtros, acciones masivas, búsqueda y paginación se encuentran agrupados en este bloque.</p></div></div></div>
        <div class="af-toolbar af-v13-account-toolbar" id="afV13AccountToolbar"><div class="af-field af-v13-filter-search"><label class="af-label" for="accountSearch"><i class="fas fa-search"></i> Buscar</label><input id="accountSearch" type="search" class="af-control" placeholder="Correo, ID, IP/país, plataforma, dominio o cliente"></div>
            <div class="af-field af-v13-filter-platform"><label class="af-label" for="accountPlatformFilter"><i class="fas fa-tv"></i> Plataforma</label><select id="accountPlatformFilter" class="af-control"><option value="">Todas</option><?php foreach ($plataformas as $platform): ?><option value="<?= (int) $platform['id'] ?>"><?= htmlspecialchars((string) $platform['nombre']) ?></option><?php endforeach; ?></select></div>
            <div class="af-field af-v13-filter-client"><label class="af-label" for="accountClientFilter"><i class="fas fa-user"></i> Cliente</label><select id="accountClientFilter" class="af-control"><option value="">Todos</option><option value="none">Sin asignar</option><?php foreach ($clientes_dropdown as $client): ?><option value="<?= (int) $client['id'] ?>"><?= htmlspecialchars((string) $client['nombre_usuario']) ?></option><?php endforeach; ?></select></div>
            <div class="af-field af-v13-filter-renewal"><label class="af-label" for="accountRenewalFilter"><i class="fas fa-calendar-check"></i> Estado de renovación</label><select id="accountRenewalFilter" class="af-control" aria-label="Filtrar por estado de renovación"><option value="">🔎 Todos los estados</option><option value="expired">🔴 Vencidas</option><option value="today">🟠 Vencen hoy</option><option value="soon3">🟡 Vencen en 1–3 días</option><option value="soon7">🟨 Vencen en 4–7 días</option><option value="soon15">🔵 Vencen en 8–15 días</option><option value="soon30">🟢 Vencen en 16–30 días</option><option value="no_date">⚪ Sin fecha</option></select></div>
            <div class="af-field af-v13-filter-show"><label class="af-label" for="accountPerPage"><i class="fas fa-list-ol"></i> Mostrar</label><select id="accountPerPage" class="af-control"><option value="100">100 registros</option><option value="200">200 registros</option><option value="400">400 registros</option><option value="all">Todos</option></select></div>
            <div class="af-toolbar-actions af-v13-filter-clear"><button type="button" id="clearAccountFilters" class="af-btn af-btn-secondary"><i class="fas fa-eraser"></i> Limpiar</button></div>
        </div>
        <div class="af-bulk-bar" aria-label="Acciones principales de cuentas">
            <div class="af-bulk-controls">
                <span class="af-selected-counter"><i class="fas fa-bolt"></i>&nbsp; Acciones</span>
                <button type="button" id="openRenewalConfig" class="af-btn af-btn-secondary" title="Definir cómo se calcula una renovación"><i class="fas fa-sliders"></i> Configurar renovación</button>
                <span class="af-pagination-summary"><i class="fas fa-calendar-check"></i> Renovación: <strong><?= htmlspecialchars($renewalPreferenceLabel) ?></strong></span>
                <button type="button" id="bulkWhatsAppButton" class="af-btn af-btn-success" disabled title="Selecciona cuentas para generar avisos agrupados por cliente"><i class="fab fa-whatsapp"></i> Notificar seleccionadas</button>
                <button type="button" id="bulkRenewSelectedButton" class="af-btn af-btn-primary" disabled title="Renovar las cuentas seleccionadas"><i class="fas fa-calendar-plus"></i> Renovar seleccionadas</button>
            </div>
            <span class="af-pagination-summary">Selecciona una o más cuentas para habilitar las acciones masivas.</span>
        </div>
        <div class="af-bulk-bar"><div class="af-bulk-controls"><select id="bulkActionSelect" class="af-control" style="width:auto;min-width:230px"><option value="">Selecciona una acción masiva</option><option value="change_client">Cambiar cliente y fecha</option><option value="change_renewal_date">Cambiar fecha de renovación</option><option value="delete_selected">Eliminar cuentas seleccionadas</option></select><button type="button" id="applyBulkAction" class="af-btn af-btn-primary" disabled><i class="fas fa-play"></i> Aplicar acción</button><span id="selectedAccountCount" class="af-selected-counter">0 seleccionadas</span></div><span class="af-pagination-summary">Selecciona registros para habilitar una acción masiva.</span></div>
        <div class="af-list-meta"><span><?= number_format(count($cuentas)) ?> cuentas registradas</span><span id="accountPageSummary"></span></div>
        <div class="af-table-wrap"><table id="accountTable" class="af-table"><thead><tr><th><input type="checkbox" id="selectAllAccounts" aria-label="Seleccionar visibles"></th><th>Cuenta</th><th>ID acceso</th><th>IP / país</th><th>Plataforma</th><th>Dominio</th><th>Cliente</th><th>Renovación</th><th>Acciones</th></tr></thead><tbody>
        <?php foreach ($cuentas as $account): ?>
            <?php
            $renewalInfo = afenixx_account_renewal_data($account);
            $renewal = !empty($account['fecha_renovacion'])
                ? strtotime((string) $account['fecha_renovacion'])
                : null;
            $clientKey = !empty($account['cliente_id'])
                ? (string) $account['cliente_id']
                : 'none';
            $phone = preg_replace('/\D+/', '', (string) ($account['cliente_telefono'] ?? ''));
            $platformLogoUrl =
                afenixx_account_platform_logo_url(
                    $account['plataforma_imagen'] ?? ''
                );
            ?>
            <tr
                data-af-row
                data-platform="<?= (int) ($account['id_plataforma'] ?? 0) ?>"
                data-client="<?= htmlspecialchars($clientKey, ENT_QUOTES, 'UTF-8') ?>"
                data-renewal="<?= htmlspecialchars((string) $renewalInfo['key'], ENT_QUOTES, 'UTF-8') ?>"
                data-account-email="<?= htmlspecialchars((string) ($account['correo'] ?? ''), ENT_QUOTES, 'UTF-8') ?>"
                data-platform-name="<?= htmlspecialchars((string) ($account['plataforma_nombre'] ?? ''), ENT_QUOTES, 'UTF-8') ?>"
                data-client-name="<?= htmlspecialchars((string) ($account['cliente_nombre'] ?? 'Sin cliente asignado'), ENT_QUOTES, 'UTF-8') ?>"
                data-client-phone="<?= htmlspecialchars($phone, ENT_QUOTES, 'UTF-8') ?>"
                data-renewal-date="<?= htmlspecialchars(!empty($account['fecha_renovacion']) ? date('d/m/Y', strtotime((string) $account['fecha_renovacion'])) : 'Sin fecha', ENT_QUOTES, 'UTF-8') ?>"
                data-renewal-detail="<?= htmlspecialchars((string) ($renewalInfo['detail'] ?? 'Sin fecha'), ENT_QUOTES, 'UTF-8') ?>"
                data-search="<?= htmlspecialchars(
                    ($account['correo'] ?? '') . ' ' .
                    ($account['codigo_acceso_cuenta'] ?? '') . ' ' .
                    ($account['plataforma_nombre'] ?? '') . ' ' .
                    ($account['dominio_asociado'] ?? '') . ' ' .
                    ($account['cliente_nombre'] ?? '') . ' ' .
                    ($account['ip'] ?? ''),
                    ENT_QUOTES,
                    'UTF-8'
                ) ?>"
            >
                <td><input type="checkbox" class="js-account-check" value="<?= (int) $account['id'] ?>"></td>
                <td>
                    <span class="af-row-title"><?= htmlspecialchars((string) $account['correo']) ?></span>
                    <span class="af-row-sub">Creada: <?= !empty($account['fecha_creacion']) ? date('d/m/Y', strtotime((string) $account['fecha_creacion'])) : '—' ?></span>
                </td>
                <td>
                    <span class="af-code">
                        <?= htmlspecialchars((string) ($account['codigo_acceso_cuenta'] ?? '—')) ?>
                        <button type="button" class="js-copy-account" data-code="<?= htmlspecialchars((string) ($account['codigo_acceso_cuenta'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" title="Copiar ID">
                            <i class="fas fa-copy"></i>
                        </button>
                    </span>
                </td>
                <td><?= htmlspecialchars((string) ($account['ip'] ?? '—')) ?></td>
                <td>
                    <span class="af-platform-cell">
                        <span class="af-platform-logo">
                            <?php if ($platformLogoUrl !== ''): ?>
                                <img
                                    src="<?= htmlspecialchars($platformLogoUrl, ENT_QUOTES, 'UTF-8') ?>"
                                    alt="<?= htmlspecialchars((string) $account['plataforma_nombre'], ENT_QUOTES, 'UTF-8') ?>"
                                    loading="lazy"
                                    onerror="this.style.display='none';this.nextElementSibling.style.display='grid'"
                                >
                                <i class="fas fa-tv" style="display:none"></i>
                            <?php else: ?>
                                <i class="fas fa-tv"></i>
                            <?php endif; ?>
                        </span>
                        <strong><?= htmlspecialchars((string) $account['plataforma_nombre']) ?></strong>
                    </span>
                </td>
                <td><?= htmlspecialchars((string) ($account['dominio_asociado'] ?? '—')) ?></td>
                <td>
                    <span class="af-row-title"><?= htmlspecialchars((string) ($account['cliente_nombre'] ?? 'Sin asignar')) ?></span>
                    <?php if ($phone !== ''): ?><span class="af-row-sub">+<?= htmlspecialchars($phone) ?></span><?php endif; ?>
                </td>
                <td>
                    <?php if ($renewal !== null): ?>
                        <div class="af-status-stack">
                            <span class="af-badge <?= htmlspecialchars((string) $renewalInfo['badge']) ?>">
                                <?= date('d/m/Y', $renewal) ?>
                            </span>
                            <span class="af-status-detail"><?= htmlspecialchars((string) $renewalInfo['detail']) ?></span>
                        </div>
                    <?php else: ?>
                        <span class="af-badge af-badge-neutral">Sin fecha</span>
                    <?php endif; ?>
                </td>
                <td>
                    <div class="af-actions af-actions-center">
                        <button type="button" class="af-btn af-btn-secondary af-btn-icon js-edit-account" data-id="<?= (int) $account['id'] ?>" title="Editar cuenta">
                            <i class="fas fa-pen"></i>
                        </button>
                        <?php if ($phone !== '' && $renewalInfo['message'] !== ''): ?>
                            <button
                                type="button"
                                class="af-btn af-btn-success af-btn-icon js-renewal-whatsapp"
                                data-phone="<?= htmlspecialchars($phone, ENT_QUOTES, 'UTF-8') ?>"
                                data-client="<?= htmlspecialchars((string) ($account['cliente_nombre'] ?? 'Cliente'), ENT_QUOTES, 'UTF-8') ?>"
                                data-message="<?= htmlspecialchars((string) $renewalInfo['message'], ENT_QUOTES, 'UTF-8') ?>"
                                title="Revisar, copiar o enviar recordatorio por WhatsApp"
                            >
                                <i class="fab fa-whatsapp"></i>
                            </button>
                        <?php else: ?>
                            <button type="button" class="af-btn af-btn-success af-btn-icon" disabled title="<?= $phone === '' ? 'El cliente no tiene WhatsApp registrado' : 'La cuenta no tiene fecha de renovación' ?>">
                                <i class="fab fa-whatsapp"></i>
                            </button>
                        <?php endif; ?>
                        <button
                            type="button"
                            class="af-btn af-btn-primary af-btn-icon js-renew-account"
                            data-id="<?= (int) $account['id'] ?>"
                            data-email="<?= htmlspecialchars((string) $account['correo'], ENT_QUOTES, 'UTF-8') ?>"
                            data-current-date="<?= htmlspecialchars((string) ($account['fecha_renovacion'] ?? ''), ENT_QUOTES, 'UTF-8') ?>"
                            title="Renovar con la configuración actual"
                        >
                            <i class="fas fa-calendar-plus"></i>
                        </button>
                        <button type="button" class="af-btn af-btn-warning af-btn-icon js-regenerate-account" data-id="<?= (int) $account['id'] ?>" title="Regenerar ID">
                            <i class="fas fa-rotate"></i>
                        </button>
                        <button type="button" class="af-btn af-btn-danger af-btn-icon js-delete-account" data-id="<?= (int) $account['id'] ?>" data-email="<?= htmlspecialchars((string) $account['correo'], ENT_QUOTES, 'UTF-8') ?>" title="Eliminar">
                            <i class="fas fa-trash"></i>
                        </button>
                    </div>
                </td>
            </tr>
        <?php endforeach; ?>
        </tbody></table><div id="accountEmpty" class="af-empty" hidden><i class="fas fa-envelope-open-text"></i><strong>No se encontraron cuentas</strong><div>Modifica los filtros o registra una cuenta nueva.</div></div></div>
        <div class="af-pagination-bar"><span class="af-pagination-summary">Selecciona filas visibles para usar las acciones masivas.</span><div id="accountPagination" class="af-pagination"></div></div>
    </section>
</div>

<div id="accountModal" class="af-modal-backdrop" aria-hidden="true"><section class="af-modal" role="dialog" aria-modal="true"><header class="af-modal-head"><div class="af-modal-title"><span class="af-modal-icon"><i class="fas fa-envelope"></i></span><div><h2 id="accountModalTitle">Nueva cuenta</h2><p id="accountModalDescription">Agrega una o varias cuentas a una plataforma y dominio.</p></div></div><button type="button" class="af-modal-close" data-af-close><i class="fas fa-xmark"></i></button></header><form id="accountForm" method="POST" enctype="multipart/form-data"><?= Security::csrfField() ?><input type="hidden" name="id" id="accountId"><input type="hidden" name="guardar_cuentas" value="1"><div class="af-modal-body"><div class="af-guide"><i class="fas fa-circle-info"></i><div>Para una cuenta nueva puedes pegar varios correos separados por salto de línea o coma. Al editar, utiliza un solo correo.</div></div><div class="af-form-grid"><div><label class="af-label" for="accountPlatform"><i class="fas fa-tv"></i> Plataforma *</label><select id="accountPlatform" name="id_plataforma" class="af-control" required><option value="">Seleccionar</option><?php foreach($plataformas as $platform): ?><option value="<?= (int)$platform['id'] ?>"><?= htmlspecialchars((string)$platform['nombre']) ?></option><?php endforeach; ?></select><span class="af-help">Plataforma cuyos mensajes buscará el sistema.</span></div><div><label class="af-label" for="accountDomain"><i class="fas fa-at"></i> Dominio *</label><select id="accountDomain" name="id_dominio" class="af-control" required><option value="">Seleccionar</option><?php foreach($dominios_disponibles as $domain): ?><option value="<?= (int)$domain['id'] ?>"><?= htmlspecialchars((string)$domain['nombre_dominio']) ?></option><?php endforeach; ?></select><span class="af-help">Determina el buzón y los asuntos configurados.</span></div><div><label class="af-label" for="accountClient"><i class="fas fa-user"></i> Cliente</label><select id="accountClient" name="id_cliente" class="af-control"><option value="">Sin asignar</option><?php foreach($clientes_dropdown as $client): ?><option value="<?= (int)$client['id'] ?>"><?= htmlspecialchars((string)$client['nombre_usuario']) ?></option><?php endforeach; ?></select><span class="af-help">Puedes asignarlo después mediante una acción masiva.</span></div><div><label class="af-label" for="accountRenewal"><i class="fas fa-calendar"></i> Renovación<span id="accountRenewalRequiredMark" hidden> *</span></label><input id="accountRenewal" name="fecha_renovacion" type="date" class="af-control"><span id="accountRenewalHelp" class="af-help">Opcional mientras la cuenta permanezca sin cliente.</span></div><div><label class="af-label" for="accountIp"><i class="fas fa-location-dot"></i> IP / país</label><input id="accountIp" name="ip" type="text" maxlength="100" class="af-control" placeholder="PE, Perú o 192.0.2.10"><span class="af-help">Dato opcional. Puede ser una IP, un país o una abreviatura.</span></div><div class="full"><label class="af-label" for="accountEmails"><i class="fas fa-envelope"></i> Cuenta(s) *</label><textarea id="accountEmails" name="cuentas_manual" class="af-control" required placeholder="cuenta1@dominio.com&#10;cuenta2@dominio.com"></textarea><span class="af-help">Acepta correos separados por salto de línea, coma, espacio o punto y coma.</span></div><div id="accountFileSection" class="full"><label class="af-label" for="accountFile"><i class="fas fa-file-lines"></i> Archivo TXT opcional</label><input id="accountFile" name="archivo_txt" type="file" accept=".txt,text/plain" class="af-control"><span class="af-help">Máximo 1 MB; un correo por línea. No se utiliza al editar.</span></div></div></div><footer class="af-modal-footer"><button type="button" class="af-btn af-btn-secondary" data-af-close>Cancelar</button><button type="submit" class="af-btn af-btn-primary"><i class="fas fa-floppy-disk"></i> Guardar cuenta</button></footer></form></section></div>

<div id="bulkUploadModal" class="af-modal-backdrop" aria-hidden="true"><section class="af-modal" role="dialog" aria-modal="true"><header class="af-modal-head"><div class="af-modal-title"><span class="af-modal-icon"><i class="fas fa-file-import"></i></span><div><h2>Carga masiva</h2><p>Importa cuentas con fecha e IP/país opcionales según su asignación.</p></div></div><button type="button" class="af-modal-close" data-af-close><i class="fas fa-xmark"></i></button></header><form id="bulkUploadForm" enctype="multipart/form-data"><?= Security::csrfField() ?><input type="hidden" name="guardar_cuentas_masivo_ajax" value="1"><div class="af-modal-body"><div class="af-guide"><i class="fas fa-file-lines"></i><div><strong>Sin cliente:</strong> basta con el correo. <strong>Con cliente:</strong> agrega fecha. Formato recomendado: <strong>correo@dominio.com | 2026-12-31 | PE</strong>. Para dejar la fecha vacía y registrar solo IP/país: <strong>correo@dominio.com | | PE</strong>.</div></div><div class="af-form-grid"><div><label class="af-label" for="bulkPlatform">Plataforma *</label><select id="bulkPlatform" name="id_plataforma" class="af-control" required><option value="">Seleccionar</option><?php foreach($plataformas as $platform): ?><option value="<?= (int)$platform['id'] ?>"><?= htmlspecialchars((string)$platform['nombre']) ?></option><?php endforeach; ?></select></div><div><label class="af-label" for="bulkDomain">Dominio *</label><select id="bulkDomain" name="id_dominio" class="af-control" required><option value="">Seleccionar</option><?php foreach($dominios_disponibles as $domain): ?><option value="<?= (int)$domain['id'] ?>"><?= htmlspecialchars((string)$domain['nombre_dominio']) ?></option><?php endforeach; ?></select></div><div class="full"><label class="af-label" for="bulkClient">Cliente</label><select id="bulkClient" name="id_cliente" class="af-control"><option value="">Sin asignar</option><?php foreach($clientes_dropdown as $client): ?><option value="<?= (int)$client['id'] ?>"><?= htmlspecialchars((string)$client['nombre_usuario']) ?></option><?php endforeach; ?></select><span id="bulkClientDateHelp" class="af-help">Sin cliente, la fecha de renovación es opcional.</span></div><div class="full"><label class="af-label" for="bulkIp"><i class="fas fa-location-dot"></i> IP / país común</label><input id="bulkIp" name="ip" type="text" maxlength="100" class="af-control" placeholder="PE, Perú o 192.0.2.10"><span class="af-help">Opcional. Se aplicará a las líneas que no indiquen un valor propio.</span></div><div class="full"><label class="af-label" for="bulkAccounts">Cuentas, fechas e IP/país</label><textarea id="bulkAccounts" name="cuentas_manual" class="af-control" placeholder="cuenta1@dominio.com | 2026-12-31 | PE&#10;cuenta2@dominio.com | | Perú"></textarea></div><div class="full"><label class="af-label" for="bulkFile">Archivo TXT</label><input id="bulkFile" name="archivo_txt" type="file" accept=".txt,text/plain" class="af-control"></div></div></div><footer class="af-modal-footer"><button type="button" class="af-btn af-btn-secondary" data-af-close>Cancelar</button><button type="submit" class="af-btn af-btn-success"><i class="fas fa-file-import"></i> Cargar cuentas</button></footer></form></section></div>

<div id="bulkActionModal" class="af-modal-backdrop" aria-hidden="true"><section class="af-modal af-modal-sm" role="dialog" aria-modal="true"><header class="af-modal-head"><div class="af-modal-title"><span class="af-modal-icon"><i class="fas fa-list-check"></i></span><div><h2 id="bulkActionTitle">Acción masiva</h2><p id="bulkActionDescription">Aplica el cambio a las cuentas seleccionadas.</p></div></div><button type="button" class="af-modal-close" data-af-close><i class="fas fa-xmark"></i></button></header><form id="bulkActionForm"><?= Security::csrfField() ?><input type="hidden" name="action" id="bulkActionName"><div class="af-modal-body"><div id="bulkActionContent"></div></div><footer class="af-modal-footer"><button type="button" class="af-btn af-btn-secondary" data-af-close>Cancelar</button><button type="submit" class="af-btn af-btn-primary"><i class="fas fa-check"></i> Aplicar cambios</button></footer></form></section></div>

<div id="bulkWhatsAppModal" class="af-modal-backdrop" aria-hidden="true">
    <section class="af-modal" role="dialog" aria-modal="true" aria-labelledby="bulkWhatsAppTitle" style="width:min(880px,94vw)">
        <header class="af-modal-head">
            <div class="af-modal-title">
                <span class="af-modal-icon" style="background:#e8f8f1;color:#169c68"><i class="fab fa-whatsapp"></i></span>
                <div>
                    <h2 id="bulkWhatsAppTitle">Notificaciones de WhatsApp</h2>
                    <p>Las cuentas seleccionadas se agrupan por cliente para enviar un solo mensaje profesional por usuario.</p>
                </div>
            </div>
            <button type="button" class="af-modal-close" data-af-close aria-label="Cerrar"><i class="fas fa-xmark"></i></button>
        </header>
        <div class="af-modal-body">
            <div class="af-guide"><i class="fas fa-users"></i><div><strong>Mensajes agrupados:</strong> cada cliente recibe un resumen con sus cuentas seleccionadas, fechas y estado de renovación. Revisa cada mensaje antes de copiarlo o abrir WhatsApp.</div></div>
            <div id="bulkWhatsAppGroups" style="display:grid;gap:14px"></div>
        </div>
        <footer class="af-modal-footer">
            <button type="button" class="af-btn af-btn-secondary" data-af-close><i class="fas fa-xmark"></i> Cerrar</button>
            <button type="button" id="copyAllBulkMessages" class="af-btn af-btn-primary"><i class="fas fa-copy"></i> Copiar todos</button>
        </footer>
    </section>
</div>

<div id="renewalConfigModal" class="af-modal-backdrop" aria-hidden="true">
    <section class="af-modal af-modal-sm" role="dialog" aria-modal="true" aria-labelledby="renewalConfigTitle">
        <header class="af-modal-head">
            <div class="af-modal-title">
                <span class="af-modal-icon"><i class="fas fa-calendar-check"></i></span>
                <div>
                    <h2 id="renewalConfigTitle">Regla de renovación</h2>
                    <p>Define cómo se calculará la próxima fecha al usar el botón Renovar.</p>
                </div>
            </div>
            <button type="button" class="af-modal-close" data-af-close aria-label="Cerrar"><i class="fas fa-xmark"></i></button>
        </header>
        <form id="renewalConfigForm">
            <?= Security::csrfField() ?>
            <input type="hidden" name="action" value="save_renewal_config">
            <div class="af-modal-body">
                <div class="af-guide"><i class="fas fa-circle-info"></i><div>En mes calendario siempre se suma un mes a la fecha de vencimiento guardada, aunque ya esté vencida: 20/07 pasa a 20/08. Si no existe fecha, se toma hoy. En días fijos, una cuenta vencida parte de hoy.</div></div>
                <div class="af-form-grid">
                    <div class="full">
                        <label class="af-label" for="renewalMode"><i class="fas fa-repeat"></i> Forma de renovar *</label>
                        <select id="renewalMode" name="modalidad" class="af-control" required>
                            <option value="mes_calendario" <?= $renewalPreference['modalidad'] === 'mes_calendario' ? 'selected' : '' ?>>Un mes calendario</option>
                            <option value="dias_fijos" <?= $renewalPreference['modalidad'] === 'dias_fijos' ? 'selected' : '' ?>>Cantidad fija de días</option>
                        </select>
                        <span class="af-help">Mes calendario mantiene el día y suma un mes desde la fecha guardada, incluso si está vencida: 20/07 pasa a 20/08. En fin de mes se ajusta al último día disponible.</span>
                    </div>
                    <div class="full" id="renewalDaysField">
                        <label class="af-label" for="renewalDays"><i class="fas fa-calendar-days"></i> Días por renovación</label>
                        <select id="renewalDays" name="cantidad_dias" class="af-control">
                            <option value="30" <?= (int) $renewalPreference['cantidad_dias'] === 30 ? 'selected' : '' ?>>30 días</option>
                            <option value="31" <?= (int) $renewalPreference['cantidad_dias'] === 31 ? 'selected' : '' ?>>31 días</option>
                        </select>
                        <span class="af-help">Solo se utiliza cuando eliges cantidad fija de días.</span>
                    </div>
                </div>
            </div>
            <footer class="af-modal-footer">
                <button type="button" class="af-btn af-btn-secondary" data-af-close>Cancelar</button>
                <button type="submit" class="af-btn af-btn-primary"><i class="fas fa-floppy-disk"></i> Guardar configuración</button>
            </footer>
        </form>
    </section>
</div>

<script>
document.addEventListener('DOMContentLoaded', () => {
    const list = new AfenixAdmin.UnifiedList({
        table: '#accountTable',
        search: '#accountSearch',
        perPage: '#accountPerPage',
        filters: [
            {selector: '#accountPlatformFilter', data: 'platform'},
            {selector: '#accountClientFilter', data: 'client'},
            {selector: '#accountRenewalFilter', data: 'renewal'}
        ],
        summary: '#accountPageSummary',
        pagination: '#accountPagination',
        empty: '#accountEmpty'
    });

    document.getElementById('clearAccountFilters').addEventListener('click', () => {
        ['accountSearch', 'accountPlatformFilter', 'accountClientFilter', 'accountRenewalFilter']
            .forEach((id) => { document.getElementById(id).value = ''; });
        document.getElementById('accountPerPage').value = '100';
        list.currentPage = 1;
        list.render();
    });

    const accountModal = new AfenixAdmin.ModalController('#accountModal');
    const uploadModal = new AfenixAdmin.ModalController('#bulkUploadModal');
    const actionModal = new AfenixAdmin.ModalController('#bulkActionModal');
    const bulkWhatsAppModal = new AfenixAdmin.ModalController('#bulkWhatsAppModal');
    const renewalConfigModal = new AfenixAdmin.ModalController('#renewalConfigModal');
    const csrf = <?= json_encode(Security::csrfToken()) ?>;
    const renewalRuleLabel = <?= json_encode($renewalPreferenceLabel, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE) ?>;
    const clients = <?= json_encode(array_map(
        static fn(array $client): array => [
            'id' => (int) $client['id'],
            'name' => (string) $client['nombre_usuario'],
        ],
        $clientes_dropdown
    ), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;

    const accountForm = document.getElementById('accountForm');
    const uploadForm = document.getElementById('bulkUploadForm');
    const actionForm = document.getElementById('bulkActionForm');
    const renewalConfigForm = document.getElementById('renewalConfigForm');
    const accountClient = document.getElementById('accountClient');
    const accountRenewal = document.getElementById('accountRenewal');
    const bulkClient = document.getElementById('bulkClient');
    const renewalMode = document.getElementById('renewalMode');
    const renewalDays = document.getElementById('renewalDays');
    const renewalDaysField = document.getElementById('renewalDaysField');

    const escapePreviewHtml = (value) => String(value ?? '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');

    const formatWhatsAppPreview = (message) => escapePreviewHtml(message)
        .replace(/\*([^*\n]+)\*/g, '<strong>$1</strong>')
        .replace(/\n/g, '<br>');

    const copyMessage = async (message) => {
        if (navigator.clipboard?.writeText) {
            await navigator.clipboard.writeText(message);
            return;
        }
        const area = document.createElement('textarea');
        area.value = message;
        area.style.position = 'fixed';
        area.style.opacity = '0';
        document.body.appendChild(area);
        area.select();
        document.execCommand('copy');
        area.remove();
    };

    function syncAccountRenewalRequirement() {
        const required = accountClient.value !== '';
        accountRenewal.required = required;
        document.getElementById('accountRenewalRequiredMark').hidden = !required;
        document.getElementById('accountRenewalHelp').textContent = required
            ? 'Obligatoria porque la cuenta está asignada a un cliente.'
            : 'Opcional mientras la cuenta permanezca sin cliente.';
    }

    function syncBulkDateHelp() {
        document.getElementById('bulkClientDateHelp').textContent = bulkClient.value !== ''
            ? 'Cada línea debe incluir una fecha válida porque se asignará a un cliente.'
            : 'Sin cliente, la fecha de renovación es opcional.';
    }

    function syncRenewalMode() {
        const fixedDays = renewalMode.value === 'dias_fijos';
        renewalDays.disabled = !fixedDays;
        renewalDaysField.style.opacity = fixedDays ? '1' : '.55';
    }

    accountClient.addEventListener('change', syncAccountRenewalRequirement);
    bulkClient.addEventListener('change', syncBulkDateHelp);
    renewalMode.addEventListener('change', syncRenewalMode);
    syncRenewalMode();

    const bulkActionSelect = document.getElementById('bulkActionSelect');
    const applyBulkAction = document.getElementById('applyBulkAction');
    const bulkWhatsAppButton = document.getElementById('bulkWhatsAppButton');
    const bulkRenewSelectedButton = document.getElementById('bulkRenewSelectedButton');
    const bulkWhatsAppGroups = document.getElementById('bulkWhatsAppGroups');
    const copyAllBulkMessages = document.getElementById('copyAllBulkMessages');
    const selectAllAccounts = document.getElementById('selectAllAccounts');
    let bulkNotificationGroups = [];

    function selectedIds() {
        return [...document.querySelectorAll('.js-account-check:checked')]
            .map((element) => element.value);
    }

    function selectedRows() {
        return [...document.querySelectorAll('.js-account-check:checked')]
            .map((element) => element.closest('tr[data-af-row]'))
            .filter(Boolean);
    }

    function updateSelected() {
        const count = selectedIds().length;
        document.getElementById('selectedAccountCount').textContent =
            `${count} seleccionada${count === 1 ? '' : 's'}`;
        applyBulkAction.disabled = count === 0 || bulkActionSelect.value === '';
        bulkWhatsAppButton.disabled = count === 0;
        bulkRenewSelectedButton.disabled = count === 0;

        const visibleChecks = [...document.querySelectorAll(
            '#accountTable tbody tr[data-af-row]:not([hidden]) .js-account-check'
        )];
        const visibleSelected = visibleChecks.filter((check) => check.checked).length;
        selectAllAccounts.checked =
            visibleChecks.length > 0 && visibleSelected === visibleChecks.length;
        selectAllAccounts.indeterminate =
            visibleSelected > 0 && visibleSelected < visibleChecks.length;
    }

    selectAllAccounts.addEventListener('change', function () {
        document.querySelectorAll(
            '#accountTable tbody tr[data-af-row]:not([hidden]) .js-account-check'
        ).forEach((check) => { check.checked = this.checked; });
        updateSelected();
    });

    document.getElementById('accountTable').addEventListener('change', (event) => {
        if (event.target.matches('.js-account-check')) updateSelected();
    });
    bulkActionSelect.addEventListener('change', updateSelected);
    document.getElementById('accountTable').addEventListener('af:list-rendered', updateSelected);
    updateSelected();

    function resetAccount(data = null) {
        accountForm.reset();
        document.getElementById('accountId').value = '';
        document.getElementById('accountFileSection').hidden = Boolean(data);
        document.getElementById('accountModalTitle').textContent =
            data ? 'Editar cuenta' : 'Nueva cuenta';
        document.getElementById('accountModalDescription').textContent = data
            ? 'Actualiza la plataforma, dominio, cliente, renovación o IP/país.'
            : 'Agrega una o varias cuentas a una plataforma y dominio.';

        if (data) {
            document.getElementById('accountId').value = data.id;
            document.getElementById('accountEmails').value = data.correo || '';
            document.getElementById('accountPlatform').value = data.id_plataforma || '';
            document.getElementById('accountDomain').value = data.id_dominio || '';
            document.getElementById('accountClient').value = data.id_cliente_asociado || '';
            document.getElementById('accountRenewal').value = data.fecha_renovacion || '';
            document.getElementById('accountIp').value = data.ip || '';
        }

        syncAccountRenewalRequirement();
    }

    document.getElementById('openAccountModal').addEventListener('click', function () {
        resetAccount();
        accountModal.open(this);
    });

    document.getElementById('openBulkUploadModal').addEventListener('click', function () {
        uploadForm.reset();
        syncBulkDateHelp();
        uploadModal.open(this);
    });

    document.getElementById('openRenewalConfig').addEventListener('click', function () {
        syncRenewalMode();
        renewalConfigModal.open(this);
    });

    function buildGroupedNotifications() {
        const groups = new Map();

        selectedRows().forEach((row) => {
            const clientKey = row.dataset.client && row.dataset.client !== 'none'
                ? row.dataset.client
                : 'none';
            const clientName = row.dataset.clientName || 'Sin cliente asignado';
            const phone = (row.dataset.clientPhone || '').replace(/\D+/g, '');

            if (!groups.has(clientKey)) {
                groups.set(clientKey, {
                    key: clientKey,
                    clientName,
                    phone,
                    accounts: []
                });
            }

            groups.get(clientKey).accounts.push({
                email: row.dataset.accountEmail || 'Cuenta sin correo',
                platform: row.dataset.platformName || 'Plataforma',
                renewalDate: row.dataset.renewalDate || 'Sin fecha',
                renewalDetail: row.dataset.renewalDetail || 'Sin estado',
                renewalKey: row.dataset.renewal || 'no_date'
            });
        });

        return [...groups.values()].map((group) => {
            const accountBlocks = group.accounts.map((account, index) => {
                const statusIconMap = {
                    expired: '🔴',
                    today: '🟠',
                    soon3: '🟡',
                    soon7: '🟨',
                    soon15: '🔵',
                    soon30: '🟢',
                    no_date: '⚪',
                    future: '🟢'
                };
                const statusIcon = statusIconMap[account.renewalKey] || '🟢';

                return [
                    `*Cuenta ${index + 1}*`,
                    `📺 *Servicio:* ${account.platform}`,
                    `📧 *Cuenta:* ${account.email}`,
                    `📅 *Renovación:* ${account.renewalDate}`,
                    `${statusIcon} *Estado:* ${account.renewalDetail}`
                ].join('\n');
            }).join('\n\n');

            const greetingName = group.clientName === 'Sin cliente asignado'
                ? 'cliente'
                : group.clientName;

            group.message = [
                '🔔 *RECORDATORIO DE RENOVACIÓN*',
                '',
                `Hola *${greetingName}*, esperamos que te encuentres muy bien.`,
                '',
                'Te compartimos el estado actualizado de las cuentas seleccionadas:',
                '',
                accountBlocks,
                '',
                '✅ *Para renovar:* responde a este mensaje indicando las cuentas que deseas renovar.',
                '💬 Estamos atentos para ayudarte y mantener tu servicio sin interrupciones.',
                '',
                '🙌 Gracias por confiar en nuestro servicio.'
            ].join('\n');

            return group;
        });
    }

    function renderBulkNotificationGroups() {
        bulkNotificationGroups = buildGroupedNotifications();

        if (!bulkNotificationGroups.length) {
            bulkWhatsAppGroups.innerHTML = '<div class="af-empty"><i class="fab fa-whatsapp"></i><strong>No hay cuentas seleccionadas</strong><div>Marca al menos una cuenta para preparar las notificaciones.</div></div>';
            copyAllBulkMessages.disabled = true;
            return;
        }

        copyAllBulkMessages.disabled = false;
        bulkWhatsAppGroups.innerHTML = bulkNotificationGroups.map((group, index) => {
            const phoneText = group.phone ? `+${escapePreviewHtml(group.phone)}` : 'Sin WhatsApp registrado';
            const sendButton = group.phone
                ? `<button type="button" class="af-btn af-btn-success js-bulk-whatsapp-send" data-group-index="${index}"><i class="fab fa-whatsapp"></i> Abrir WhatsApp</button>`
                : `<button type="button" class="af-btn af-btn-success" disabled title="El cliente no tiene WhatsApp registrado"><i class="fab fa-whatsapp"></i> Sin número</button>`;

            return `<article style="border:1px solid #e4e7ef;border-radius:14px;background:#fff;overflow:hidden">
                <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:14px;padding:14px 16px;border-bottom:1px solid #e4e7ef;background:#f9fafc">
                    <div>
                        <strong style="display:block;color:#242735">${escapePreviewHtml(group.clientName)}</strong>
                        <span style="display:block;margin-top:3px;color:#687083;font-size:12px">${phoneText} · ${group.accounts.length} cuenta${group.accounts.length === 1 ? '' : 's'}</span>
                    </div>
                    <div style="display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end">
                        <button type="button" class="af-btn af-btn-secondary js-bulk-whatsapp-copy" data-group-index="${index}"><i class="fas fa-copy"></i> Copiar</button>
                        ${sendButton}
                    </div>
                </div>
                <div style="max-height:320px;overflow:auto;padding:16px;line-height:1.65;color:#1e293b;background:#f8fafc">${formatWhatsAppPreview(group.message)}</div>
            </article>`;
        }).join('');
    }

    async function renewSelectedAccounts(trigger) {
        const ids = selectedIds();
        if (!ids.length) {
            await Swal.fire('Selecciona cuentas', 'Marca al menos una cuenta para renovar.', 'warning');
            return;
        }

        const confirmation = await Swal.fire({
            title: `¿Renovar ${ids.length} cuenta${ids.length === 1 ? '' : 's'}?`,
            html: `Se utilizará la regla <strong>${escapePreviewHtml(renewalRuleLabel)}</strong> para todas las cuentas seleccionadas.<br><small style="color:#64748b">En mes calendario se suma un mes desde la fecha guardada, aunque ya esté vencida.</small>`,
            icon: 'question',
            showCancelButton: true,
            confirmButtonText: '<i class="fas fa-calendar-plus"></i> Renovar seleccionadas',
            cancelButtonText: 'Cancelar',
            confirmButtonColor: '#6758e8'
        });
        if (!confirmation.isConfirmed) return;

        trigger.disabled = true;
        try {
            const body = new URLSearchParams({action: 'renew_selected', _csrf: csrf});
            ids.forEach((id) => body.append('selected_cuentas[]', id));
            const result = await AfenixAdmin.request('gestionar_cuentas.php', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'X-Requested-With': 'XMLHttpRequest'
                },
                body
            });
            await Swal.fire('Renovación completada', result.message, 'success');
            location.reload();
        } catch (error) {
            await Swal.fire('Error', error.message, 'error');
        } finally {
            trigger.disabled = false;
        }
    }

    bulkWhatsAppButton.addEventListener('click', function () {
        if (!selectedIds().length) {
            Swal.fire('Selecciona cuentas', 'Marca al menos una cuenta para preparar la notificación.', 'warning');
            return;
        }
        renderBulkNotificationGroups();
        bulkWhatsAppModal.open(this);
    });

    bulkRenewSelectedButton.addEventListener('click', function () {
        renewSelectedAccounts(this);
    });

    bulkWhatsAppGroups.addEventListener('click', async (event) => {
        const copyButton = event.target.closest('.js-bulk-whatsapp-copy');
        const sendButton = event.target.closest('.js-bulk-whatsapp-send');
        const button = copyButton || sendButton;
        if (!button) return;

        const group = bulkNotificationGroups[Number(button.dataset.groupIndex)];
        if (!group) return;

        if (copyButton) {
            try {
                await copyMessage(group.message);
                await Swal.fire({
                    toast: true,
                    position: 'top-end',
                    icon: 'success',
                    title: `Mensaje de ${group.clientName} copiado`,
                    showConfirmButton: false,
                    timer: 1600
                });
            } catch (error) {
                await Swal.fire('No se pudo copiar', 'Copia el mensaje manualmente desde la vista previa.', 'error');
            }
            return;
        }

        if (sendButton && group.phone) {
            window.open(
                `https://wa.me/${group.phone}?text=${encodeURIComponent(group.message)}`,
                '_blank',
                'noopener'
            );
        }
    });

    copyAllBulkMessages.addEventListener('click', async () => {
        if (!bulkNotificationGroups.length) return;
        const combined = bulkNotificationGroups
            .map((group) => `*${group.clientName}*\n${group.message}`)
            .join('\n\n────────────────────\n\n');
        try {
            await copyMessage(combined);
            await Swal.fire({
                icon: 'success',
                title: 'Mensajes copiados',
                text: 'Todos los mensajes agrupados están listos para pegarse.',
                timer: 1800,
                showConfirmButton: false
            });
        } catch (error) {
            await Swal.fire('No se pudo copiar', 'Copia los mensajes manualmente desde el modal.', 'error');
        }
    });

    renewalConfigForm.addEventListener('submit', async (event) => {
        event.preventDefault();
        const submit = renewalConfigForm.querySelector('[type="submit"]');
        submit.disabled = true;
        try {
            const result = await AfenixAdmin.request('gestionar_cuentas.php', {
                method: 'POST',
                headers: {'X-Requested-With': 'XMLHttpRequest'},
                body: new FormData(renewalConfigForm)
            });
            renewalConfigModal.clean();
            await Swal.fire('Configuración guardada', result.message, 'success');
            location.reload();
        } catch (error) {
            Swal.fire('Error', error.message, 'error');
        } finally {
            submit.disabled = false;
        }
    });

    document.getElementById('exportAccounts').addEventListener('click', async () => {
        const result = await Swal.fire({
            title: '¿Exportar cuentas?',
            text: 'Se descargará un CSV con los filtros de plataforma y cliente seleccionados.',
            icon: 'info',
            showCancelButton: true,
            confirmButtonText: 'Exportar',
            cancelButtonText: 'Cancelar'
        });
        if (!result.isConfirmed) return;

        const url = new URL('gestionar_cuentas.php', location.href);
        url.searchParams.set('action', 'export_excel');
        const platform = document.getElementById('accountPlatformFilter').value;
        const client = document.getElementById('accountClientFilter').value;
        if (platform) url.searchParams.set('plataforma_filtro', platform);
        if (client && client !== 'none') url.searchParams.set('cliente_filtro', client);
        location.href = url.toString();
    });

    document.getElementById('accountTable').addEventListener('click', async (event) => {
        const copy = event.target.closest('.js-copy-account');
        if (copy) {
            await AfenixAdmin.copy(copy.dataset.code);
            Swal.fire({
                toast: true,
                position: 'top-end',
                icon: 'success',
                title: 'ID copiado',
                showConfirmButton: false,
                timer: 1300
            });
            return;
        }

        const notify = event.target.closest('.js-renewal-whatsapp');
        if (notify) {
            const phone = (notify.dataset.phone || '').replace(/\D+/g, '');
            const message = notify.dataset.message || '';
            const client = notify.dataset.client || 'Cliente';
            if (!phone || !message) return;

            const result = await Swal.fire({
                title: 'Recordatorio para ' + client,
                html: `<div style="text-align:left">
                    <p style="margin:0 0 12px;color:#64748b;font-size:13px">
                        Revisa el mensaje antes de copiarlo o enviarlo.
                    </p>
                    <div style="max-height:340px;overflow:auto;white-space:normal;background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:16px;line-height:1.65;color:#1e293b">
                        ${formatWhatsAppPreview(message)}
                    </div>
                </div>`,
                icon: 'info',
                showCancelButton: true,
                showDenyButton: true,
                confirmButtonText: '<i class="fab fa-whatsapp"></i> Enviar por WhatsApp',
                denyButtonText: '<i class="fas fa-copy"></i> Copiar mensaje',
                cancelButtonText: 'Cancelar',
                confirmButtonColor: '#16a34a',
                denyButtonColor: '#475569',
                reverseButtons: true,
                width: 620
            });

            if (result.isConfirmed) {
                window.open(
                    `https://wa.me/${phone}?text=${encodeURIComponent(message)}`,
                    '_blank',
                    'noopener'
                );
            } else if (result.isDenied) {
                try {
                    await copyMessage(message);
                    await Swal.fire({
                        icon: 'success',
                        title: 'Mensaje copiado',
                        text: 'El recordatorio está listo para pegarse donde lo necesites.',
                        timer: 1800,
                        showConfirmButton: false
                    });
                } catch (error) {
                    await Swal.fire(
                        'No se pudo copiar',
                        'Selecciona el mensaje y cópialo manualmente.',
                        'error'
                    );
                }
            }
            return;
        }

        const renew = event.target.closest('.js-renew-account');
        if (renew) {
            const currentText = renew.dataset.currentDate
                ? new Date(`${renew.dataset.currentDate}T00:00:00`).toLocaleDateString('es-PE')
                : 'Sin fecha';

            const confirmation = await Swal.fire({
                title: '¿Renovar esta cuenta?',
                html: `<div style="text-align:left;line-height:1.6">
                    <p><strong>Cuenta:</strong> ${escapePreviewHtml(renew.dataset.email || '')}</p>
                    <p><strong>Fecha actual:</strong> ${escapePreviewHtml(currentText)}</p>
                    <p><strong>Regla:</strong> ${escapePreviewHtml(renewalRuleLabel)}</p>
                    <p style="color:#64748b;font-size:13px">
                        En mes calendario se sumará un mes a la fecha guardada, aunque ya esté vencida. Si no hay fecha, se tomará hoy. En días fijos, una fecha vencida parte de hoy.
                    </p>
                </div>`,
                icon: 'question',
                showCancelButton: true,
                confirmButtonText: '<i class="fas fa-calendar-plus"></i> Renovar',
                cancelButtonText: 'Cancelar',
                confirmButtonColor: '#6758e8'
            });
            if (!confirmation.isConfirmed) return;

            try {
                const body = new URLSearchParams({
                    action: 'renew_account',
                    id: renew.dataset.id,
                    _csrf: csrf
                });
                const result = await AfenixAdmin.request('gestionar_cuentas.php', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                        'X-Requested-With': 'XMLHttpRequest'
                    },
                    body
                });
                await Swal.fire({
                    icon: 'success',
                    title: 'Cuenta renovada',
                    html: `La nueva fecha de <strong>${escapePreviewHtml(result.correo)}</strong> es
                        <strong>${escapePreviewHtml(result.fecha_nueva_texto)}</strong>.`
                });
                location.reload();
            } catch (error) {
                Swal.fire('Error', error.message, 'error');
            }
            return;
        }

        const edit = event.target.closest('.js-edit-account');
        if (edit) {
            try {
                const result = await AfenixAdmin.request(
                    `gestionar_cuentas.php?fetch_edit_data=${encodeURIComponent(edit.dataset.id)}`
                );
                resetAccount(result.data);
                accountModal.open(edit);
            } catch (error) {
                Swal.fire('Error', error.message, 'error');
            }
            return;
        }

        const regenerate = event.target.closest('.js-regenerate-account');
        if (regenerate) {
            const confirmation = await Swal.fire({
                title: '¿Regenerar ID?',
                text: 'El ID actual dejará de funcionar.',
                icon: 'question',
                showCancelButton: true,
                confirmButtonText: 'Sí, regenerar',
                cancelButtonText: 'Cancelar'
            });
            if (!confirmation.isConfirmed) return;

            try {
                const body = new URLSearchParams({
                    action: 'regenerate_account_id',
                    id: regenerate.dataset.id,
                    _csrf: csrf
                });
                const result = await AfenixAdmin.request('gestionar_cuentas.php', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                        'X-Requested-With': 'XMLHttpRequest'
                    },
                    body
                });
                await Swal.fire({
                    icon: 'success',
                    title: 'ID regenerado',
                    html: `Nuevo ID: <strong>${escapePreviewHtml(result.codigo)}</strong>`
                });
                location.reload();
            } catch (error) {
                Swal.fire('Error', error.message, 'error');
            }
            return;
        }

        const remove = event.target.closest('.js-delete-account');
        if (remove) {
            const confirmation = await Swal.fire({
                title: '¿Eliminar cuenta?',
                html: `Se eliminará <strong>${escapePreviewHtml(remove.dataset.email)}</strong> y su asociación con el cliente.`,
                icon: 'warning',
                showCancelButton: true,
                confirmButtonText: 'Sí, eliminar',
                cancelButtonText: 'Cancelar',
                confirmButtonColor: '#dc2626'
            });
            if (!confirmation.isConfirmed) return;

            try {
                const body = new URLSearchParams({
                    action: 'delete_account',
                    id: remove.dataset.id,
                    _csrf: csrf
                });
                const result = await AfenixAdmin.request('gestionar_cuentas.php', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                        'X-Requested-With': 'XMLHttpRequest'
                    },
                    body
                });
                await Swal.fire('Eliminada', result.message, 'success');
                location.reload();
            } catch (error) {
                Swal.fire('Error', error.message, 'error');
            }
        }
    });

    document.getElementById('applyBulkAction').addEventListener('click', async function () {
        const action = bulkActionSelect.value;
        const ids = selectedIds();

        if (!action || !ids.length) {
            Swal.fire('Selecciona cuentas', 'Elige una acción y al menos una cuenta.', 'warning');
            return;
        }

        if (action === 'delete_selected') {
            const confirmation = await Swal.fire({
                title: `¿Eliminar ${ids.length} cuenta${ids.length === 1 ? '' : 's'}?`,
                html: 'También se eliminarán sus asociaciones.',
                icon: 'warning',
                showCancelButton: true,
                confirmButtonText: 'Sí, eliminar',
                cancelButtonText: 'Cancelar',
                confirmButtonColor: '#dc2626'
            });
            if (!confirmation.isConfirmed) return;

            try {
                const body = new URLSearchParams({action, _csrf: csrf});
                ids.forEach((id) => body.append('selected_cuentas[]', id));
                const result = await AfenixAdmin.request('gestionar_cuentas.php', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                        'X-Requested-With': 'XMLHttpRequest'
                    },
                    body
                });
                await Swal.fire('Completado', result.message, 'success');
                location.reload();
            } catch (error) {
                Swal.fire('Error', error.message, 'error');
            }
            return;
        }

        document.getElementById('bulkActionName').value = action;
        const content = document.getElementById('bulkActionContent');
        content.innerHTML = '';

        ids.forEach((id) => {
            const input = document.createElement('input');
            input.type = 'hidden';
            input.name = 'selected_cuentas[]';
            input.value = id;
            content.appendChild(input);
        });

        if (action === 'change_renewal_date') {
            document.getElementById('bulkActionTitle').textContent =
                'Cambiar fecha de renovación';
            content.insertAdjacentHTML(
                'beforeend',
                '<label class="af-label" for="bulkRenewalDate">Nueva fecha *</label>' +
                '<input id="bulkRenewalDate" type="date" name="batch_fecha_renovacion" class="af-control" required>' +
                '<span class="af-help">Se aplicará a todas las cuentas seleccionadas.</span>'
            );
        } else {
            document.getElementById('bulkActionTitle').textContent =
                'Cambiar cliente y fecha';
            const options = clients.map((client) =>
                `<option value="${client.id}">${escapePreviewHtml(client.name)}</option>`
            ).join('');
            content.insertAdjacentHTML(
                'beforeend',
                `<label class="af-label" for="bulkNewClient">Nuevo cliente *</label>
                 <select id="bulkNewClient" name="batch_id_cliente" class="af-control" required>
                    <option value="">Seleccionar</option>${options}
                 </select>
                 <span class="af-help">La asignación anterior será reemplazada.</span>
                 <label class="af-label mt-4" for="bulkClientDate">Nueva fecha opcional</label>
                 <input id="bulkClientDate" type="date" name="batch_fecha_renovacion_cliente" class="af-control">`
            );
        }

        actionModal.open(this);
    });

    actionForm.addEventListener('submit', async (event) => {
        event.preventDefault();
        try {
            const result = await AfenixAdmin.request('gestionar_cuentas.php', {
                method: 'POST',
                headers: {'X-Requested-With': 'XMLHttpRequest'},
                body: new FormData(actionForm)
            });
            actionModal.clean();
            await Swal.fire('Completado', result.message, 'success');
            location.reload();
        } catch (error) {
            Swal.fire('Error', error.message, 'error');
        }
    });

    uploadForm.addEventListener('submit', async (event) => {
        event.preventDefault();
        const submit = uploadForm.querySelector('[type="submit"]');
        submit.disabled = true;
        try {
            Swal.fire({
                title: 'Procesando cuentas...',
                allowOutsideClick: false,
                didOpen: () => Swal.showLoading()
            });
            const result = await AfenixAdmin.request('gestionar_cuentas.php', {
                method: 'POST',
                headers: {'X-Requested-With': 'XMLHttpRequest'},
                body: new FormData(uploadForm)
            });
            uploadModal.clean();
            await Swal.fire({
                icon: 'success',
                title: result.title || 'Carga completada',
                html: result.message
            });
            location.reload();
        } catch (error) {
            Swal.fire('Error', error.message, 'error');
        } finally {
            submit.disabled = false;
        }
    });
});
</script>
<?php require_once __DIR__ . '/admin_footer.php'; ob_end_flush(); ?>

<!-- AFENIXX-CUENTAS-V9-LOGOS-FILTROS -->
<style id="afenixx-cuentas-v9-styles">
    .af-v9-platform-db {
        display: inline-flex !important;
        align-items: center !important;
        gap: 9px !important;
        min-width: 0 !important;
    }

    .af-v9-platform-logo-box {
        width: 34px !important;
        height: 34px !important;
        min-width: 34px !important;
        display: grid !important;
        place-items: center !important;
        overflow: hidden !important;
        padding: 4px !important;
        border: 1px solid #dbe4ef !important;
        border-radius: 10px !important;
        background: #ffffff !important;
        box-shadow: 0 5px 14px rgba(15, 23, 42, .08) !important;
    }

    .af-v9-platform-logo-box img {
        display: block !important;
        width: 100% !important;
        height: 100% !important;
        object-fit: contain !important;
    }

    .af-v9-platform-name {
        min-width: 0 !important;
        color: #0f172a !important;
        font-size: 11px !important;
        font-weight: 850 !important;
        line-height: 1.25 !important;
        white-space: nowrap !important;
    }

    .af-v9-filter-row {
        display: flex !important;
        align-items: flex-end !important;
        gap: 9px !important;
        flex-wrap: nowrap !important;
    }

    .af-v9-filter-row > * {
        min-width: 0 !important;
        margin-top: 0 !important;
        margin-bottom: 0 !important;
    }

    input.af-v9-search-compact {
        width: 195px !important;
        min-width: 165px !important;
        max-width: 195px !important;
    }

    .af-v9-clear-inline {
        align-self: flex-end !important;
        width: auto !important;
        min-width: auto !important;
        margin: 0 !important;
        white-space: nowrap !important;
    }

    @media (max-width: 1180px) {
        .af-v9-filter-row {
            flex-wrap: wrap !important;
        }
    }

    @media (max-width: 760px) {
        input.af-v9-search-compact {
            width: 100% !important;
            max-width: none !important;
        }
    }
</style>

<script id="afenixx-cuentas-v9-script">
(function () {
    'use strict';

    const normalizeText = function (value) {
        return String(value || '')
            .normalize('NFD')
            .replace(/[\u0300-\u036f]/g, '')
            .trim()
            .toLowerCase();
    };

    const resolveAssetUrl = function (value) {
        let url = String(value || '').trim();

        if (!url) {
            return '';
        }

        if (
            /^(?:https?:|data:|blob:|\/)/i.test(url)
        ) {
            return url;
        }

        url = url.replace(/^\.\//, '');

        return '../' + url;
    };

    const getPlatformTables = function () {
        return Array.from(
            document.querySelectorAll('table')
        ).filter(function (table) {
            const heading = normalizeText(
                table.tHead
                    ? table.tHead.innerText
                    : ''
            );

            return heading.includes('plataforma')
                && heading.includes('renovacion');
        });
    };

    const decoratePlatformRows = function (platformMap) {
        getPlatformTables().forEach(function (table) {
            const headers = Array.from(
                table.querySelectorAll('thead th')
            );

            const platformIndex = headers.findIndex(
                function (header) {
                    return normalizeText(
                        header.textContent
                    ).includes('plataforma');
                }
            );

            if (platformIndex < 0) {
                return;
            }

            table.querySelectorAll('tbody tr')
                .forEach(function (row) {
                    const cell = row.cells
                        ? row.cells[platformIndex]
                        : null;

                    if (
                        !cell
                        || cell.dataset.afV9LogoApplied === '1'
                    ) {
                        return;
                    }

                    const platformName = String(
                        cell.dataset.platformName
                        || cell.textContent
                        || ''
                    ).trim();

                    const platform = platformMap.get(
                        normalizeText(platformName)
                    );

                    if (
                        !platform
                        || !platform.image
                    ) {
                        return;
                    }

                    const originalHtml = cell.innerHTML;

                    const wrapper = document.createElement(
                        'span'
                    );

                    wrapper.className =
                        'af-v9-platform-db';

                    const logoBox = document.createElement(
                        'span'
                    );

                    logoBox.className =
                        'af-v9-platform-logo-box';

                    const image = document.createElement(
                        'img'
                    );

                    image.src = resolveAssetUrl(
                        platform.image
                    );

                    image.alt = platformName;
                    image.loading = 'lazy';

                    image.addEventListener(
                        'error',
                        function () {
                            cell.innerHTML = originalHtml;

                            delete cell.dataset
                                .afV9LogoApplied;
                        },
                        { once: true }
                    );

                    const name = document.createElement(
                        'strong'
                    );

                    name.className =
                        'af-v9-platform-name';

                    name.textContent = platformName;

                    logoBox.appendChild(image);
                    wrapper.appendChild(logoBox);
                    wrapper.appendChild(name);

                    cell.replaceChildren(wrapper);

                    cell.dataset.afV9LogoApplied = '1';
                    cell.dataset.platformName =
                        platformName;
                });
        });
    };

    const compactAccountFilters = function () {
        // V16: la barra nativa ya contiene todos los filtros en el orden correcto.
        return;
    };

    const initializeAccountsV9 = function () {
        loadPlatformLogos();
    };

    if (
        document.readyState === 'loading'
    ) {
        document.addEventListener(
            'DOMContentLoaded',
            initializeAccountsV9,
            { once: true }
        );
    } else {
        initializeAccountsV9();
    }
})();
</script>
