<?php
/**
 * @intranet-file base/gestion_enlaces/index.php
 * @intranet-file-version 2026.03.13.1
 * @intranet-file-date 2026-03-13
 * @intranet-file-owner CSIF Intranet
 */
declare(strict_types=1);

// 1) Seguridad central
require_once dirname(__DIR__, 2) . '/common/app_guard.php';
require_once dirname(__DIR__, 2) . '/common/url.php';
require_once dirname(__DIR__, 2) . '/common/roles.php';
checkAppAccess();

require_once dirname(__DIR__, 2) . '/common/catalogo_db.php';

// 2) Solo administradores
if (!catalogo_is_admin()) {
    if (function_exists('renderErrorPage')) {
        renderErrorPage('Solo administradores pueden gestionar el catálogo.');
        exit;
    }
    http_response_code(403);
    die('Acceso restringido');
}

$pdo = db();
catalogo_schema_ensure($pdo);

// UI
function h($s): string { return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
function is_active(string $expected, string $actual): string { return $expected === $actual ? 'active' : ''; }
function admin_icon_preview_markup(?string $image, ?string $markup, string $alt = 'Vista previa'): string
{
    $markup = trim((string)$markup);
    if ($markup !== '') {
        return '<span class="catalogo-svg-icon" aria-hidden="true" style="display:inline-flex; width:72px; height:72px; align-items:center; justify-content:center;">' . $markup . '</span>';
    }

    $src = htmlspecialchars(catalogo_image_src($image), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    $alt = htmlspecialchars($alt, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    return '<img src="' . $src . '" alt="' . $alt . '">';
}

function catalogo_admin_app_group_exists(PDO $pdo, string $groupName): bool
{
    return catalogo_group_exists($pdo, 'apps', $groupName);
}

function catalogo_admin_roles_from_post($value): array
{
    return intranet_roles_normalize($value);
}

function catalogo_admin_group_type_label(string $groupType): string
{
    $groupType = catalogo_normalize_group_type($groupType);
    $labels = [
        'links' => 'Enlaces',
        'novedades' => 'Novedades',
        'nntt' => 'NNTT',
        'apps' => 'Aplicaciones',
    ];
    return $labels[$groupType];
}

function catalogo_admin_group_item_label(string $groupType): string
{
    $groupType = catalogo_normalize_group_type($groupType);
    $labels = [
        'links' => 'enlace(s)',
        'novedades' => 'novedad(es)',
        'nntt' => 'elemento(s) NNTT',
        'apps' => 'aplicacion(es)',
    ];
    return $labels[$groupType];
}

function catalogo_admin_group_redirect_url(string $groupType, int $ok): string
{
    $groupType = catalogo_normalize_group_type($groupType);
    if ($groupType === 'apps') {
        return 'index.php?section=apps&app_tab=groups&ok=' . $ok . '&ts=' . time();
    }
    return 'index.php?section=links&type=' . rawurlencode($groupType) . '&link_tab=groups&ok=' . $ok . '&ts=' . time();
}

$section = isset($_GET['section']) && is_string($_GET['section']) ? trim($_GET['section']) : 'links';
$section = in_array($section, ['links', 'apps'], true) ? $section : 'links';

$types = [
    'links' => ['label' => 'Enlaces útiles', 'back' => app_url('base/links/index.php')],
    'novedades' => ['label' => 'Novedades', 'back' => app_url('base/novedades/index.php')],
    'nntt' => ['label' => 'NNTT', 'back' => app_url('base/nntt/index.php')],
];

$type = isset($_GET['type']) && is_string($_GET['type']) ? trim($_GET['type']) : 'links';
$type = array_key_exists($type, $types) ? $type : 'links';

$linkTab = isset($_GET['link_tab']) && is_string($_GET['link_tab']) ? trim($_GET['link_tab']) : 'items';
$linkTab = in_array($linkTab, ['items', 'groups'], true) ? $linkTab : 'items';
if ($section !== 'links') {
    $linkTab = 'items';
}

$appTab = isset($_GET['app_tab']) && is_string($_GET['app_tab']) ? trim($_GET['app_tab']) : 'apps';
$appTab = in_array($appTab, ['apps', 'groups'], true) ? $appTab : 'apps';
if ($section !== 'apps') {
    $appTab = 'apps';
}

$backUrl = ($section === 'links') ? $types[$type]['back'] : app_url('dashboard.php');

$msg = '';
$err = '';

// Mensajes OK
if (isset($_GET['ok'])) {
    $ok = (int)$_GET['ok'];
    if ($ok === 1) $msg = 'Enlace añadido correctamente.';
    if ($ok === 2) $msg = 'Enlaces actualizados correctamente.';
    if ($ok === 11) $msg = 'Aplicación añadida correctamente.';
    if ($ok === 12) $msg = 'Aplicaciones actualizadas correctamente.';
    if ($ok === 21 || $ok === 31) $msg = 'Grupo añadido correctamente.';
    if ($ok === 22 || $ok === 32) $msg = 'Grupos actualizados correctamente.';
}

// POST
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        catalogo_csrf_validate($_POST['csrf'] ?? null, 'catalogo_admin_csrf');
        $action = isset($_POST['action']) && is_string($_POST['action']) ? trim($_POST['action']) : '';

        // === GROUPS ===
        if ($action === 'catalog_groups_add' || $action === 'app_groups_add') {
            $groupType = $action === 'app_groups_add'
                ? 'apps'
                : catalogo_normalize_group_type((string)($_POST['group_type'] ?? ''));
            $rawGroupName = trim((string)($_POST['group_name'] ?? ''));
            if ($rawGroupName === '') throw new RuntimeException('El nombre del grupo es obligatorio.');
            $groupName = catalogo_normalize_group_name($rawGroupName, $groupType);
            $iconSvg = catalogo_normalize_svg_markup((string)($_POST['icon_svg'] ?? ''));

            if (catalogo_group_exists($pdo, $groupType, $groupName)) {
                throw new RuntimeException('Ya existe un grupo con ese nombre para este tipo.');
            }

            $st = $pdo->prepare('INSERT INTO intranet_catalog_groups (group_type, group_name, icon_svg, position) VALUES (:t, :g, :icon_svg, :p)');
            $st->execute([
                't' => $groupType,
                'g' => $groupName,
                'icon_svg' => $iconSvg,
                'p' => catalogo_next_group_position($pdo, $groupType),
            ]);
            catalogo_ensure_group($pdo, $groupType, $groupName, $iconSvg);

            header('Location: ' . catalogo_admin_group_redirect_url($groupType, 31));
            exit;
        }

        if ($action === 'catalog_groups_bulk_save' || $action === 'app_groups_bulk_save') {
            $groupType = $action === 'app_groups_bulk_save'
                ? 'apps'
                : catalogo_normalize_group_type((string)($_POST['group_type'] ?? ''));
            $rows = $_POST['catalog_groups'] ?? ($_POST['app_groups'] ?? []);
            if (!is_array($rows)) $rows = [];
            usort($rows, static function ($a, $b): int {
                $pa = is_array($a) ? (int)($a['position'] ?? 0) : 0;
                $pb = is_array($b) ? (int)($b['position'] ?? 0) : 0;
                return $pa <=> $pb;
            });

            $orig = [];
            foreach (catalogo_groups_fetch($pdo, $groupType) as $row) {
                $id = (int)($row['id'] ?? 0);
                if ($id > 0) $orig[$id] = $row;
            }

            $pos = 0;
            foreach ($rows as $r) {
                if (!is_array($r)) continue;
                $id = isset($r['id']) ? (int)$r['id'] : 0;
                if ($id <= 0 || !isset($orig[$id])) continue;

                $oldName = catalogo_normalize_group_name($orig[$id]['group_name'] ?? catalogo_default_group_name($groupType), $groupType);
                $doDelete = !empty($r['_delete']);
                if ($doDelete) {
                    if (catalogo_group_usage_count($pdo, $groupType, $oldName) > 0) {
                        throw new RuntimeException('No se puede eliminar el grupo "' . $oldName . '" porque tiene elementos asignados.');
                    }
                    $stDel = $pdo->prepare('DELETE FROM intranet_catalog_groups WHERE id = :id AND group_type = :t');
                    $stDel->execute(['id' => $id, 't' => $groupType]);
                    if ($groupType === 'apps') {
                        $stLegacyDel = $pdo->prepare('DELETE FROM intranet_app_groups WHERE group_name = :g');
                        $stLegacyDel->execute(['g' => $oldName]);
                    }
                    continue;
                }

                $pos++;
                $stPos = $pdo->prepare('UPDATE intranet_catalog_groups SET position = :p WHERE id = :id AND group_type = :t');
                $stPos->execute(['p' => $pos, 'id' => $id, 't' => $groupType]);
                if ($groupType === 'apps') {
                    $stLegacyPos = $pdo->prepare('UPDATE intranet_app_groups SET position = :p WHERE group_name = :g');
                    $stLegacyPos->execute(['p' => $pos, 'g' => $oldName]);
                }

                $doUpdate = !empty($r['_update']);
                if ($doUpdate) {
                    $rawGroupName = trim((string)($r['group_name'] ?? ''));
                    if ($rawGroupName === '') throw new RuntimeException('El nombre del grupo es obligatorio (ID ' . $id . ').');
                    $groupName = catalogo_normalize_group_name($rawGroupName, $groupType);
                    $iconSvg = catalogo_normalize_svg_markup((string)($r['icon_svg'] ?? ''));

                    $stDup = $pdo->prepare('SELECT id FROM intranet_catalog_groups WHERE group_type = :t AND group_name = :g AND id <> :id LIMIT 1');
                    $stDup->execute(['t' => $groupType, 'g' => $groupName, 'id' => $id]);
                    if ($stDup->fetch(PDO::FETCH_ASSOC)) {
                        throw new RuntimeException('Ya existe otro grupo con el nombre "' . $groupName . '" para este tipo.');
                    }

                    $stUp = $pdo->prepare('UPDATE intranet_catalog_groups SET group_name = :g, icon_svg = :icon_svg WHERE id = :id AND group_type = :t');
                    $stUp->execute(['g' => $groupName, 'icon_svg' => $iconSvg, 'id' => $id, 't' => $groupType]);

                    if ($oldName !== '' && $oldName !== $groupName) {
                        catalogo_rename_group_items($pdo, $groupType, $oldName, $groupName);
                    }
                    if ($groupType === 'apps') {
                        catalogo_ensure_group($pdo, $groupType, $groupName, $iconSvg);
                        $stLegacyUp = $pdo->prepare('UPDATE intranet_app_groups SET icon_svg = :icon_svg, position = :p WHERE group_name = :g');
                        $stLegacyUp->execute(['icon_svg' => $iconSvg, 'p' => $pos, 'g' => $groupName]);
                    }
                }
            }

            header('Location: ' . catalogo_admin_group_redirect_url($groupType, 32));
            exit;
        }

        // === LINKS ===
        if ($action === 'links_add') {
            $t = isset($_POST['type']) && is_string($_POST['type']) ? trim($_POST['type']) : $type;
            if (!array_key_exists($t, $types)) throw new RuntimeException('Tipo inválido');

            $grupoSel = trim((string)($_POST['grupo_select'] ?? 'General'));
            $grupo = $grupoSel !== '' ? $grupoSel : 'General';
            $grupo = catalogo_normalize_group_name($grupo, $t);
            if (!catalogo_group_exists($pdo, $t, $grupo)) {
                throw new RuntimeException('El grupo seleccionado no existe para este tipo.');
            }
            $descripcion = trim((string)($_POST['descripcion'] ?? ''));
            $destino = trim((string)($_POST['destino'] ?? ''));
            $imagen = trim((string)($_POST['imagen'] ?? '/images/default.png'));
            $iconSvg = catalogo_normalize_svg_markup((string)($_POST['icon_svg'] ?? ''));
            $aviso = trim((string)($_POST['aviso'] ?? ''));
            $isEnabled = isset($_POST['is_enabled']) && (string)$_POST['is_enabled'] === '1' ? 1 : 0;

            if ($descripcion === '') throw new RuntimeException('La descripción es obligatoria.');
            if ($destino === '') throw new RuntimeException('El destino (URL) es obligatorio.');
            if ($imagen === '') $imagen = '/images/default.png';

            $pos = catalogo_next_link_position($pdo, $t);
            $st = $pdo->prepare("INSERT INTO intranet_links (link_type, grupo, descripcion, destino, imagen, icon_svg, aviso, position, is_enabled)
                                VALUES (:t, :g, :d, :dest, :img, :icon_svg, :av, :pos, :en)");
            $st->execute([
                't' => $t,
                'g' => $grupo,
                'd' => $descripcion,
                'dest' => $destino,
                'img' => $imagen,
                'icon_svg' => $iconSvg,
                'av' => $aviso !== '' ? $aviso : null,
                'pos' => $pos,
                'en' => $isEnabled,
            ]);

            header('Location: index.php?section=links&type=' . rawurlencode($t) . '&ok=1');
            exit;
        }

        if ($action === 'links_bulk_save') {
            $t = isset($_POST['type']) && is_string($_POST['type']) ? trim($_POST['type']) : $type;
            if (!array_key_exists($t, $types)) throw new RuntimeException('Tipo inválido');

            $rows = $_POST['links'] ?? [];
            if (!is_array($rows)) $rows = [];

            // Reordenación global por tipo
            $pos = 0;
            foreach ($rows as $r) {
                if (!is_array($r)) continue;
                $id = isset($r['id']) ? (int)$r['id'] : 0;
                if ($id <= 0) continue;

                $doDelete = !empty($r['_delete']);
                if ($doDelete) {
                    $st = $pdo->prepare('DELETE FROM intranet_links WHERE id = :id AND link_type = :t');
                    $st->execute(['id' => $id, 't' => $t]);
                    continue;
                }

                $pos++;
                // Siempre actualizamos posición (orden visual)
                $stPos = $pdo->prepare('UPDATE intranet_links SET position = :p WHERE id = :id AND link_type = :t');
                $stPos->execute(['p' => $pos, 'id' => $id, 't' => $t]);

                $doUpdate = !empty($r['_update']);
                if ($doUpdate) {
                    $grupo = catalogo_normalize_group_name($r['grupo'] ?? 'General', $t);
                    if (!catalogo_group_exists($pdo, $t, $grupo)) {
                        throw new RuntimeException('El grupo seleccionado no existe (ID ' . $id . ').');
                    }
                    $descripcion = trim((string)($r['descripcion'] ?? ''));
                    $destino = trim((string)($r['destino'] ?? ''));
                    $imagen = trim((string)($r['imagen'] ?? '/images/default.png'));
                    $iconSvg = catalogo_normalize_svg_markup((string)($r['icon_svg'] ?? ''));
                    $aviso = trim((string)($r['aviso'] ?? ''));
                    $isEnabled = !empty($r['is_enabled']) ? 1 : 0;

                    if ($descripcion === '') throw new RuntimeException('Descripción obligatoria (ID ' . $id . ').');
                    if ($destino === '') throw new RuntimeException('Destino obligatorio (ID ' . $id . ').');
                    if ($imagen === '') $imagen = '/images/default.png';

                    $stUp = $pdo->prepare('UPDATE intranet_links
                                           SET grupo = :g, descripcion = :d, destino = :dest, imagen = :img, icon_svg = :icon_svg, aviso = :av, is_enabled = :en
                                           WHERE id = :id AND link_type = :t');
                    $stUp->execute([
                        'g' => $grupo,
                        'd' => $descripcion,
                        'dest' => $destino,
                        'img' => $imagen,
                        'icon_svg' => $iconSvg,
                        'av' => $aviso !== '' ? $aviso : null,
                        'en' => $isEnabled,
                        'id' => $id,
                        't' => $t,
                    ]);
                }
            }

            header('Location: index.php?section=links&type=' . rawurlencode($t) . '&ok=2');
            exit;
        }

        // === APPS ===
        if ($action === 'apps_add') {
            $groupName = catalogo_normalize_app_group_name($_POST['group_name'] ?? 'Mis apps');
            if (!catalogo_admin_app_group_exists($pdo, $groupName)) {
                throw new RuntimeException('El grupo de aplicaciones seleccionado no existe.');
            }

            $appId = trim((string)($_POST['app_id'] ?? ''));
            if ($appId === '' || !preg_match('/^[a-zA-Z0-9_-]{1,100}$/', $appId)) {
                throw new RuntimeException('app_id inválido. Usa letras/números/guión/guión_bajo (máx 100).');
            }

            $name = trim((string)($_POST['name'] ?? ''));
            $url  = trim((string)($_POST['url'] ?? ''));
            if ($name === '') throw new RuntimeException('El nombre es obligatorio.');
            if ($url === '') throw new RuntimeException('La URL es obligatoria.');

            $rolesArr = catalogo_admin_roles_from_post($_POST['roles'] ?? []);
            if (empty($rolesArr)) throw new RuntimeException('Indica al menos un rol.');
            $rolesJson = catalogo_roles_to_json($rolesArr);

            $description = trim((string)($_POST['description'] ?? ''));
            $icon = (string)($_POST['icon'] ?? '');
            $icon = trim($icon);
            $image = trim((string)($_POST['image'] ?? ''));
            $newTab = isset($_POST['new_tab']) && (string)$_POST['new_tab'] === '1' ? 1 : 0;
            $isEnabled = isset($_POST['is_enabled']) && (string)$_POST['is_enabled'] === '1' ? 1 : 0;

            $pos = catalogo_next_app_position($pdo, $groupName);

            $st = $pdo->prepare("INSERT INTO intranet_apps (app_id, name, url, group_name, description, icon, image, new_tab, roles_json, position, is_enabled)
                                VALUES (:app_id, :name, :url, :group_name, :description, :icon, :image, :new_tab, :roles_json, :position, :is_enabled)");
            $st->execute([
                'app_id' => $appId,
                'name' => $name,
                'url' => $url,
                'group_name' => $groupName,
                'description' => $description !== '' ? $description : null,
                'icon' => $icon !== '' ? $icon : null,
                'image' => $image !== '' ? $image : null,
                'new_tab' => $newTab,
                'roles_json' => $rolesJson,
                'position' => $pos,
                'is_enabled' => $isEnabled,
            ]);

            header('Location: index.php?section=apps&app_tab=apps&ok=11&ts=' . time());
            exit;
        }

        if ($action === 'apps_bulk_save') {
            $rows = $_POST['apps'] ?? [];
            if (!is_array($rows)) $rows = [];

            // Mapa original (para asignar posiciones en grupo cuando no se marca actualizar)
            $orig = [];
            foreach (catalogo_apps_fetch($pdo, false) as $r0) {
                $id0 = (int)($r0['id'] ?? 0);
                if ($id0 > 0) $orig[$id0] = $r0;
            }

            $posByGroup = [];
            foreach ($rows as $r) {
                if (!is_array($r)) continue;
                $id = isset($r['id']) ? (int)$r['id'] : 0;
                if ($id <= 0) continue;

                $doDelete = !empty($r['_delete']);
                if ($doDelete) {
                    $st = $pdo->prepare('DELETE FROM intranet_apps WHERE id = :id');
                    $st->execute(['id' => $id]);
                    continue;
                }

                $doUpdate = !empty($r['_update']);

                $groupName = $doUpdate
                    ? catalogo_normalize_app_group_name($r['group_name'] ?? 'Mis apps')
                    : catalogo_normalize_app_group_name($orig[$id]['group_name'] ?? 'Mis apps');
                if (!catalogo_admin_app_group_exists($pdo, $groupName)) {
                    throw new RuntimeException('El grupo seleccionado no existe (ID ' . $id . ').');
                }

                if (!isset($posByGroup[$groupName])) $posByGroup[$groupName] = 0;
                $posByGroup[$groupName]++;
                $newPos = (int)$posByGroup[$groupName];

                // Siempre actualizamos position (orden visual dentro del grupo resultante)
                $stPos = $pdo->prepare('UPDATE intranet_apps SET position = :p WHERE id = :id');
                $stPos->execute(['p' => $newPos, 'id' => $id]);

                if ($doUpdate) {
                    $name = trim((string)($r['name'] ?? ''));
                    $url = trim((string)($r['url'] ?? ''));
                    if ($name === '') throw new RuntimeException('Nombre obligatorio (ID ' . $id . ').');
                    if ($url === '') throw new RuntimeException('URL obligatoria (ID ' . $id . ').');

                    $rolesArr = catalogo_admin_roles_from_post($r['roles'] ?? []);
                    if (empty($rolesArr)) throw new RuntimeException('Roles obligatorios (ID ' . $id . ').');
                    $rolesJson = catalogo_roles_to_json($rolesArr);

                    $description = trim((string)($r['description'] ?? ''));
                    $image = trim((string)($r['image'] ?? ''));
                    $icon = trim((string)($r['icon'] ?? ''));
                    $newTab = !empty($r['new_tab']) ? 1 : 0;
                    $isEnabled = !empty($r['is_enabled']) ? 1 : 0;

                    $stUp = $pdo->prepare('UPDATE intranet_apps
                                           SET group_name = :group_name, name = :name, url = :url, description = :description,
                                               icon = :icon, image = :image, new_tab = :new_tab, roles_json = :roles_json, is_enabled = :is_enabled
                                           WHERE id = :id');
                    $stUp->execute([
                        'group_name' => $groupName,
                        'name' => $name,
                        'url' => $url,
                        'description' => $description !== '' ? $description : null,
                        'icon' => $icon !== '' ? $icon : null,
                        'image' => $image !== '' ? $image : null,
                        'new_tab' => $newTab,
                        'roles_json' => $rolesJson,
                        'is_enabled' => $isEnabled,
                        'id' => $id,
                    ]);
                }
            }

            header('Location: index.php?section=apps&app_tab=apps&ok=12&ts=' . time());
            exit;
        }

        throw new RuntimeException('Acción no soportada.');

    } catch (Throwable $e) {
        $err = $e->getMessage();
    }
}

// Datos para pintar
$images = catalogo_list_images(realpath(__DIR__ . '/../../styles/images') ?: (__DIR__ . '/../../styles/images'));
if (empty($images)) {
    $images = ['/images/default.png'];
}
$csrf = catalogo_csrf_token('catalogo_admin_csrf');

$linksPlain = [];
$linkGroupRows = [];
$groupNames = [];
if ($section === 'links') {
    $linksPlain = catalogo_links_fetch($pdo, $type, false);
    $groupCounts = [];
    $stGroupCounts = $pdo->prepare('SELECT grupo, COUNT(*) AS total FROM intranet_links WHERE link_type = :t GROUP BY grupo');
    $stGroupCounts->execute(['t' => $type]);
    while ($row = $stGroupCounts->fetch(PDO::FETCH_ASSOC)) {
        $g = catalogo_normalize_group_name($row['grupo'] ?? '', $type);
        if ($g !== '') {
            $groupCounts[$g] = ($groupCounts[$g] ?? 0) + (int)($row['total'] ?? 0);
        }
    }

    foreach (catalogo_groups_fetch($pdo, $type) as $groupRow) {
        $g = catalogo_normalize_group_name($groupRow['group_name'] ?? '', $type);
        if ($g === '') {
            continue;
        }
        $groupRow['group_name'] = $g;
        $groupRow['item_count'] = (int)($groupCounts[$g] ?? 0);
        $linkGroupRows[] = $groupRow;
        $groupNames[] = $g;
    }

    sort($groupNames, SORT_NATURAL | SORT_FLAG_CASE);
}

$appsPlain = [];
$appGroupsRows = [];
$appsGroups = [];
$availableRoles = [];
if ($section === 'apps') {
    $groupCounts = [];
    $stGroupCounts = $pdo->query('SELECT group_name, COUNT(*) AS total FROM intranet_apps GROUP BY group_name');
    if ($stGroupCounts !== false) {
        while ($row = $stGroupCounts->fetch(PDO::FETCH_ASSOC)) {
            $g = catalogo_normalize_app_group_name($row['group_name'] ?? '');
            if ($g !== '') {
                $groupCounts[$g] = ($groupCounts[$g] ?? 0) + (int)($row['total'] ?? 0);
            }
        }
    }

    foreach (catalogo_app_groups_fetch($pdo) as $groupRow) {
        $g = catalogo_normalize_app_group_name($groupRow['group_name'] ?? '');
        if ($g === '') {
            continue;
        }
        $groupRow['group_name'] = $g;
        $groupRow['app_count'] = (int)($groupCounts[$g] ?? 0);
        $appGroupsRows[] = $groupRow;
        $appsGroups[] = $g;
    }

    $appsPlain = catalogo_apps_fetch($pdo, false);
    foreach ($appsPlain as &$a) {
        $a['group_name'] = catalogo_normalize_app_group_name($a['group_name'] ?? 'Mis apps');
        $rolesArr = catalogo_roles_from_json($a['roles_json'] ?? '');
        $a['_roles_csv'] = implode(', ', $rolesArr);
    }
    unset($a);
    $availableRoles = intranet_available_roles($pdo);
    sort($appsGroups, SORT_NATURAL | SORT_FLAG_CASE);
}

?>
<!DOCTYPE html>
<html lang="es" class="app-html" <?= intranet_theme_html_attr(); ?>>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Gestión catálogo</title>
    <?= intranet_theme_bootstrap_script_tag(); ?>
    <link rel="stylesheet" href="<?= h(asset_url('styles/css/style.css')) ?>">
    <link rel="stylesheet" href="<?= h(asset_url('styles/css/theme-vars.css')) ?>">
    <style>
        .catalogo-preview-box {
            min-height: 96px;
            border: 1px dashed rgba(148, 163, 184, 0.4);
            border-radius: 16px;
            padding: 12px;
            display: flex;
            align-items: center;
            justify-content: center;
            background: rgba(15, 23, 42, 0.18);
        }
        .catalogo-preview-box img,
        .catalogo-preview-box svg {
            max-width: 72px;
            max-height: 72px;
        }
        .catalogo-preview-box .catalogo-preview-empty {
            font-size: 0.85rem;
            opacity: 0.7;
            text-align: center;
        }
        .catalogo-mini-preview {
            min-height: 44px;
            width: 56px;
            border-radius: 10px;
            border: 1px dashed rgba(148, 163, 184, 0.45);
            display: inline-flex;
            align-items: center;
            justify-content: center;
            background: rgba(15, 23, 42, 0.14);
            color: var(--text-main);
        }
        .catalogo-mini-preview svg {
            width: 24px;
            height: 24px;
        }
        .catalogo-role-select {
            min-height: 118px;
        }
        .catalogo-role-select option {
            padding: 6px 8px;
        }
        .catalogo-select-help {
            display:block;
            margin-top:6px;
            color: var(--text-muted);
            font-size: .82rem;
        }
        .catalogo-subtabs {
            display: flex;
            gap: 8px;
            align-items: center;
            flex-wrap: wrap;
            margin: 0 0 14px 0;
        }
        .catalogo-subtab {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            min-height: 36px;
            padding: 8px 14px;
            border-radius: 8px;
            border: 1px solid rgba(148, 163, 184, 0.35);
            color: var(--text-main);
            text-decoration: none;
            font-weight: 900;
            background: rgba(15, 23, 42, 0.12);
        }
        .catalogo-subtab.active {
            color: #fff;
            border-color: rgba(220, 38, 38, 0.55);
            background: rgba(220, 38, 38, 0.86);
        }
    </style>
    <script src="<?= h(asset_url('styles/js/table_sort.js')) ?>" defer></script>
</head>
<body class="app-body">

<header class="header-top">
    <div class="header-titles">
        <h2 style="margin:0;">Gestión de catálogo</h2>
        <p style="margin:5px 0 0 0; opacity:0.9;">Usuario: <strong><?php echo h($_SESSION['givenName'] ?? $_SESSION['username'] ?? ''); ?></strong></p>
    </div>

    <div class="header-actions">
        <!-- Reutilizamos el estilo corporativo de navegación en cabecera (ver styles/css/style.css .header-nav) -->
        <nav class="header-nav" aria-label="Secciones de catálogo">
<ul>
            <li><a class="<?php echo ($section==='links' && $type==='links') ? 'active' : ''; ?>" href="?section=links&type=links">Enlaces</a></li>
            <li><a class="<?php echo ($section==='links' && $type==='novedades') ? 'active' : ''; ?>" href="?section=links&type=novedades">Novedades</a></li>
            <li><a class="<?php echo ($section==='links' && $type==='nntt') ? 'active' : ''; ?>" href="?section=links&type=nntt">NNTT</a></li>
            <li style="opacity:.5; padding: 0 6px;">|</li>
            <li><a class="<?php echo ($section==='apps') ? 'active' : ''; ?>" href="?section=apps">Aplicaciones</a></li>
        </ul>
    </nav>

        <a href="<?= h(asset_url('dashboard.php')) ?>" target="_top" class="btn-back-dashboard">Volver al inicio</a>
    </div>
</header>

<div class="admin-toolbar">
    <a class="btn-mini" href="<?php echo h($backUrl); ?>">← Volver</a>
    <div class="hint">
        Orden = como se ve en la tabla (arrastrar o ordenar por campos). Marca <strong>Actualizar</strong> o <strong>Eliminar</strong> y pulsa <strong>Aplicar todo</strong>.
    </div>
</div>

<?php if ($msg): ?>
    <div class="msg-card success" style="margin-bottom: 14px;">
        <h3 style="margin:0;">✅ <?php echo h($msg); ?></h3>
    </div>
<?php endif; ?>

<?php if ($err): ?>
    <div class="msg-card error" style="margin-bottom: 14px;">
        <h3 style="margin:0;">⛔ <?php echo h($err); ?></h3>
    </div>
<?php endif; ?>

<?php if ($section === 'links'): ?>

    <nav class="catalogo-subtabs" aria-label="Subsecciones de <?php echo h($types[$type]['label']); ?>">
        <a class="catalogo-subtab <?php echo $linkTab === 'items' ? 'active' : ''; ?>" href="?section=links&type=<?php echo h($type); ?>&link_tab=items"><?php echo h($types[$type]['label']); ?></a>
        <a class="catalogo-subtab <?php echo $linkTab === 'groups' ? 'active' : ''; ?>" href="?section=links&type=<?php echo h($type); ?>&link_tab=groups">Grupos</a>
    </nav>

<?php if ($linkTab === 'groups'): ?>

    <div class="card">
        <div class="card-title">
            <span>Grupos de <?php echo h(catalogo_admin_group_type_label($type)); ?></span>
            <span class="opacity-40" style="font-weight:700; font-size: 0.95rem;">Cat&aacute;logo com&uacute;n</span>
        </div>

        <form method="post" data-preview-scope="app-group" style="display:grid; grid-template-columns: repeat(12, 1fr); gap: 14px; margin-bottom:18px;">
            <input type="hidden" name="csrf" value="<?php echo h($csrf); ?>">
            <input type="hidden" name="action" value="catalog_groups_add">
            <input type="hidden" name="group_type" value="<?php echo h($type); ?>">

            <div class="form-group" style="grid-column: span 5;">
                <label for="catalog_group_name_<?php echo h($type); ?>">Nuevo grupo</label>
                <input id="catalog_group_name_<?php echo h($type); ?>" name="group_name" class="form-control" placeholder="Nombre del grupo" required>
            </div>

            <div class="form-group" style="grid-column: span 5;">
                <label for="catalog_group_icon_svg_<?php echo h($type); ?>">Icono SVG del grupo</label>
                <textarea id="catalog_group_icon_svg_<?php echo h($type); ?>" name="icon_svg" class="form-control" rows="3" data-preview-markup placeholder="<svg ...>...</svg>"></textarea>
            </div>

            <div class="form-group" style="grid-column: span 1;">
                <label>Vista</label>
                <div class="catalogo-mini-preview" data-preview-target>
                    <span class="catalogo-preview-empty">SVG</span>
                </div>
            </div>

            <div class="form-group" style="grid-column: span 1; display:flex; align-items:end; justify-content:flex-end;">
                <button type="submit" class="btn-primary-intranet">A&ntilde;adir</button>
            </div>
        </form>

        <div class="table-responsive">
            <form method="post" id="bulk-catalog-groups-form">
                <input type="hidden" name="csrf" value="<?php echo h($csrf); ?>">
                <input type="hidden" name="action" value="catalog_groups_bulk_save">
                <input type="hidden" name="group_type" value="<?php echo h($type); ?>">

                <table class="admin-table" id="catalogGroupsTable">
                    <thead>
                    <tr>
                        <th style="width: 90px;">Orden</th>
                        <th>Grupo</th>
                        <th style="width: 28%;">Icono SVG</th>
                        <th style="width: 150px;">Uso</th>
                        <th style="width: 180px;">Acciones</th>
                    </tr>
                    </thead>
                    <tbody>
                    <?php foreach ($linkGroupRows as $idx => $groupRow): ?>
                        <?php $groupIconPreview = catalogo_normalize_svg_markup($groupRow['icon_svg'] ?? null); ?>
                        <tr class="agg-row" data-preview-scope="app-group">
                            <td>
                                <input type="hidden" name="catalog_groups[<?php echo (int)$idx; ?>][id]" value="<?php echo (int)($groupRow['id'] ?? 0); ?>">
                                <input type="number" min="1" name="catalog_groups[<?php echo (int)$idx; ?>][position]" class="form-control agg-position"
                                       value="<?php echo (int)($groupRow['position'] ?? ($idx + 1)); ?>">
                            </td>
                            <td>
                                <input name="catalog_groups[<?php echo (int)$idx; ?>][group_name]" class="form-control agg-name"
                                       value="<?php echo h($groupRow['group_name'] ?? ''); ?>" required>
                            </td>
                            <td>
                                <textarea name="catalog_groups[<?php echo (int)$idx; ?>][icon_svg]" class="form-control agg-icon-svg" rows="3" data-preview-markup placeholder="SVG del grupo"><?php echo h($groupRow['icon_svg'] ?? ''); ?></textarea>
                                <div class="catalogo-mini-preview" data-preview-target style="margin-top:8px;">
                                    <?php echo $groupIconPreview !== null ? $groupIconPreview : '<span class="catalogo-preview-empty">Auto</span>'; ?>
                                </div>
                            </td>
                            <td>
                                <span><?php echo (int)($groupRow['item_count'] ?? 0); ?> <?php echo h(catalogo_admin_group_item_label($type)); ?></span>
                            </td>
                            <td>
                                <div class="ga-actions" style="display:flex; flex-direction:column; gap:10px;">
                                    <label class="ge-check">
                                        <input type="checkbox" class="agg-cb-update" name="catalog_groups[<?php echo (int)$idx; ?>][_update]" value="1">
                                        Actualizar
                                    </label>
                                    <label class="ge-check del">
                                        <input type="checkbox" class="agg-cb-delete" name="catalog_groups[<?php echo (int)$idx; ?>][_delete]" value="1">
                                        Eliminar
                                    </label>
                                </div>
                            </td>
                        </tr>
                    <?php endforeach; ?>

                    <?php if (empty($linkGroupRows)): ?>
                        <tr>
                            <td colspan="5" style="text-align:center; padding: 18px; color: var(--text-muted);">No hay grupos definidos todav&iacute;a.</td>
                        </tr>
                    <?php endif; ?>
                    </tbody>
                </table>

                <div class="admin-actions" style="justify-content:flex-end; margin-top: 10px;">
                    <button class="btn-mini primary" type="submit">Guardar grupos</button>
                </div>
            </form>
        </div>
    </div>

<?php endif; ?>

<?php if ($linkTab === 'items'): ?>

    <div class="card">
        <div class="card-title">
            <span>Añadir enlace</span>
            <span class="opacity-40" style="font-weight:700; font-size: 0.95rem;">Tipo: <?php echo h($types[$type]['label']); ?></span>
        </div>

        <form method="post" id="links-add-form" data-preview-scope="link" style="display:grid; grid-template-columns: repeat(12, 1fr); gap: 14px;">
            <input type="hidden" name="csrf" value="<?php echo h($csrf); ?>">
            <input type="hidden" name="action" value="links_add">
            <input type="hidden" name="type" value="<?php echo h($type); ?>">

            
            <div class="form-group" style="grid-column: span 4;">
                <label for="grupo_select">Grupo</label>
                <select id="grupo_select" name="grupo_select" class="form-control">
                    <?php foreach ($groupNames as $g): ?>
                        <option value="<?php echo h($g); ?>" <?php echo ($g==='General')?'selected':''; ?>><?php echo h($g); ?></option>
                    <?php endforeach; ?>
                </select>
            </div>


            <div class="form-group" style="grid-column: span 8;">
                <label for="descripcion">Descripción</label>
                <input id="descripcion" name="descripcion" class="form-control" placeholder="Texto del enlace" required>
            </div>

            <div class="form-group" style="grid-column: span 8;">
                <label for="destino">Destino (URL)</label>
                <input id="destino" name="destino" class="form-control" placeholder="https://..." required>
            </div>

            <div class="form-group" style="grid-column: span 4;">
                <label for="imagen">Imagen</label>
                <select id="imagen" name="imagen" class="form-control" data-preview-image>
                    <?php foreach ($images as $img): ?>
                        <option value="<?php echo h($img); ?>"><?php echo h($img); ?></option>
                    <?php endforeach; ?>
                </select>
            </div>

            <div class="form-group" style="grid-column: span 5;">
                <label for="icon_svg">Icono SVG (opcional)</label>
                <textarea id="icon_svg" name="icon_svg" class="form-control" rows="4" data-preview-markup placeholder="<svg ...>...</svg>"></textarea>
            </div>

            <div class="form-group" style="grid-column: span 3;">
                <label>Vista previa</label>
                <div class="catalogo-preview-box" data-preview-target>
                    <?php echo admin_icon_preview_markup('/images/default.png', null, 'Vista previa'); ?>
                </div>
            </div>

            <div class="form-group" style="grid-column: span 9;">
                <label for="aviso">Aviso (opcional)</label>
                <input id="aviso" name="aviso" class="form-control" placeholder="Ej: Requiere Sophos">
            </div>

            <div class="form-group" style="grid-column: span 3; display:flex; align-items:end; gap:10px;">
                <label style="display:flex; align-items:center; gap:8px; font-weight:900; margin:0;">
                    <input type="checkbox" name="is_enabled" value="1" checked>
                    Activo
                </label>
                <div style="flex:1;"></div>
                <button type="submit" class="btn-primary-intranet">Guardar enlace</button>
            </div>
        </form>
</div>

    <div class="card">
        <div class="card-title">
            <span>Enlaces existentes</span>
            <span class="opacity-40" style="font-weight:700; font-size: 0.95rem;">Total: <?php echo (int)count($linksPlain); ?></span>
        </div>

        <div class="table-responsive">
            <form method="post" id="bulk-links-form">
                <input type="hidden" name="csrf" value="<?php echo h($csrf); ?>">
                <input type="hidden" name="action" value="links_bulk_save">
                <input type="hidden" name="type" value="<?php echo h($type); ?>">

                <!-- Filtros + Ordenación -->
                <div class="ge-toolbar">
                    <input id="geFilterText" class="form-control" type="text" placeholder="Filtrar (grupo/descr/destino/aviso)…">

                    <select id="geFilterGroup" class="form-control">
                        <option value="">Todos los grupos</option>
                    </select>

                    <select id="geFilterAviso" class="form-control">
                        <option value="">Aviso: todos</option>
                        <option value="1">Solo con aviso</option>
                        <option value="0">Sin aviso</option>
                    </select>

                    <select id="geSortKey" class="form-control">
                        <option value="manual">Orden manual (arrastrar)</option>
                        <option value="grupo">Grupo</option>
                        <option value="descripcion">Descripción</option>
                        <option value="destino">Destino</option>
                        <option value="aviso">Aviso</option>
                        <option value="activo">Activo</option>
                    </select>

                    <select id="geSortDir" class="form-control" style="min-width:140px;">
                        <option value="asc">Ascendente</option>
                        <option value="desc">Descendente</option>
                    </select>

                    <button class="btn-mini" type="button" id="geClear">Limpiar</button>

                    <div style="flex:1;"></div>

                    <button class="btn-mini primary" type="submit">Aplicar todo</button>
                </div>

                <div class="ge-muted" style="margin: 0 0 10px 0;">
                    Consejo: al editar una fila se marcará automáticamente <strong>Actualizar</strong>. Para borrar, marca <strong>Eliminar</strong>.
                </div>

                <table class="admin-table table-sortable" id="geTable">
                    <thead>
                    <tr>
                        <th style="width: 34px;" title="Orden manual" data-sort-type="text">↕</th>
                        <th style="width: 16%;" data-sort-type="text">Grupo</th>
                        <th data-sort-type="text">Descripción</th>
                        <th data-sort-type="text">Destino</th>
                        <th style="width: 24%;">Icono / Aviso / Estado</th>
                        <th style="width: 20%;" data-sort-type="text">Acciones</th>
                    </tr>
                    </thead>
                    <tbody>
                    <?php foreach ($linksPlain as $idx => $l): ?>
                        <tr class="ge-row" draggable="true" data-idx="<?php echo (int)$idx; ?>" data-preview-scope="link">
                            <td class="ge-handle" title="Arrastrar para reordenar">⋮⋮</td>

                            <td>
                                <input type="hidden" name="links[<?php echo (int)$idx; ?>][id]" value="<?php echo (int)($l['id'] ?? 0); ?>">
                                <select name="links[<?php echo (int)$idx; ?>][grupo]" class="form-control ge-grupo">
                                    <?php foreach ($groupNames as $g): ?>
                                        <option value="<?php echo h($g); ?>" <?php echo (($l['grupo'] ?? 'General') === $g) ? 'selected' : ''; ?>><?php echo h($g); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </td>

                            <td>
                                <input name="links[<?php echo (int)$idx; ?>][descripcion]" class="form-control ge-descripcion"
                                       value="<?php echo h($l['descripcion'] ?? ''); ?>" required>
                            </td>

                            <td>
                                <input name="links[<?php echo (int)$idx; ?>][destino]" class="form-control ge-destino"
                                       value="<?php echo h($l['destino'] ?? ''); ?>" required>
                            </td>

                            <td>
                                <select name="links[<?php echo (int)$idx; ?>][imagen]" class="form-control ge-imagen" data-preview-image>
                                    <?php
                                    $curImg = (string)($l['imagen'] ?? '/images/default.png');
                                    foreach ($images as $img):
                                        $sel = ($img === $curImg) ? 'selected' : '';
                                        echo '<option value="' . h($img) . '" ' . $sel . '>' . h($img) . '</option>';
                                    endforeach;
                                    ?>
                                </select>

                                <textarea name="links[<?php echo (int)$idx; ?>][icon_svg]" class="form-control ge-icon-svg" rows="3" data-preview-markup placeholder="Icono SVG" style="margin-top:8px;"><?php echo h($l['icon_svg'] ?? ''); ?></textarea>

                                <div class="catalogo-preview-box" data-preview-target style="margin-top:8px;">
                                    <?php echo admin_icon_preview_markup($l['imagen'] ?? '/images/default.png', $l['icon_svg'] ?? null, 'Icono'); ?>
                                </div>

                                <input name="links[<?php echo (int)$idx; ?>][aviso]" class="form-control ge-aviso"
                                       value="<?php echo h($l['aviso'] ?? ''); ?>"
                                       placeholder="Aviso (opcional)" style="margin-top:8px;">

                                <label style="display:flex; align-items:center; gap:8px; margin-top:8px; font-weight:900;">
                                    <input type="checkbox" class="ge-enabled" name="links[<?php echo (int)$idx; ?>][is_enabled]" value="1" <?php echo !empty($l['is_enabled']) ? 'checked' : ''; ?>>
                                    Activo
                                </label>
                            </td>

                            <td>
                                <div class="ge-actions">
                                    <label class="ge-check">
                                        <input type="checkbox" class="ge-cb-update" name="links[<?php echo (int)$idx; ?>][_update]" value="1">
                                        Actualizar
                                    </label>

                                    <label class="ge-check del">
                                        <input type="checkbox" class="ge-cb-delete" name="links[<?php echo (int)$idx; ?>][_delete]" value="1">
                                        Eliminar
                                    </label>
                                </div>
                            </td>
                        </tr>
                    <?php endforeach; ?>

                    <?php if (empty($linksPlain)): ?>
                        <tr>
                            <td colspan="6" style="text-align:center; padding: 18px; color: var(--text-muted);">No hay enlaces todavía.</td>
                        </tr>
                    <?php endif; ?>
                    </tbody>
                </table>

                <div class="admin-actions" style="justify-content:flex-end; margin-top: 10px;">
                    <button class="btn-mini primary" type="submit">Aplicar todo</button>
                </div>
            </form>
        </div>

        <script>
            (function () {
                const table = document.getElementById('geTable');
                const tbody = table ? table.querySelector('tbody') : null;

                const elFilterText = document.getElementById('geFilterText');
                const elFilterGroup = document.getElementById('geFilterGroup');
                const elFilterAviso = document.getElementById('geFilterAviso');
                const elSortKey = document.getElementById('geSortKey');
                const elSortDir = document.getElementById('geSortDir');
                const btnClear = document.getElementById('geClear');

                function rows() {
                    return Array.from(tbody ? tbody.querySelectorAll('tr.ge-row') : []);
                }

                function getRowData(tr) {
                    const grupo = (tr.querySelector('.ge-grupo')?.value || '').trim();
                    const descripcion = (tr.querySelector('.ge-descripcion')?.value || '').trim();
                    const destino = (tr.querySelector('.ge-destino')?.value || '').trim();
                    const aviso = (tr.querySelector('.ge-aviso')?.value || '').trim();
                    const activo = (tr.querySelector('.ge-enabled')?.checked) ? '1' : '0';
                    return { grupo, descripcion, destino, aviso, activo };
                }

                function normalize(s) {
                    return (s || '').toLowerCase();
                }

                // Renumerar names links[IDX][campo] según el orden DOM
                function renumberNames() {
                    const r = rows();
                    r.forEach((tr, newIdx) => {
                        tr.dataset.idx = String(newIdx);

                        const fields = tr.querySelectorAll('input[name^="links["], select[name^="links["], textarea[name^="links["]');
                        fields.forEach(el => {
                            const name = el.getAttribute('name') || '';
                            const m = name.match(/^links\[\d+\]\[(.+)\]$/);
                            if (m && m[1]) {
                                el.setAttribute('name', `links[${newIdx}][${m[1]}]`);
                            }
                        });
                    });
                }

                // Datalist grupos
                function rebuildGroupFilter() {
                    const set = new Set();
                    rows().forEach(tr => {
                        const g = (tr.querySelector('.ge-grupo')?.value || '').trim();
                        if (g) set.add(g);
                    });
                    const arr = Array.from(set).sort((a,b)=>a.localeCompare(b, undefined, {numeric:true, sensitivity:'base'}));
                    const cur = elFilterGroup.value;
                    elFilterGroup.innerHTML = '<option value="">Todos los grupos</option>';
                    arr.forEach(g => {
                        const opt = document.createElement('option');
                        opt.value = g; opt.textContent = g;
                        elFilterGroup.appendChild(opt);
                    });
                    elFilterGroup.value = cur;
                }

                function applyFilters() {
                    const q = normalize(elFilterText.value.trim());
                    const g = (elFilterGroup.value || '').trim();
                    const avisoMode = elFilterAviso.value;

                    rows().forEach(tr => {
                        const d = getRowData(tr);
                        const hayAviso = d.aviso.trim() !== '';

                        let ok = true;
                        if (q) {
                            const blob = normalize([d.grupo, d.descripcion, d.destino, d.aviso].join(' '));
                            ok = ok && blob.includes(q);
                        }
                        if (g) ok = ok && d.grupo === g;
                        if (avisoMode === '1') ok = ok && hayAviso;
                        if (avisoMode === '0') ok = ok && !hayAviso;

                        tr.style.display = ok ? '' : 'none';
                    });
                }

                function sortRows() {
                    const key = elSortKey.value;
                    const dir = elSortDir.value === 'desc' ? -1 : 1;

                    // Si manual, no tocar
                    if (key === 'manual') {
                        rows().forEach(tr => tr.setAttribute('draggable', 'true'));
                        return;
                    }

                    // Desactivar drag al ordenar por campos
                    rows().forEach(tr => tr.setAttribute('draggable', 'false'));

                    const r = rows();
                    r.sort((a, b) => {
                        const da = getRowData(a);
                        const db = getRowData(b);
                        let va = da[key] || '';
                        let vb = db[key] || '';
                        if (key === 'activo') {
                            va = da.activo;
                            vb = db.activo;
                        }
                        va = normalize(String(va));
                        vb = normalize(String(vb));
                        if (va < vb) return -1 * dir;
                        if (va > vb) return 1 * dir;
                        return 0;
                    });

                    r.forEach(tr => tbody.appendChild(tr));
                    renumberNames();
                    rebuildGroupFilter();
                    applyFilters();
                }

                // Drag & drop
                let dragEl = null;
                rows().forEach(tr => {
                    tr.addEventListener('dragstart', (e) => {
                        if (elSortKey.value !== 'manual') {
                            e.preventDefault();
                            return;
                        }
                        dragEl = tr;
                        tr.classList.add('dragging');
                        e.dataTransfer.effectAllowed = 'move';
                    });
                    tr.addEventListener('dragend', () => {
                        tr.classList.remove('dragging');
                        dragEl = null;
                        renumberNames();
                    });
                    tr.addEventListener('dragover', (e) => {
                        if (!dragEl || elSortKey.value !== 'manual') return;
                        e.preventDefault();
                        const rect = tr.getBoundingClientRect();
                        const next = (e.clientY - rect.top) / (rect.bottom - rect.top) > 0.5;
                        tbody.insertBefore(dragEl, next ? tr.nextSibling : tr);
                    });

                    // Auto-marcar actualizar si cambia algo
                    const updateCb = tr.querySelector('.ge-cb-update');
                    tr.querySelectorAll('input, select, textarea').forEach(inp => {
                        inp.addEventListener('input', () => {
                            if (updateCb) updateCb.checked = true;
                        });
                        inp.addEventListener('change', () => {
                            if (updateCb) updateCb.checked = true;
                        });
                    });
                });

                // Delete marca
                rows().forEach(tr => {
                    const delCb = tr.querySelector('.ge-cb-delete');
                    if (!delCb) return;
                    delCb.addEventListener('change', () => {
                        tr.style.opacity = delCb.checked ? '0.45' : '';
                    });
                });

                // Eventos filtros
                [elFilterText, elFilterGroup, elFilterAviso].forEach(el => {
                    el.addEventListener('input', applyFilters);
                    el.addEventListener('change', applyFilters);
                });

                // Eventos sort
                [elSortKey, elSortDir].forEach(el => {
                    el.addEventListener('change', sortRows);
                });

                btnClear.addEventListener('click', () => {
                    elFilterText.value = '';
                    elFilterGroup.value = '';
                    elFilterAviso.value = '';
                    elSortKey.value = 'manual';
                    elSortDir.value = 'asc';
                    rows().forEach(tr => { tr.style.display = ''; tr.setAttribute('draggable','true'); });
                    rebuildGroupFilter();
                });

                // Init
                rebuildGroupFilter();
                applyFilters();

            })();
        </script>
    </div>

<?php endif; ?>

<?php endif; ?>

<?php if ($section === 'apps'): ?>

    <nav class="catalogo-subtabs" aria-label="Subsecciones de aplicaciones">
        <a class="catalogo-subtab <?php echo $appTab === 'apps' ? 'active' : ''; ?>" href="?section=apps&app_tab=apps">Aplicaciones</a>
        <a class="catalogo-subtab <?php echo $appTab === 'groups' ? 'active' : ''; ?>" href="?section=apps&app_tab=groups">Grupos</a>
    </nav>

<?php if ($appTab === 'groups'): ?>

    <div class="card">
        <div class="card-title">
            <span>Grupos de aplicaciones</span>
            <span class="opacity-40" style="font-weight:700; font-size: 0.95rem;">Catálogo común</span>
        </div>

        <form method="post" data-preview-scope="app-group" style="display:grid; grid-template-columns: repeat(12, 1fr); gap: 14px; margin-bottom:18px;">
            <input type="hidden" name="csrf" value="<?php echo h($csrf); ?>">
            <input type="hidden" name="action" value="app_groups_add">

            <div class="form-group" style="grid-column: span 5;">
                <label for="app_group_name">Nuevo grupo</label>
                <input id="app_group_name" name="group_name" class="form-control" placeholder="Nombre del grupo" required>
            </div>

            <div class="form-group" style="grid-column: span 5;">
                <label for="app_group_icon_svg">Icono SVG del grupo</label>
                <textarea id="app_group_icon_svg" name="icon_svg" class="form-control" rows="3" data-preview-markup placeholder="<svg ...>...</svg>"></textarea>
            </div>

            <div class="form-group" style="grid-column: span 1;">
                <label>Vista</label>
                <div class="catalogo-mini-preview" data-preview-target>
                    <span class="catalogo-preview-empty">SVG</span>
                </div>
            </div>

            <div class="form-group" style="grid-column: span 1; display:flex; align-items:end; justify-content:flex-end;">
                <button type="submit" class="btn-primary-intranet">Añadir</button>
            </div>
        </form>

        <div class="table-responsive">
            <form method="post" id="bulk-app-groups-form">
                <input type="hidden" name="csrf" value="<?php echo h($csrf); ?>">
                <input type="hidden" name="action" value="app_groups_bulk_save">

                <table class="admin-table" id="aggTable">
                    <thead>
                    <tr>
                        <th style="width: 90px;">Orden</th>
                        <th>Grupo</th>
                        <th style="width: 28%;">Icono SVG</th>
                        <th style="width: 140px;">Apps</th>
                        <th style="width: 180px;">Acciones</th>
                    </tr>
                    </thead>
                    <tbody>
                    <?php foreach ($appGroupsRows as $idx => $groupRow): ?>
                        <?php $groupIconPreview = catalogo_normalize_svg_markup($groupRow['icon_svg'] ?? null); ?>
                        <tr class="agg-row" data-preview-scope="app-group">
                            <td>
                                <input type="hidden" name="app_groups[<?php echo (int)$idx; ?>][id]" value="<?php echo (int)($groupRow['id'] ?? 0); ?>">
                                <input type="number" min="1" name="app_groups[<?php echo (int)$idx; ?>][position]" class="form-control agg-position"
                                       value="<?php echo (int)($groupRow['position'] ?? ($idx + 1)); ?>">
                            </td>
                            <td>
                                <input name="app_groups[<?php echo (int)$idx; ?>][group_name]" class="form-control agg-name"
                                       value="<?php echo h($groupRow['group_name'] ?? ''); ?>" required>
                            </td>
                            <td>
                                <textarea name="app_groups[<?php echo (int)$idx; ?>][icon_svg]" class="form-control agg-icon-svg" rows="3" data-preview-markup placeholder="SVG del grupo"><?php echo h($groupRow['icon_svg'] ?? ''); ?></textarea>
                                <div class="catalogo-mini-preview" data-preview-target style="margin-top:8px;">
                                    <?php echo $groupIconPreview !== null ? $groupIconPreview : '<span class="catalogo-preview-empty">Auto</span>'; ?>
                                </div>
                            </td>
                            <td>
                                <span><?php echo (int)($groupRow['app_count'] ?? 0); ?> aplicación(es)</span>
                            </td>
                            <td>
                                <div class="ga-actions" style="display:flex; flex-direction:column; gap:10px;">
                                    <label class="ge-check">
                                        <input type="checkbox" class="agg-cb-update" name="app_groups[<?php echo (int)$idx; ?>][_update]" value="1">
                                        Actualizar
                                    </label>
                                    <label class="ge-check del">
                                        <input type="checkbox" class="agg-cb-delete" name="app_groups[<?php echo (int)$idx; ?>][_delete]" value="1">
                                        Eliminar
                                    </label>
                                </div>
                            </td>
                        </tr>
                    <?php endforeach; ?>

                    <?php if (empty($appGroupsRows)): ?>
                        <tr>
                            <td colspan="5" style="text-align:center; padding: 18px; color: var(--text-muted);">No hay grupos definidos todavía.</td>
                        </tr>
                    <?php endif; ?>
                    </tbody>
                </table>

                <div class="admin-actions" style="justify-content:flex-end; margin-top: 10px;">
                    <button class="btn-mini primary" type="submit">Guardar grupos</button>
                </div>
            </form>
        </div>
    </div>

<?php endif; ?>

<?php if ($appTab === 'apps'): ?>

    <div class="card">
        <div class="card-title">
            <span>Añadir aplicación</span>
            <span class="opacity-40" style="font-weight:700; font-size: 0.95rem;">Catálogo en BD</span>
        </div>

        <form method="post" id="apps-add-form" data-preview-scope="app" style="display:grid; grid-template-columns: repeat(12, 1fr); gap: 14px;">
            <input type="hidden" name="csrf" value="<?php echo h($csrf); ?>">
            <input type="hidden" name="action" value="apps_add">

            
            <div class="form-group" style="grid-column: span 4;">
                <label for="group_name">Grupo</label>
                <select id="group_name" name="group_name" class="form-control">
                    <?php foreach ($appsGroups as $g): ?>
                        <option value="<?php echo h($g); ?>" <?php echo ($g==='Mis apps')?'selected':''; ?>><?php echo h($g); ?></option>
                    <?php endforeach; ?>
                </select>
            </div>


            <div class="form-group" style="grid-column: span 4;">
                <label for="app_id">app_id (único)</label>
                <input id="app_id" name="app_id" class="form-control" placeholder="ej: entregas" required>
            </div>

            <div class="form-group" style="grid-column: span 4;">
                <label for="name">Nombre</label>
                <input id="name" name="name" class="form-control" placeholder="Nombre visible" required>
            </div>

            <div class="form-group" style="grid-column: span 8;">
                <label for="url">URL</label>
                <input id="url" name="url" class="form-control" placeholder="base/miapp/index.php" required>
            </div>

            <div class="form-group" style="grid-column: span 4;">
                <label for="roles">Roles</label>
                <select id="roles" name="roles[]" class="form-control catalogo-role-select" multiple required>
                    <?php foreach ($availableRoles as $role): ?>
                        <option value="<?php echo h($role); ?>" <?php echo $role === 'administradores' ? 'selected' : ''; ?>><?php echo h($role); ?></option>
                    <?php endforeach; ?>
                </select>
                <span class="catalogo-select-help">Ctrl/Cmd + clic para seleccionar varios roles.</span>
            </div>

            <div class="form-group" style="grid-column: span 12;">
                <label for="description">Descripción (opcional)</label>
                <input id="description" name="description" class="form-control" placeholder="Texto corto">
            </div>

            <div class="form-group" style="grid-column: span 4;">
                <label for="image">Imagen (opcional)</label>
                <input id="image" name="image" class="form-control" data-preview-image placeholder="styles/images/xxx.png o URL">
            </div>

            <div class="form-group" style="grid-column: span 5;">
                <label for="icon">Icono (SVG/HTML, opcional)</label>
                <textarea id="icon" name="icon" class="form-control" rows="3" data-preview-markup placeholder="<svg ...>...</svg>"></textarea>
            </div>

            <div class="form-group" style="grid-column: span 3;">
                <label>Vista previa</label>
                <div class="catalogo-preview-box" data-preview-target>
                    <?php echo admin_icon_preview_markup('/images/default.png', null, 'Vista previa'); ?>
                </div>
            </div>

            <div class="form-group" style="grid-column: span 12; display:flex; gap: 18px; align-items:center; justify-content:flex-end;">
                <label style="display:flex; align-items:center; gap:8px; font-weight:900; margin:0;">
                    <input type="checkbox" name="new_tab" value="1">
                    Abrir en nueva pestaña
                </label>
                <label style="display:flex; align-items:center; gap:8px; font-weight:900; margin:0;">
                    <input type="checkbox" name="is_enabled" value="1" checked>
                    Activa
                </label>
                <button type="submit" class="btn-primary-intranet">Guardar aplicación</button>
            </div>
        </form>
</div>

    <div class="card">
        <div class="card-title">
            <span>Aplicaciones existentes</span>
            <span class="opacity-40" style="font-weight:700; font-size: 0.95rem;">Total: <?php echo (int)count($appsPlain); ?></span>
        </div>

        <div class="table-responsive">
            <form method="post" id="bulk-apps-form">
                <input type="hidden" name="csrf" value="<?php echo h($csrf); ?>">
                <input type="hidden" name="action" value="apps_bulk_save">

                <div class="ga-toolbar" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center; margin-bottom:10px;">
                    <input id="gaFilterText" class="form-control" type="text" placeholder="Filtrar (grupo/nombre/url/roles/descr)…" style="min-width:320px;">

                    <select id="gaFilterGroup" class="form-control">
                        <option value="">Todos los grupos</option>
                    </select>

                    <select id="gaSortKey" class="form-control">
                        <option value="manual">Orden manual (arrastrar)</option>
                        <option value="group_name">Grupo</option>
                        <option value="name">Nombre</option>
                        <option value="app_id">app_id</option>
                        <option value="url">URL</option>
                        <option value="roles">Roles</option>
                        <option value="activo">Activa</option>
                    </select>

                    <select id="gaSortDir" class="form-control" style="min-width:140px;">
                        <option value="asc">Ascendente</option>
                        <option value="desc">Descendente</option>
                    </select>

                    <button class="btn-mini" type="button" id="gaClear">Limpiar</button>

                    <div style="flex:1;"></div>

                    <button class="btn-mini primary" type="submit">Aplicar todo</button>
                </div>

                <table class="admin-table table-sortable" id="gaTable">
                    <thead>
                    <tr>
                        <th style="width: 34px;" title="Orden manual" data-sort-type="text">↕</th>
                        <th style="width: 16%;" data-sort-type="text">Grupo</th>
                        <th style="width: 16%;" data-sort-type="text">Nombre</th>
                        <th style="width: 22%;" data-sort-type="text">URL</th>
                        <th style="width: 18%;" data-sort-type="text">Roles</th>
                        <th style="width: 22%;" data-sort-type="text">Detalles</th>
                        <th style="width: 16%;" data-sort-type="text">Acciones</th>
                    </tr>
                    </thead>
                    <tbody>
                    <?php foreach ($appsPlain as $idx => $a): ?>
                        <tr class="ga-row" draggable="true" data-idx="<?php echo (int)$idx; ?>" data-preview-scope="app">
                            <td class="ga-handle" title="Arrastrar para reordenar">⋮⋮</td>

                            <td>
                                <input type="hidden" name="apps[<?php echo (int)$idx; ?>][id]" value="<?php echo (int)($a['id'] ?? 0); ?>">
                                <div style="font-size: 0.85rem; opacity: .85; margin-bottom:6px;"><strong><?php echo h($a['app_id'] ?? ''); ?></strong></div>
                                <select name="apps[<?php echo (int)$idx; ?>][group_name]" class="form-control ga-group">
                                    <?php foreach ($appsGroups as $g): ?>
                                        <option value="<?php echo h($g); ?>" <?php echo (($a['group_name'] ?? 'Mis apps') === $g) ? 'selected' : ''; ?>><?php echo h($g); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </td>

                            <td>
                                <input name="apps[<?php echo (int)$idx; ?>][name]" class="form-control ga-name"
                                       value="<?php echo h($a['name'] ?? ''); ?>" required>
                            </td>

                            <td>
                                <input name="apps[<?php echo (int)$idx; ?>][url]" class="form-control ga-url"
                                       value="<?php echo h($a['url'] ?? ''); ?>" required>
                                <label style="display:flex; align-items:center; gap:8px; margin-top:8px; font-weight:900;">
                                    <input type="checkbox" class="ga-newtab" name="apps[<?php echo (int)$idx; ?>][new_tab]" value="1" <?php echo !empty($a['new_tab']) ? 'checked' : ''; ?>>
                                    Nueva pestaña
                                </label>
                                <label style="display:flex; align-items:center; gap:8px; margin-top:6px; font-weight:900;">
                                    <input type="checkbox" class="ga-enabled" name="apps[<?php echo (int)$idx; ?>][is_enabled]" value="1" <?php echo !empty($a['is_enabled']) ? 'checked' : ''; ?>>
                                    Activa
                                </label>
                            </td>

                            <td>
                                <?php $selectedRoles = catalogo_roles_from_json($a['roles_json'] ?? ''); ?>
                                <?php
                                    $rowRoleOptions = array_values(array_unique(array_merge($availableRoles, $selectedRoles)));
                                    sort($rowRoleOptions, SORT_NATURAL | SORT_FLAG_CASE);
                                ?>
                                <select name="apps[<?php echo (int)$idx; ?>][roles][]" class="form-control ga-roles catalogo-role-select" multiple required>
                                    <?php foreach ($rowRoleOptions as $role): ?>
                                        <option value="<?php echo h($role); ?>" <?php echo in_array($role, $selectedRoles, true) ? 'selected' : ''; ?>><?php echo h($role); ?></option>
                                    <?php endforeach; ?>
                                </select>
                                <span class="catalogo-select-help">Seleccion multiple.</span>
                            </td>

                            <td>
                                <input name="apps[<?php echo (int)$idx; ?>][description]" class="form-control ga-desc" placeholder="Descripción"
                                       value="<?php echo h($a['description'] ?? ''); ?>">
                                <input name="apps[<?php echo (int)$idx; ?>][image]" class="form-control ga-image" data-preview-image placeholder="Imagen" style="margin-top:8px;"
                                       value="<?php echo h($a['image'] ?? ''); ?>">
                                <textarea name="apps[<?php echo (int)$idx; ?>][icon]" class="form-control ga-icon" rows="2" data-preview-markup placeholder="Icono SVG/HTML" style="margin-top:8px;"><?php echo h($a['icon'] ?? ''); ?></textarea>
                                <div class="catalogo-preview-box" data-preview-target style="margin-top:8px;">
                                    <?php echo admin_icon_preview_markup($a['image'] ?? '/images/default.png', $a['icon'] ?? null, 'Icono'); ?>
                                </div>
                            </td>

                            <td>
                                <div class="ga-actions" style="display:flex; flex-direction:column; gap:10px;">
                                    <label class="ge-check">
                                        <input type="checkbox" class="ga-cb-update" name="apps[<?php echo (int)$idx; ?>][_update]" value="1">
                                        Actualizar
                                    </label>
                                    <label class="ge-check del">
                                        <input type="checkbox" class="ga-cb-delete" name="apps[<?php echo (int)$idx; ?>][_delete]" value="1">
                                        Eliminar
                                    </label>
                                </div>
                            </td>
                        </tr>
                    <?php endforeach; ?>

                    <?php if (empty($appsPlain)): ?>
                        <tr>
                            <td colspan="7" style="text-align:center; padding: 18px; color: var(--text-muted);">No hay aplicaciones todavía.</td>
                        </tr>
                    <?php endif; ?>
                    </tbody>
                </table>

                <div class="admin-actions" style="justify-content:flex-end; margin-top: 10px;">
                    <button class="btn-mini primary" type="submit">Aplicar todo</button>
                </div>
            </form>
        </div>

        <script>
            (function () {
                const table = document.getElementById('gaTable');
                const tbody = table ? table.querySelector('tbody') : null;

                const elFilterText = document.getElementById('gaFilterText');
                const elFilterGroup = document.getElementById('gaFilterGroup');
                const elSortKey = document.getElementById('gaSortKey');
                const elSortDir = document.getElementById('gaSortDir');
                const btnClear = document.getElementById('gaClear');

                function rows() {
                    return Array.from(tbody ? tbody.querySelectorAll('tr.ga-row') : []);
                }

                function selectedValues(select) {
                    return Array.from(select ? select.selectedOptions : []).map(opt => opt.value).join(', ');
                }

                function getRowData(tr) {
                    const group_name = (tr.querySelector('.ga-group')?.value || '').trim();
                    const name = (tr.querySelector('.ga-name')?.value || '').trim();
                    const url = (tr.querySelector('.ga-url')?.value || '').trim();
                    const roles = selectedValues(tr.querySelector('.ga-roles')).trim();
                    const desc = (tr.querySelector('.ga-desc')?.value || '').trim();
                    const app_id = (tr.querySelector('td strong')?.textContent || '').trim();
                    const activo = (tr.querySelector('.ga-enabled')?.checked) ? '1' : '0';
                    return { group_name, name, url, roles, desc, app_id, activo };
                }

                function normalize(s) { return (s || '').toLowerCase(); }

                function renumberNames() {
                    const r = rows();
                    r.forEach((tr, newIdx) => {
                        tr.dataset.idx = String(newIdx);
                        const fields = tr.querySelectorAll('input[name^="apps["], select[name^="apps["], textarea[name^="apps["]');
                        fields.forEach(el => {
                            const name = el.getAttribute('name') || '';
                            const m = name.match(/^apps\[\d+\]\[(.+?)\](\[\])?$/);
                            if (m && m[1]) {
                                el.setAttribute('name', `apps[${newIdx}][${m[1]}]${m[2] || ''}`);
                            }
                        });
                    });
                }

                function rebuildGroupFilter() {
                    const set = new Set();
                    rows().forEach(tr => {
                        const g = (tr.querySelector('.ga-group')?.value || '').trim();
                        if (g) set.add(g);
                    });
                    const arr = Array.from(set).sort((a,b)=>a.localeCompare(b, undefined, {numeric:true, sensitivity:'base'}));
                    const cur = elFilterGroup.value;
                    elFilterGroup.innerHTML = '<option value="">Todos los grupos</option>';
                    arr.forEach(g => {
                        const opt = document.createElement('option');
                        opt.value = g; opt.textContent = g;
                        elFilterGroup.appendChild(opt);
                    });
                    elFilterGroup.value = cur;
                }

                function applyFilters() {
                    const q = normalize(elFilterText.value.trim());
                    const g = (elFilterGroup.value || '').trim();

                    rows().forEach(tr => {
                        const d = getRowData(tr);
                        let ok = true;
                        if (q) {
                            const blob = normalize([d.group_name, d.name, d.url, d.roles, d.desc, d.app_id].join(' '));
                            ok = ok && blob.includes(q);
                        }
                        if (g) ok = ok && d.group_name === g;
                        tr.style.display = ok ? '' : 'none';
                    });
                }

                function sortRows() {
                    const key = elSortKey.value;
                    const dir = elSortDir.value === 'desc' ? -1 : 1;

                    if (key === 'manual') {
                        rows().forEach(tr => tr.setAttribute('draggable', 'true'));
                        return;
                    }
                    rows().forEach(tr => tr.setAttribute('draggable', 'false'));

                    const r = rows();
                    r.sort((a, b) => {
                        const da = getRowData(a);
                        const db = getRowData(b);
                        let va = da[key] || '';
                        let vb = db[key] || '';
                        if (key === 'activo') {
                            va = da.activo;
                            vb = db.activo;
                        }
                        va = normalize(String(va));
                        vb = normalize(String(vb));
                        if (va < vb) return -1 * dir;
                        if (va > vb) return 1 * dir;
                        return 0;
                    });

                    r.forEach(tr => tbody.appendChild(tr));
                    renumberNames();
                    rebuildGroupFilter();
                    applyFilters();
                }

                let dragEl = null;
                rows().forEach(tr => {
                    tr.addEventListener('dragstart', (e) => {
                        if (elSortKey.value !== 'manual') {
                            e.preventDefault();
                            return;
                        }
                        dragEl = tr;
                        tr.classList.add('dragging');
                        e.dataTransfer.effectAllowed = 'move';
                    });
                    tr.addEventListener('dragend', () => {
                        tr.classList.remove('dragging');
                        dragEl = null;
                        renumberNames();
                    });
                    tr.addEventListener('dragover', (e) => {
                        if (!dragEl || elSortKey.value !== 'manual') return;
                        e.preventDefault();
                        const rect = tr.getBoundingClientRect();
                        const next = (e.clientY - rect.top) / (rect.bottom - rect.top) > 0.5;
                        tbody.insertBefore(dragEl, next ? tr.nextSibling : tr);
                    });

                    const updateCb = tr.querySelector('.ga-cb-update');
                    tr.querySelectorAll('input, select, textarea').forEach(inp => {
                        inp.addEventListener('input', () => { if (updateCb) updateCb.checked = true; });
                        inp.addEventListener('change', () => { if (updateCb) updateCb.checked = true; });
                    });

                    const delCb = tr.querySelector('.ga-cb-delete');
                    if (delCb) {
                        delCb.addEventListener('change', () => {
                            tr.style.opacity = delCb.checked ? '0.45' : '';
                        });
                    }
                });

                [elFilterText, elFilterGroup].forEach(el => {
                    el.addEventListener('input', applyFilters);
                    el.addEventListener('change', applyFilters);
                });

                [elSortKey, elSortDir].forEach(el => {
                    el.addEventListener('change', sortRows);
                });

                btnClear.addEventListener('click', () => {
                    elFilterText.value = '';
                    elFilterGroup.value = '';
                    elSortKey.value = 'manual';
                    elSortDir.value = 'asc';
                    rows().forEach(tr => { tr.style.display = ''; tr.setAttribute('draggable','true'); });
                    rebuildGroupFilter();
                });

                rebuildGroupFilter();
                applyFilters();

            })();
        </script>
    </div>

<?php endif; ?>

<?php endif; ?>

<script>
    (function () {
        function resolveCatalogImageSrc(image) {
            const value = (image || '').trim();
            if (!value) {
                return '/styles/images/default.png';
            }
            if (/^(?:https?:)?\/\//i.test(value)) {
                return value;
            }
            if (value.indexOf('/styles/') === 0) {
                return value;
            }
            if (value.indexOf('/images/') === 0) {
                return '/styles' + value;
            }
            if (value.indexOf('styles/') === 0) {
                return '/' + value.replace(/^\/+/, '');
            }
            return '/styles/' + value.replace(/^\/+/, '');
        }

        function updatePreview(scope) {
            const target = scope.querySelector('[data-preview-target]');
            if (!target) {
                return;
            }

            const scopeType = scope.getAttribute('data-preview-scope') || '';
            const imageField = scope.querySelector('[data-preview-image]');
            const markupField = scope.querySelector('[data-preview-markup]');
            const markup = (markupField ? markupField.value : '').trim();
            const image = imageField ? imageField.value : '';

            if (markup) {
                target.innerHTML = markup;
                return;
            }

            if (scopeType === 'app-group' && !imageField) {
                target.innerHTML = '<span class="catalogo-preview-empty">Auto</span>';
                return;
            }

            const src = resolveCatalogImageSrc(image);
            target.innerHTML = '<img src="' + src.replace(/"/g, '&quot;') + '" alt="Vista previa">';
        }

        document.querySelectorAll('[data-preview-scope]').forEach((scope) => {
            const watched = scope.querySelectorAll('[data-preview-image], [data-preview-markup]');
            watched.forEach((field) => {
                field.addEventListener('input', () => updatePreview(scope));
                field.addEventListener('change', () => updatePreview(scope));
            });
            updatePreview(scope);
        });

        const groupsForm = document.getElementById('bulk-app-groups-form') || document.getElementById('bulk-catalog-groups-form');
        if (groupsForm) {
            groupsForm.querySelectorAll('.agg-row').forEach((row) => {
                const updateCb = row.querySelector('.agg-cb-update');
                row.querySelectorAll('input, textarea, select').forEach((field) => {
                    field.addEventListener('input', () => {
                        if (updateCb && field !== row.querySelector('.agg-cb-delete')) {
                            updateCb.checked = true;
                        }
                    });
                    field.addEventListener('change', () => {
                        if (updateCb && field !== row.querySelector('.agg-cb-delete')) {
                            updateCb.checked = true;
                        }
                    });
                });

                const deleteCb = row.querySelector('.agg-cb-delete');
                if (deleteCb) {
                    deleteCb.addEventListener('change', () => {
                        row.style.opacity = deleteCb.checked ? '0.45' : '';
                    });
                }
            });
        }
    })();
</script>

<footer>
    <?= date('Y') ?> &copy; <?= h(function_exists('intranet_brand_name') ? intranet_brand_name() : 'Intranet') ?>
</footer>

</body>
</html>
