{% extends "shared/frontend/base.html" %}

{% block title %}Links - BMC Hub{% endblock %}

{% block extra_css %}
<style>
  .links-hero {
    background: linear-gradient(135deg, var(--accent, #0f4c75) 0%, #1a759f 100%);
    color: #fff;
    border-radius: 16px;
    padding: 0.95rem 1rem;
    box-shadow: 0 10px 30px rgba(15, 76, 117, 0.22);
  }

  .links-actions {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
  }

  .links-summary {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 0.55rem;
  }

  .links-summary .summary-card {
    box-shadow: 0 4px 14px rgba(2, 32, 71, 0.06);
  }

  .summary-card {
    border-radius: 12px;
    padding: 0.62rem 0.7rem;
    background: var(--bg-card);
    border: 1px solid rgba(15, 76, 117, 0.12);
  }

  .summary-value {
    font-size: 1.28rem;
    font-weight: 700;
    line-height: 1.1;
  }

  .status-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    border-radius: 999px;
    padding: 0.22rem 0.68rem;
    font-size: 0.79rem;
    font-weight: 600;
  }

  .status-ok {
    color: #0f5132;
    background: #d1e7dd;
  }

  .status-down {
    color: #842029;
    background: #f8d7da;
  }

  .status-unknown {
    color: #664d03;
    background: #fff3cd;
  }

  .links-container {
    padding-bottom: 0.75rem;
  }

  .links-results-meta {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.65rem;
    margin: 0.45rem 0 0.7rem;
    color: var(--text-secondary);
    font-size: 0.84rem;
  }

  .results-chip {
    border: 1px solid rgba(15, 76, 117, 0.2);
    border-radius: 999px;
    padding: 0.16rem 0.5rem;
    color: var(--accent, #0f4c75);
    background: rgba(15, 76, 117, 0.06);
    font-weight: 600;
  }

  .flame-category {
    margin-bottom: 1.2rem;
  }
  .flame-category-title {
    font-size: 0.98rem;
    font-weight: 700;
    margin-bottom: 0.62rem;
    color: var(--text-primary);
    display: flex;
    align-items: center;
    gap: 0.6rem;
    border-bottom: 1px solid rgba(15, 76, 117, 0.1);
    padding-bottom: 0.4rem;
  }
  .flame-category-count {
    font-size: 0.75rem;
    background: rgba(15, 76, 117, 0.1);
    color: var(--accent, #0f4c75);
    padding: 0.15rem 0.5rem;
    border-radius: 999px;
  }

  .flame-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
    gap: 0.6rem;
  }

  .flame-card {
    background: var(--bg-card, #ffffff);
    border: 1px solid rgba(0, 0, 0, 0.08);
    border-radius: 9px;
    padding: 0.48rem 0.58rem;
    display: flex;
    align-items: center;
    gap: 0.7rem;
    position: relative;
    transition: all 0.15s ease-in-out;
    box-shadow: 0 2px 4px rgba(0,0,0,0.01);
  }
  .flame-card:hover {
    background: rgba(15, 76, 117, 0.02);
    border-color: rgba(15, 76, 117, 0.2);
    transform: translateY(-1px);
    box-shadow: 0 4px 10px rgba(0,0,0,0.04);
  }

  .flame-icon-wrap {
    width: 34px;
    height: 34px;
    border-radius: 8px;
    background: rgba(15, 76, 117, 0.06);
    color: var(--accent, #0f4c75);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.05rem;
    flex-shrink: 0;
    position: relative;
  }
  
  .flame-status-dot {
    position: absolute;
    bottom: -2px;
    right: -2px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    border: 2px solid var(--bg-card, #fff);
  }
  .flame-status-dot.ok { background-color: #198754; }
  .flame-status-dot.down { background-color: #dc3545; }
  .flame-status-dot.unknown { background-color: #ffc107; }

  .flame-content {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
  
  .flame-title {
    font-size: 0.87rem;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .flame-title a {
    color: var(--text-primary);
    text-decoration: none;
  }
  .flame-title a::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
  }
  .flame-title a:hover {
    color: var(--accent, #0f4c75);
  }

  .flame-subtitle {
    font-size: 0.66rem;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-top: 0.2rem;
    display: flex;
    align-items: center;
    gap: 0.4rem;
  }

  .flame-actions {
    display: flex;
    gap: 0.25rem;
    opacity: 0;
    position: relative;
    z-index: 2;
    transition: opacity 0.2s;
  }
  .flame-card:hover .flame-actions {
    opacity: 1;
  }
  .flame-actions .btn {
    padding: 0.25rem 0.45rem;
    font-size: 0.8rem;
    background: var(--bg-card, #fff);
  }

  .links-table td, .links-table th {
    vertical-align: middle;
  }

  .links-table thead th {
    font-size: 0.76rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-secondary);
    white-space: nowrap;
    padding-top: 0.55rem;
    padding-bottom: 0.55rem;
  }

  .links-table tbody td {
    padding-top: 0.4rem;
    padding-bottom: 0.4rem;
    font-size: 0.86rem;
  }

  .links-table .btn.btn-sm {
    padding: 0.22rem 0.45rem;
    font-size: 0.75rem;
  }

  .link-main {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    min-width: 0;
  }

  .link-name {
    font-weight: 600;
    font-size: 0.92rem;
    line-height: 1.1;
  }

  .link-env {
    border-radius: 999px;
    padding: 0.1rem 0.4rem;
    background: rgba(15, 76, 117, 0.1);
    color: #0f4c75;
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
  }

  .target-text {
    display: inline-block;
    max-width: 360px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .search-shell {
    border: 1px solid rgba(15, 76, 117, 0.2);
    border-radius: 12px;
    background: linear-gradient(180deg, rgba(255,255,255,0.95), rgba(255,255,255,0.9));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
  }

  .search-shell .input-group-text,
  .search-shell .btn,
  .search-shell .form-control {
    border: none;
    background: transparent;
  }

  .search-shell .input-group-text {
    color: var(--accent, #0f4c75);
  }

  .search-shell:focus-within {
    box-shadow: 0 0 0 0.2rem rgba(15, 76, 117, 0.14);
    border-color: rgba(15, 76, 117, 0.45);
  }

  .search-shell .btn {
    color: var(--text-secondary);
  }

  .links-filter-card .card-body {
    padding: 0.62rem 0.78rem;
  }

  .links-filter-card .form-label {
    margin-bottom: 0.28rem;
  }

  .links-filter-card .form-select,
  .links-filter-card .form-control {
    padding-top: 0.38rem;
    padding-bottom: 0.38rem;
    font-size: 0.9rem;
  }

  .view-toggle {
    display: inline-flex;
    border: 1px solid rgba(15, 76, 117, 0.18);
    border-radius: 10px;
    overflow: hidden;
    width: 100%;
    background: var(--bg-card);
  }

  .view-toggle .btn {
    border: 0;
    border-radius: 0;
    flex: 1;
    font-size: 0.84rem;
    padding: 0.38rem 0.45rem;
    color: var(--text-secondary);
    background: transparent;
  }

  .view-toggle .btn.active {
    background: rgba(15, 76, 117, 0.12);
    color: var(--accent, #0f4c75);
    font-weight: 600;
  }

  .links-list-card {
    border: 1px solid rgba(15, 76, 117, 0.12);
    border-radius: 12px;
    overflow: hidden;
    background: var(--bg-card);
    box-shadow: 0 8px 22px rgba(2, 32, 71, 0.06);
  }

  .links-list-table {
    margin-bottom: 0;
  }

  .links-list-table thead th {
    font-size: 0.74rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-secondary);
    background: rgba(15, 76, 117, 0.03);
    border-bottom: 1px solid rgba(15, 76, 117, 0.12);
    padding-top: 0.62rem;
    padding-bottom: 0.62rem;
    white-space: nowrap;
  }

  .links-list-table tbody td {
    vertical-align: middle;
    border-color: rgba(15, 76, 117, 0.08);
    padding-top: 0.52rem;
    padding-bottom: 0.52rem;
  }

  .links-list-table tbody tr:hover {
    background: rgba(15, 76, 117, 0.04);
  }

  .list-name {
    font-weight: 600;
    color: var(--text-primary);
    text-decoration: none;
  }

  .list-name:hover {
    color: var(--accent, #0f4c75);
  }

  .list-actions {
    display: flex;
    gap: 0.28rem;
    justify-content: flex-end;
  }

  .list-actions .btn {
    padding: 0.2rem 0.42rem;
    font-size: 0.78rem;
  }

  .mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
    font-size: 0.86rem;
  }

  @media (max-width: 767.98px) {
    .links-hero {
      border-radius: 12px;
      padding: 0.72rem 0.8rem;
    }

    .target-text {
      max-width: 170px;
    }

    .links-results-meta {
      flex-direction: column;
      align-items: flex-start;
    }
  }
</style>
{% endblock %}

{% block content %}
<div class="container-fluid py-3">
  <div class="links-hero mb-3">
    <div class="d-flex flex-column flex-lg-row justify-content-between gap-3 align-items-lg-center">
      <div>
        <h1 class="h4 mb-1 text-white">Links / Endpoints</h1>
        <p class="mb-0 opacity-75">Operationelt overblik over adgangspunkter med live status-check.</p>
        <p class="mb-0 small opacity-75" id="scopeHint"></p>
      </div>
      <div class="links-actions">
        <button id="createBtn" class="btn btn-warning btn-sm"><i class="bi bi-plus-lg"></i> Nyt link</button>
        <button id="refreshBtn" class="btn btn-light btn-sm"><i class="bi bi-arrow-clockwise"></i> Opdater</button>
        <button id="runHealthBtn" class="btn btn-outline-light btn-sm"><i class="bi bi-heart-pulse"></i> Kør health check</button>
      </div>
    </div>
  </div>

  <div class="links-summary mb-2">
    <div class="summary-card">
      <div class="text-muted small">Links i alt</div>
      <div id="totalCount" class="summary-value">0</div>
    </div>
    <div class="summary-card">
      <div class="text-muted small">OK</div>
      <div id="okCount" class="summary-value text-success">0</div>
    </div>
    <div class="summary-card">
      <div class="text-muted small">Down</div>
      <div id="downCount" class="summary-value text-danger">0</div>
    </div>
    <div class="summary-card">
      <div class="text-muted small">Unknown</div>
      <div id="unknownCount" class="summary-value text-warning">0</div>
    </div>
  </div>

  <div class="card border-0 shadow-sm mb-2 links-filter-card">
    <div class="card-body">
      <div class="row g-2 align-items-end">
        <div class="col-lg-5">
          <label for="searchInput" class="form-label small text-muted">Søg</label>
          <div class="input-group search-shell">
            <span class="input-group-text"><i class="bi bi-search"></i></span>
            <input id="searchInput" class="form-control" placeholder="Find links hurtigt (navn, URL, host, kategori)" />
            <button id="clearSearchBtn" class="btn btn-sm" type="button" title="Ryd søgning"><i class="bi bi-x-lg"></i></button>
          </div>
        </div>
        <div class="col-lg-2 col-md-4">
          <label for="statusFilter" class="form-label small text-muted">Status</label>
          <select id="statusFilter" class="form-select">
            <option value="all">Alle</option>
            <option value="ok">OK</option>
            <option value="down">Down</option>
            <option value="unknown">Unknown</option>
          </select>
        </div>
        <div class="col-lg-3 col-md-4">
          <label for="categoryFilter" class="form-label small text-muted">Kategori</label>
          <select id="categoryFilter" class="form-select">
            <option value="all">Alle kategorier</option>
          </select>
        </div>
        <div class="col-lg-2 col-md-4">
          <label class="form-label small text-muted">Visning</label>
          <div class="view-toggle" role="group" aria-label="Skift visning">
            <button class="btn active" id="cardsViewBtn" type="button"><i class="bi bi-grid-3x2-gap me-1"></i>Kort</button>
            <button class="btn" id="listViewBtn" type="button"><i class="bi bi-list-ul me-1"></i>Liste</button>
          </div>
        </div>
      </div>
    </div>
  </div>

  <div class="links-results-meta">
    <div id="resultsMeta">Viser 0 links</div>
    <div class="results-chip" id="resultsScope">Scope: Alle</div>
  </div>

  <div class="links-container">
  <div id="linksColumns">
    <div class="text-muted small">Henter links...</div>
  </div>
  </div>
</div>

<div class="modal fade" id="linkModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-lg modal-dialog-scrollable">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="linkModalTitle">Nyt link</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <form id="linkForm" class="row g-3">
          <input type="hidden" id="linkId" />
          <div class="col-md-8">
            <label class="form-label">Navn</label>
            <input class="form-control" id="fName" required />
          </div>
          <div class="col-md-4">
            <label class="form-label">Type</label>
            <select class="form-select" id="fType" required>
              <option value="http">HTTP</option>
              <option value="ssh">SSH</option>
              <option value="rdp">RDP</option>
              <option value="command">Command</option>
            </select>
          </div>
          <div class="col-md-8">
            <label class="form-label">URL</label>
            <input class="form-control" id="fUrl" placeholder="https://..." />
          </div>
          <div class="col-md-4">
            <label class="form-label">Miljø</label>
            <select class="form-select" id="fEnvironment">
              <option value="prod">Prod</option>
              <option value="test">Test</option>
              <option value="dev">Dev</option>
            </select>
          </div>
          <div class="col-md-6">
            <label class="form-label">Host</label>
            <input class="form-control" id="fHost" />
          </div>
          <div class="col-md-3">
            <label class="form-label">Port</label>
            <input class="form-control" id="fPort" type="number" min="1" max="65535" />
          </div>
          <div class="col-md-3">
            <label class="form-label">Brugernavn</label>
            <input class="form-control" id="fUsername" />
          </div>
          <div class="col-md-6">
            <label class="form-label">Vault item ID</label>
            <input class="form-control" id="fVaultItemId" placeholder="fx 2f8f6c7d-..." />
          </div>
          <div class="col-md-6">
            <label class="form-label">Vault item IDs (ekstra)</label>
            <input class="form-control" id="fVaultItemIds" placeholder="id1, id2, id3" />
            <div class="form-text">Kommasepareret liste af fallback item IDs.</div>
          </div>
          <div class="col-md-4">
            <label class="form-label">Kunde ID</label>
            <input class="form-control" id="fCustomerId" type="number" min="1" />
          </div>
          <div class="col-md-4">
            <label class="form-label">Sag ID</label>
            <input class="form-control" id="fCaseId" type="number" min="1" />
          </div>
          <div class="col-md-4">
            <label class="form-label">Hardware ID</label>
            <input class="form-control" id="fHardwareId" type="number" min="1" />
          </div>
          <div class="col-12">
            <label class="form-label">Kategorier</label>
            <select class="form-select" id="fCategoryIds" multiple size="6"></select>
          </div>
          <div class="col-12 d-flex gap-3">
            <div class="form-check">
              <input class="form-check-input" type="checkbox" id="fIsCritical" />
              <label class="form-check-label" for="fIsCritical">Kritisk</label>
            </div>
            <div class="form-check">
              <input class="form-check-input" type="checkbox" id="fIsFavorite" />
              <label class="form-check-label" for="fIsFavorite">Favorit</label>
            </div>
          </div>
        </form>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Annuller</button>
        <button type="button" class="btn btn-primary" id="saveBtn">Gem</button>
      </div>
    </div>
  </div>
</div>

<div class="modal fade" id="vaultModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Vault credentials</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <div id="vaultState" class="text-muted">Henter vault-oplysninger...</div>
        <div id="vaultContent" class="d-none">
          <div class="mb-2">
            <label class="form-label small text-muted mb-1">Item</label>
            <div class="fw-semibold" id="vaultItemName">-</div>
          </div>
          <div class="mb-2">
            <label class="form-label small text-muted mb-1">Username</label>
            <div class="input-group input-group-sm">
              <input id="vaultUsername" class="form-control" readonly />
              <button class="btn btn-outline-secondary" type="button" onclick="copyVaultField('vaultUsername')">Kopi</button>
            </div>
          </div>
          <div class="mb-2">
            <label class="form-label small text-muted mb-1">Password</label>
            <div class="input-group input-group-sm">
              <input id="vaultPassword" class="form-control" readonly />
              <button class="btn btn-outline-secondary" type="button" onclick="copyVaultField('vaultPassword')">Kopi</button>
            </div>
          </div>
          <div class="mb-2">
            <label class="form-label small text-muted mb-1">TOTP</label>
            <div class="input-group input-group-sm">
              <input id="vaultTotp" class="form-control" readonly />
              <button class="btn btn-outline-secondary" type="button" onclick="copyVaultField('vaultTotp')">Kopi</button>
            </div>
          </div>
          <div>
            <label class="form-label small text-muted mb-1">URL</label>
            <input id="vaultUrl" class="form-control form-control-sm" readonly />
          </div>
        </div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Luk</button>
      </div>
    </div>
  </div>
</div>
{% endblock %}

{% block extra_js %}
<script>
  const urlParams = new URLSearchParams(window.location.search);
  const customerIdFilter = urlParams.get('customer_id');

  const state = {
    links: [],
    statuses: new Map(),
    categories: [],
    viewMode: localStorage.getItem('linksViewMode') || 'cards',
  };

  let linkModal;
  let vaultModal;

  function authHeaders() {
    const token = localStorage.getItem('access_token') || sessionStorage.getItem('access_token');
    return token ? { Authorization: `Bearer ${token}` } : {};
  }

  async function apiGet(path) {
    const response = await fetch(path, {
      headers: { ...authHeaders() },
      credentials: 'include'
    });
    if (!response.ok) {
      if (response.status === 401 || response.status === 403) {
        throw new Error('Ingen adgang. Log ind igen eller tjek links.read permission.');
      }
      if (response.status === 404) {
        throw new Error('Links-endpoint ikke fundet. Bekraeft at modulet er aktiveret og API er recreatet.');
      }
      throw new Error(`HTTP ${response.status}`);
    }
    return response.json();
  }

  async function apiRequest(path, method, body) {
    const response = await fetch(path, {
      method,
      headers: {
        ...authHeaders(),
        'Content-Type': 'application/json'
      },
      credentials: 'include',
      body: body ? JSON.stringify(body) : undefined
    });

    if (!response.ok) {
      let detail = `HTTP ${response.status}`;
      try {
        const payload = await response.json();
        if (payload && payload.detail) {
          detail = typeof payload.detail === 'string' ? payload.detail : JSON.stringify(payload.detail);
        }
      } catch (err) {
        void err;
      }
      throw new Error(detail);
    }

    if (response.status === 204) {
      return null;
    }
    return response.json();
  }

  function escapeHtml(value) {
    const span = document.createElement('span');
    span.textContent = value ?? '';
    return span.innerHTML;
  }

  function statusDot(status) {
    const value = status || 'unknown';
    return `<span class="status-dot ${value}" title="Status: ${value}"></span>`;
  }

  function resolveTarget(link) {
    if (link.type === 'http') return link.url || link.host || '-';
    if (link.type === 'command') return link.description || link.url || '-';
    const host = link.host || '-';
    const port = link.port ? `:${link.port}` : '';
    return `${host}${port}`;
  }

  function buildOpenHref(link) {
    if (link.url && String(link.url).trim()) {
      return String(link.url).trim();
    }
    if (link.host && String(link.host).trim()) {
      const raw = String(link.host).trim();
      if (/^https?:\/\//i.test(raw)) {
        return raw;
      }
      if (link.type === 'http') {
        return `http://${raw}`;
      }
      return `//${raw}`;
    }
    return '';
  }

  function formatDate(value) {
    if (!value) return '-';
    const dt = new Date(value);
    if (Number.isNaN(dt.getTime())) return '-';
    return dt.toLocaleString('da-DK');
  }

  function applyFilters() {
    const q = (document.getElementById('searchInput').value || '').toLowerCase().trim();
    const statusFilter = document.getElementById('statusFilter').value;
    const categoryFilter = document.getElementById('categoryFilter').value;

    const rows = state.links.filter((link) => {
      const statusRow = state.statuses.get(link.id);
      const status = statusRow ? statusRow.status : 'unknown';

      if (statusFilter !== 'all' && status !== statusFilter) {
        return false;
      }

      if (categoryFilter !== 'all') {
        const ids = Array.isArray(link.category_ids) ? link.category_ids.map((v) => String(v)) : [];
        if (!ids.includes(String(categoryFilter))) {
          return false;
        }
      }

      if (!q) return true;
      const categoryNames = resolveCategoryNamesForLink(link).join(' ');
      const hay = `${link.name || ''} ${link.url || ''} ${link.host || ''} ${link.environment || ''} ${link.type || ''} ${categoryNames}`.toLowerCase();
      return hay.includes(q);
    });

    renderResultsMeta(rows.length);
    renderTable(rows);
  }

  function renderResultsMeta(filteredCount) {
    const total = state.links.length;
    const statusFilter = document.getElementById('statusFilter').value;
    const categorySelect = document.getElementById('categoryFilter');
    const categoryLabel = categorySelect?.selectedOptions?.[0]?.textContent || 'Alle kategorier';

    document.getElementById('resultsMeta').textContent = `Viser ${filteredCount} af ${total} links`;

    const statusText = statusFilter === 'all' ? 'Alle statusser' : statusFilter.toUpperCase();
    document.getElementById('resultsScope').textContent = `Status: ${statusText} · ${categoryLabel}`;
  }

  function categoryNameForLink(link) {
    const ids = Array.isArray(link.category_ids) ? link.category_ids : [];
    if (!ids.length) {
      return 'Ukategoriseret';
    }
    const first = state.categories.find((cat) => Number(cat.id) === Number(ids[0]));
    return first ? first.name : `Kategori ${ids[0]}`;
  }

  function resolveCategoryNamesForLink(link) {
    const ids = Array.isArray(link.category_ids) ? link.category_ids : [];
    if (!ids.length) {
      return ['Ukategoriseret'];
    }

    const names = ids.map((id) => {
      const match = state.categories.find((cat) => Number(cat.id) === Number(id));
      return match ? String(match.name || '') : `Kategori ${id}`;
    }).filter(Boolean);

    return names.length ? names : ['Ukategoriseret'];
  }

  function getTypeIcon(type) {
    if (type === 'http') return 'bi-globe2';
    if (type === 'ssh') return 'bi-terminal';
    if (type === 'rdp') return 'bi-pc-display';
    return 'bi-link-45deg';
  }

  function renderColumns(rows) {
    const container = document.getElementById('linksColumns');
    if (!rows.length) {
      container.innerHTML = '<div class="text-muted text-center py-4">Ingen links fundet.</div>';
      return;
    }

    const groups = new Map();
    rows.forEach((link) => {
      const category = categoryNameForLink(link);
      if (!groups.has(category)) {
        groups.set(category, []);
      }
      groups.get(category).push(link);
    });

    const sortedGroupNames = Array.from(groups.keys()).sort((a, b) => a.localeCompare(b, 'da-DK'));

    container.innerHTML = sortedGroupNames.map((groupName) => {
      const items = groups.get(groupName).sort((a, b) => String(a.name || '').localeCompare(String(b.name || ''), 'da-DK'));
      
      const cards = items.map((link) => {
        const statusRow = state.statuses.get(link.id);
        const hasVault = Boolean(link.vault_item_id) || (Array.isArray(link.vault_item_ids) && link.vault_item_ids.length > 0);
        const openHref = buildOpenHref(link);
        const canOpen = Boolean(openHref);
        const st = statusRow ? statusRow.status : 'unknown';
        const typeIcon = getTypeIcon(link.type);

        return `
          <div class="flame-card">
            <div class="flame-icon-wrap">
              <i class="bi ${typeIcon}"></i>
              <div class="flame-status-dot ${st}" title="Status: ${st}"></div>
            </div>
            <div class="flame-content">
              <div class="flame-title">
                ${canOpen
                  ? `<a href="${escapeHtml(openHref)}" target="_blank" rel="noopener noreferrer" title="${escapeHtml(link.name || '')}">${escapeHtml(link.name || '-')}</a>`
                  : `<span>${escapeHtml(link.name || '-')}</span>`}
              </div>
              <div class="flame-subtitle">
                <span>${escapeHtml((link.type || 'http').toUpperCase())}</span>
                ${link.is_critical ? '<span class="text-danger fw-bold ms-1" title="Kritisk"><i class="bi bi-exclamation-triangle-fill"></i></span>' : ''}
                ${link.is_favorite ? '<span class="text-warning ms-1" title="Favorit"><i class="bi bi-star-fill"></i></span>' : ''}
              </div>
            </div>
            <div class="flame-actions">
              ${hasVault ? `<button class="btn btn-sm text-primary" onclick="resolveVault(${link.id}); event.preventDefault();" title="Hent Vault Credentials"><i class="bi bi-shield-lock"></i></button>` : ''}
              <button class="btn btn-sm text-secondary" onclick="openEditModal(${link.id}); event.preventDefault();" title="Rediger"><i class="bi bi-pencil"></i></button>
              <button class="btn btn-sm text-danger" onclick="deleteLink(${link.id}, '${escapeHtml(link.name || '')}'); event.preventDefault();" title="Slet"><i class="bi bi-trash"></i></button>
            </div>
          </div>
        `;
      }).join('');

      return `
        <div class="flame-category">
          <h4 class="flame-category-title">
            ${escapeHtml(groupName)}
            <span class="flame-category-count">${items.length}</span>
          </h4>
          <div class="flame-grid">
            ${cards}
          </div>
        </div>
      `;
    }).join('');
  }

  function renderList(rows) {
    const container = document.getElementById('linksColumns');
    if (!rows.length) {
      container.innerHTML = '<div class="text-muted text-center py-4">Ingen links fundet.</div>';
      return;
    }

    const ordered = [...rows].sort((a, b) => String(a.name || '').localeCompare(String(b.name || ''), 'da-DK'));

    const rowsHtml = ordered.map((link) => {
      const statusRow = state.statuses.get(link.id);
      const status = statusRow ? statusRow.status : 'unknown';
      const names = resolveCategoryNamesForLink(link);
      const category = names[0] || 'Ukategoriseret';
      const target = resolveTarget(link);
      const openHref = buildOpenHref(link);
      const canOpen = Boolean(openHref);
      const hasVault = Boolean(link.vault_item_id) || (Array.isArray(link.vault_item_ids) && link.vault_item_ids.length > 0);
      const env = String(link.environment || 'prod').toUpperCase();

      return `
        <tr>
          <td>
            <div class="d-flex align-items-center gap-2">
              <span class="flame-status-dot ${status}" title="Status: ${status}"></span>
              ${canOpen
                ? `<a class="list-name" href="${escapeHtml(openHref)}" target="_blank" rel="noopener noreferrer">${escapeHtml(link.name || '-')}</a>`
                : `<span class="list-name">${escapeHtml(link.name || '-')}</span>`}
            </div>
          </td>
          <td><span class="mono target-text" title="${escapeHtml(target)}">${escapeHtml(target)}</span></td>
          <td><span class="link-env">${escapeHtml(env)}</span></td>
          <td><span class="small text-muted">${escapeHtml(category)}</span></td>
          <td>${statusPill(status)}</td>
          <td class="text-end">
            <div class="list-actions">
              ${hasVault ? `<button class="btn btn-sm btn-outline-primary" onclick="resolveVault(${link.id}); event.preventDefault();" title="Vault"><i class="bi bi-shield-lock"></i></button>` : ''}
              <button class="btn btn-sm btn-outline-secondary" onclick="openEditModal(${link.id}); event.preventDefault();" title="Rediger"><i class="bi bi-pencil"></i></button>
              <button class="btn btn-sm btn-outline-danger" onclick="deleteLink(${link.id}, '${escapeHtml(link.name || '')}'); event.preventDefault();" title="Slet"><i class="bi bi-trash"></i></button>
            </div>
          </td>
        </tr>
      `;
    }).join('');

    container.innerHTML = `
      <div class="links-list-card">
        <div class="table-responsive">
          <table class="table links-list-table">
            <thead>
              <tr>
                <th>Navn</th>
                <th>Target</th>
                <th>Miljø</th>
                <th>Kategori</th>
                <th>Status</th>
                <th class="text-end">Handlinger</th>
              </tr>
            </thead>
            <tbody>
              ${rowsHtml}
            </tbody>
          </table>
        </div>
      </div>
    `;
  }

  function statusPill(status) {
    if (status === 'ok') return '<span class="status-pill status-ok">OK</span>';
    if (status === 'down') return '<span class="status-pill status-down">Down</span>';
    return '<span class="status-pill status-unknown">Unknown</span>';
  }

  function renderTable(rows) {
    if (state.viewMode === 'list') {
      renderList(rows);
      return;
    }
    renderColumns(rows);
  }

  function setViewMode(mode) {
    state.viewMode = mode === 'list' ? 'list' : 'cards';
    localStorage.setItem('linksViewMode', state.viewMode);

    document.getElementById('cardsViewBtn').classList.toggle('active', state.viewMode === 'cards');
    document.getElementById('listViewBtn').classList.toggle('active', state.viewMode === 'list');

    applyFilters();
  }

  function populateCategoryFilter() {
    const select = document.getElementById('categoryFilter');
    const previousValue = select.value || 'all';

    const options = state.categories
      .slice()
      .sort((a, b) => String(a.name || '').localeCompare(String(b.name || ''), 'da-DK'))
      .map((item) => `<option value="${item.id}">${escapeHtml(item.name)}</option>`)
      .join('');

    select.innerHTML = `<option value="all">Alle kategorier</option>${options}`;

    if (previousValue !== 'all' && Array.from(select.options).some((opt) => opt.value === previousValue)) {
      select.value = previousValue;
    }
  }

  function renderSummary() {
    const counts = { ok: 0, down: 0, unknown: 0 };
    state.links.forEach((link) => {
      const status = state.statuses.get(link.id)?.status || 'unknown';
      if (status === 'ok') counts.ok += 1;
      else if (status === 'down') counts.down += 1;
      else counts.unknown += 1;
    });

    document.getElementById('totalCount').textContent = String(state.links.length);
    document.getElementById('okCount').textContent = String(counts.ok);
    document.getElementById('downCount').textContent = String(counts.down);
    document.getElementById('unknownCount').textContent = String(counts.unknown);
  }

  async function loadData() {
    try {
      const linksPath = customerIdFilter
        ? `/api/v1/links?customer_id=${encodeURIComponent(customerIdFilter)}`
        : '/api/v1/links';

      const [links, latest] = await Promise.all([
        apiGet(linksPath),
        apiGet('/api/v1/links/status/latest')
      ]);
      state.links = Array.isArray(links) ? links : [];
      state.statuses = new Map((Array.isArray(latest) ? latest : []).map((row) => [row.link_id, row]));
      renderSummary();
      applyFilters();
    } catch (error) {
      const container = document.getElementById('linksColumns');
      container.innerHTML = `<div class="text-danger py-4 text-center">Kunne ikke indlæse links: ${escapeHtml(error.message || 'ukendt fejl')}</div>`;
    }
  }

  async function loadCategories() {
    try {
      state.categories = await apiGet('/api/v1/links/categories');
    } catch (error) {
      state.categories = [];
    }
    const select = document.getElementById('fCategoryIds');
    select.innerHTML = state.categories.map((item) => `<option value="${item.id}">${escapeHtml(item.name)}</option>`).join('');
    populateCategoryFilter();

    if (state.links.length) {
      applyFilters();
    }
  }

  function getFormPayload() {
    const toOptionalInt = (value) => {
      const raw = String(value || '').trim();
      if (!raw) return null;
      const parsed = Number(raw);
      return Number.isFinite(parsed) ? parsed : null;
    };

    const selectedCategories = Array.from(document.getElementById('fCategoryIds').selectedOptions).map((o) => Number(o.value));
    const vaultItemIds = (document.getElementById('fVaultItemIds').value || '')
      .split(',')
      .map((item) => item.trim())
      .filter((item) => item.length > 0);

    return {
      name: document.getElementById('fName').value.trim(),
      type: document.getElementById('fType').value,
      url: document.getElementById('fUrl').value.trim() || null,
      host: document.getElementById('fHost').value.trim() || null,
      port: toOptionalInt(document.getElementById('fPort').value),
      username: document.getElementById('fUsername').value.trim() || null,
      vault_item_id: document.getElementById('fVaultItemId').value.trim() || null,
      vault_item_ids: vaultItemIds,
      customer_id: toOptionalInt(document.getElementById('fCustomerId').value),
      case_id: toOptionalInt(document.getElementById('fCaseId').value),
      hardware_id: toOptionalInt(document.getElementById('fHardwareId').value),
      environment: document.getElementById('fEnvironment').value,
      category_ids: selectedCategories,
      is_critical: document.getElementById('fIsCritical').checked,
      is_favorite: document.getElementById('fIsFavorite').checked,
    };
  }

  function resetFormForCreate() {
    document.getElementById('linkModalTitle').textContent = 'Nyt link';
    document.getElementById('linkId').value = '';
    document.getElementById('linkForm').reset();
    document.getElementById('fEnvironment').value = 'prod';
    if (customerIdFilter) {
      document.getElementById('fCustomerId').value = customerIdFilter;
    }
    Array.from(document.getElementById('fCategoryIds').options).forEach((opt) => {
      opt.selected = false;
    });
  }

  function fillFormForEdit(link) {
    document.getElementById('linkModalTitle').textContent = 'Rediger link';
    document.getElementById('linkId').value = link.id;
    document.getElementById('fName').value = link.name || '';
    document.getElementById('fType').value = link.type || 'http';
    document.getElementById('fUrl').value = link.url || '';
    document.getElementById('fHost').value = link.host || '';
    document.getElementById('fPort').value = link.port || '';
    document.getElementById('fUsername').value = link.username || '';
    document.getElementById('fVaultItemId').value = link.vault_item_id || '';
    document.getElementById('fVaultItemIds').value = Array.isArray(link.vault_item_ids) ? link.vault_item_ids.join(', ') : '';
    document.getElementById('fCustomerId').value = link.customer_id || '';
    document.getElementById('fCaseId').value = link.case_id || '';
    document.getElementById('fHardwareId').value = link.hardware_id || '';
    document.getElementById('fEnvironment').value = link.environment || 'prod';
    document.getElementById('fIsCritical').checked = Boolean(link.is_critical);
    document.getElementById('fIsFavorite').checked = Boolean(link.is_favorite);

    const selected = new Set(link.category_ids || []);
    Array.from(document.getElementById('fCategoryIds').options).forEach((opt) => {
      opt.selected = selected.has(Number(opt.value));
    });
  }

  async function saveLink() {
    const saveBtn = document.getElementById('saveBtn');
    const linkId = document.getElementById('linkId').value;
    const payload = getFormPayload();

    if (!payload.name) {
      alert('Navn er paakraevet.');
      return;
    }

    saveBtn.disabled = true;
    saveBtn.textContent = 'Gemmer...';
    try {
      if (linkId) {
        await apiRequest(`/api/v1/links/${encodeURIComponent(linkId)}`, 'PATCH', payload);
      } else {
        await apiRequest('/api/v1/links', 'POST', payload);
      }
      linkModal.hide();
      await loadData();
    } catch (error) {
      alert(`Kunne ikke gemme link: ${error.message || 'ukendt fejl'}`);
    } finally {
      saveBtn.disabled = false;
      saveBtn.textContent = 'Gem';
    }
  }

  window.openEditModal = function openEditModal(linkId) {
    const row = state.links.find((item) => Number(item.id) === Number(linkId));
    if (!row) return;
    fillFormForEdit(row);
    linkModal.show();
  };

  window.deleteLink = async function deleteLink(linkId, linkName) {
    const confirmed = window.confirm(`Slet link "${linkName || ''}"?`);
    if (!confirmed) return;

    try {
      await apiRequest(`/api/v1/links/${encodeURIComponent(linkId)}`, 'DELETE');
      await loadData();
    } catch (error) {
      alert(`Kunne ikke slette link: ${error.message || 'ukendt fejl'}`);
    }
  };

  function setVaultLoading(message) {
    document.getElementById('vaultState').classList.remove('d-none');
    document.getElementById('vaultState').textContent = message;
    document.getElementById('vaultContent').classList.add('d-none');
  }

  function showVaultCredential(credential) {
    document.getElementById('vaultItemName').textContent = credential.item_name || credential.item_id || '-';
    document.getElementById('vaultUsername').value = credential.username || '';
    document.getElementById('vaultPassword').value = credential.password || '';
    document.getElementById('vaultTotp').value = credential.totp || '';
    document.getElementById('vaultUrl').value = credential.url || '';

    document.getElementById('vaultState').classList.add('d-none');
    document.getElementById('vaultContent').classList.remove('d-none');
  }

  window.copyVaultField = async function copyVaultField(fieldId) {
    const field = document.getElementById(fieldId);
    if (!field || !field.value) return;
    try {
      await navigator.clipboard.writeText(field.value);
    } catch (error) {
      alert('Kunne ikke kopiere feltet.');
    }
  };

  window.resolveVault = async function resolveVault(linkId) {
    setVaultLoading('Henter vault-oplysninger...');
    vaultModal.show();
    try {
      const result = await apiRequest(`/api/v1/links/${encodeURIComponent(linkId)}/vault/resolve`, 'POST', {});
      if (result && result.credential) {
        showVaultCredential(result.credential);
        return;
      }
      setVaultLoading(result?.message || 'Ingen credentials fundet for dette link.');
    } catch (error) {
      setVaultLoading(`Vault-opslag fejlede: ${error.message || 'ukendt fejl'}`);
    }
  };

  async function runHealthCheck() {
    const btn = document.getElementById('runHealthBtn');
    btn.disabled = true;
    btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span> Kører...';
    try {
      await fetch('/api/v1/links/health/run', {
        method: 'POST',
        headers: { ...authHeaders() },
        credentials: 'include'
      }).then(async (response) => {
        if (!response.ok) {
          const msg = response.status === 403 ? 'Mangler links.diagnose permission.' : `HTTP ${response.status}`;
          throw new Error(msg);
        }
      });
      await loadData();
    } catch (error) {
      alert(`Health check fejlede: ${error.message || 'ukendt fejl'}`);
    } finally {
      btn.disabled = false;
      btn.innerHTML = '<i class="bi bi-heart-pulse"></i> Kør health check';
    }
  }

  document.getElementById('searchInput').addEventListener('input', applyFilters);
  document.getElementById('statusFilter').addEventListener('change', applyFilters);
  document.getElementById('categoryFilter').addEventListener('change', applyFilters);
  document.getElementById('clearSearchBtn').addEventListener('click', () => {
    document.getElementById('searchInput').value = '';
    applyFilters();
    document.getElementById('searchInput').focus();
  });
  document.getElementById('cardsViewBtn').addEventListener('click', () => setViewMode('cards'));
  document.getElementById('listViewBtn').addEventListener('click', () => setViewMode('list'));
  document.getElementById('refreshBtn').addEventListener('click', loadData);
  document.getElementById('runHealthBtn').addEventListener('click', runHealthCheck);
  document.getElementById('createBtn').addEventListener('click', () => {
    resetFormForCreate();
    linkModal.show();
  });
  document.getElementById('saveBtn').addEventListener('click', saveLink);

  linkModal = new bootstrap.Modal(document.getElementById('linkModal'));
  vaultModal = new bootstrap.Modal(document.getElementById('vaultModal'));

  if (customerIdFilter) {
    document.getElementById('scopeHint').textContent = `Filter: Kunde #${customerIdFilter}`;
    document.getElementById('resultsScope').textContent = `Scope: Kunde #${customerIdFilter}`;
  }

  setViewMode(state.viewMode);

  Promise.all([loadCategories(), loadData()]);
</script>
{% endblock %}
