/**
 * Helpdesk debtor detail page — role-based dashboard rendering.
 *
 * - Support tab: health tiles, system recommendations, contracts/products widget, tickets grid
 * - Sales tab: contact table (lazy-loaded)
 * - Controlling tab: placeholder
 * - Aside: cross-ticket communication feed
 */
import { initListSection } from '/js/core/app-list-page-module.js';
import { getJson } from '/js/core/app-http.js';
import { resolveHost } from '/js/core/app-dom.js';
import { readPageConfig } from '/js/core/app-page-config.js';
import { initDetailDrawer } from '/js/components/app-detail-drawer.js';
import { initHelpdeskTicket } from './helpdesk-ticket.js';
import {
  renderCommunicationError,
  renderCommunicationFeed,
  renderCommunicationHint,
} from './helpdesk-communication.js';
import { renderEmptyState } from './helpdesk-empty-state.js';

const EMPTY_API = Object.freeze({ destroy: () => {} });

const resolveContainer = (root) => {
  const host = resolveHost(root);
  if (host instanceof HTMLElement && host.matches('.app-details-container[data-app-component="helpdesk-detail"]')) {
    return host;
  }
  return host.querySelector('.app-details-container[data-app-component="helpdesk-detail"]');
};

export function initHelpdeskDetail(root = document) {
  const container = resolveContainer(root);
  if (!container) {
    return EMPTY_API;
  }

  return init(container);
}

function init(container) {
  const listenerController = new AbortController();
  const requestController = new AbortController();
  const on = (target, type, handler, options = {}) => {
    if (!target || typeof target.addEventListener !== 'function') {
      return;
    }
    target.addEventListener(type, handler, { ...options, signal: listenerController.signal });
  };

  const customerNo = container.dataset.customerNo || '';
  const customerName = container.dataset.customerName || '';
  const supportDashboardUrl = container.dataset.supportDashboardUrl || '';
  const contactsUrl = container.dataset.contactsUrl || '';
  const ticketBaseUrl = container.dataset.ticketBaseUrl || '';
  const communicationUrl = container.dataset.communicationUrl || '';
  const fileDownloadUrl = container.dataset.fileDownloadUrl || '';
  const salesDashboardUrl = container.dataset.salesDashboardUrl || '';
  const meetingsUrl = container.dataset.meetingsUrl || '';
  const refreshFromUrl = (() => {
    const value = new URLSearchParams(window.location.search).get('refresh');
    if (!value) return false;
    const normalized = String(value).trim().toLowerCase();
    return normalized === '1' || normalized === 'true' || normalized === 'yes';
  })();

  if (!customerNo || !customerName) {
    return EMPTY_API;
  }

  const i18nEl = document.getElementById('helpdesk-i18n');
  const i18n = i18nEl ? JSON.parse(i18nEl.textContent) : {};
  const t = (key) => i18n[key] || key;

  let supportDashboardPromise = null;
  let salesDashboardPromise = null;
  let meetingsPromise = null;
  let contactsPromise = null;
  let ticketCategoriesPromise = null;
  let debitorCommunicationPromise = null;

  let supportTabRendered = false;
  let salesTabRendered = false;
  let ticketsGridInitialized = false;
  let ticketsGridInitializing = false;

  function buildBaseParams() {
    const params = new URLSearchParams({ customerNo, customerName });
    if (refreshFromUrl) {
      params.set('refresh', '1');
    }
    return params;
  }

  function fetchSupportDashboard() {
    if (!supportDashboardPromise) {
      if (!supportDashboardUrl) {
        supportDashboardPromise = Promise.resolve({ ok: false, error: 'No support dashboard endpoint configured' });
      } else {
        const params = buildBaseParams();
        supportDashboardPromise = getJson(`${supportDashboardUrl}?${params.toString()}`, {
          signal: requestController.signal,
        })
          .catch(() => ({ ok: false, error: 'Network error' }));
      }
    }

    return supportDashboardPromise;
  }

  function fetchSalesDashboard() {
    if (!salesDashboardPromise) {
      if (!salesDashboardUrl) {
        salesDashboardPromise = Promise.resolve({ ok: false, error: 'No sales dashboard endpoint configured' });
      } else {
        const params = buildBaseParams();
        salesDashboardPromise = getJson(`${salesDashboardUrl}?${params.toString()}`, {
          signal: requestController.signal,
        })
          .catch(() => ({ ok: false, error: 'Network error' }));
      }
    }

    return salesDashboardPromise;
  }

  function fetchMeetings() {
    if (!meetingsPromise) {
      if (!meetingsUrl) {
        meetingsPromise = Promise.resolve({ ok: false, error: 'No meetings endpoint configured' });
      } else {
        const params = new URLSearchParams({ customerNo });
        if (refreshFromUrl) params.set('refresh', '1');
        meetingsPromise = getJson(`${meetingsUrl}?${params.toString()}`, {
          signal: requestController.signal,
        })
          .catch(() => ({ ok: false, error: 'Network error' }));
      }
    }
    return meetingsPromise;
  }

  function fetchContacts() {
    if (!contactsPromise) {
      const params = buildBaseParams();
      contactsPromise = getJson(`${contactsUrl}?${params.toString()}`, {
        signal: requestController.signal,
      })
        .catch(() => ({ ok: false, error: 'Network error' }));
    }

    return contactsPromise;
  }

  function fetchTicketCategories(config, appBase) {
    if (!ticketCategoriesPromise) {
      const categoryOptionsUrl = config.categoryOptionsUrl || '';
      if (!categoryOptionsUrl) {
        ticketCategoriesPromise = Promise.resolve({ ok: false, categories: [] });
        return ticketCategoriesPromise;
      }

      const url = new URL(categoryOptionsUrl, appBase);
      url.searchParams.set('customerNo', config.customerNo || customerNo);
      url.searchParams.set('customerName', config.customerName || customerName);
      if (refreshFromUrl) {
        url.searchParams.set('refresh', '1');
      }

      ticketCategoriesPromise = getJson(url.toString(), {
        signal: requestController.signal,
      })
        .catch(() => ({ ok: false, categories: [] }));
    }

    return ticketCategoriesPromise;
  }

  function fetchDebitorCommunication() {
    if (!debitorCommunicationPromise) {
      if (!communicationUrl) {
        debitorCommunicationPromise = Promise.resolve({ ok: false, error: 'No communication endpoint configured' });
      } else {
        const params = buildBaseParams();
        debitorCommunicationPromise = getJson(`${communicationUrl}?${params.toString()}`, {
          signal: requestController.signal,
        })
          .catch(() => ({ ok: false, error: 'Network error' }));
      }
    }

    return debitorCommunicationPromise;
  }

  /**
   * Hydrate a dynamic select filter with values from the API response.
   * Works for category, support user, and contact filters.
   */
  function hydrateSelectFilter(config, selectorId, filterKey, values) {
    const selectEl = document.querySelector(selectorId);
    if (!(selectEl instanceof HTMLSelectElement)) return;

    const uniqueValues = Array.from(new Set(
      (Array.isArray(values) ? values : [])
        .map(item => String(item || '').trim())
        .filter(Boolean),
    )).sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));

    const currentValue = String(selectEl.value || '');
    selectEl.innerHTML = '';

    const allOption = document.createElement('option');
    allOption.value = '';
    allOption.textContent = t('All');
    selectEl.appendChild(allOption);

    const optionMap = {};
    for (const value of uniqueValues) {
      const option = document.createElement('option');
      option.value = value;
      option.textContent = value;
      selectEl.appendChild(option);
      optionMap[value] = value;
    }

    if (currentValue !== '' && Object.prototype.hasOwnProperty.call(optionMap, currentValue)) {
      selectEl.value = currentValue;
    } else {
      selectEl.value = '';
    }

    if (!config.filterChipMeta || typeof config.filterChipMeta !== 'object') {
      config.filterChipMeta = {};
    }

    const currentMeta = (config.filterChipMeta[filterKey] && typeof config.filterChipMeta[filterKey] === 'object')
      ? config.filterChipMeta[filterKey]
      : {};

    config.filterChipMeta[filterKey] = {
      ...currentMeta,
      type: 'select',
      default: '',
      options: optionMap,
    };
  }

  function esc(str) {
    const d = document.createElement('div');
    d.textContent = str;
    return d.innerHTML;
  }

  function ticketUrl(ticketNo) {
    return ticketBaseUrl + encodeURIComponent(ticketNo) + '?from=' + encodeURIComponent(customerNo);
  }

  function errorNotice(message) {
    return `<div class="notice" data-variant="warning" role="alert"><p>${esc(message)}</p></div>`;
  }

  let contactsGridInitialized = false;
  let contactsGridInitializing = false;

  async function initContactsGrid() {
    if (contactsGridInitialized || contactsGridInitializing) return;
    contactsGridInitializing = true;

    const contactsConfig = readPageConfig('helpdesk-contacts');
    if (!contactsConfig) {
      contactsGridInitializing = false;
      return;
    }

    const labels = contactsConfig.labels || {};
    const gridjsLib = window.gridjs;
    if (!gridjsLib) {
      contactsGridInitializing = false;
      return;
    }

    const baseEl = document.querySelector('base');
    const appBase = (baseEl && baseEl.href) ? baseEl.href : '/';

    const gridDataUrl = new URL(contactsConfig.dataUrl || '', appBase);
    gridDataUrl.searchParams.set('customerNo', contactsConfig.customerNo || customerNo);
    gridDataUrl.searchParams.set('customerName', contactsConfig.customerName || customerName);
    if (refreshFromUrl) {
      gridDataUrl.searchParams.set('refresh', '1');
    }

    try {
      // Fetch type options from first data response
      const typeOptionsUrl = new URL(gridDataUrl.toString());
      typeOptionsUrl.searchParams.set('limit', '1');
      const typeResp = await getJson(typeOptionsUrl.toString(), {
        signal: requestController.signal,
      }).catch(() => null);
      const typeOptions = Array.isArray(typeResp?.type_options) ? typeResp.type_options : [];
      hydrateContactTypeFilter(contactsConfig, typeOptions);

      const gridOptions = {
        gridjs: gridjsLib,
        container: '#helpdesk-contacts-grid',
        dataUrl: gridDataUrl.toString(),
        appBase,
        columns: [
          { name: labels.name || 'Name', sort: true },
          { name: labels.type || 'Type', sort: true },
          { name: labels.jobTitle || 'Job title', sort: true },
          { name: labels.email || 'E-Mail', sort: true },
          { name: labels.phone || 'Phone', sort: true },
          { name: labels.mobile || 'Mobile', sort: false },
        ],
        sortColumns: ['Name', 'Type', 'Job_Title', 'E_Mail', 'Phone_No', null],
        paginationLimit: 10,
        language: contactsConfig.gridLang || {},
        mapData: (data) => (data.data || []).map((row) => [
          row.Name || '',
          row.Type || '',
          row.Job_Title || '',
          row.E_Mail || '',
          row.Phone_No || '',
          row.Mobile_Phone_No || '',
        ]),
        search: contactsConfig.gridSearch || null,
        filterSchema: contactsConfig.filterSchema || [],
        urlSync: false,
        actions: { enabled: false },
      };

      // Scope filter UI to the contacts section to avoid collision with tickets filter
      const contactsSection = document.getElementById('sales-contacts-section');
      const contactsChipsEl = contactsSection
        ? contactsSection.querySelector('[data-active-filter-chips]')
        : null;
      const contactsLiveEl = contactsSection
        ? contactsSection.querySelector('[data-filter-live-region]')
        : null;

      initListSection({
        moduleId: 'helpdesk-detail-contacts',
        initErrorMessage: 'Helpdesk contacts grid init failed',
        initOptions: {
        grid: gridOptions,
        filters: {
          mode: 'drawer',
          chipMeta: contactsConfig.filterChipMeta || {},
          watchInputs: ['#helpdesk-contact-search'],
          drawerRoot: contactsSection || undefined,
          chips: contactsChipsEl ? { container: contactsChipsEl } : {},
          live: contactsLiveEl ? { regionSelector: null } : {},
        },
        },
      });
      contactsGridInitialized = true;
    } finally {
      contactsGridInitializing = false;
    }
  }

  function hydrateContactTypeFilter(contactsConfig, typeOptions) {
    const select = document.getElementById('helpdesk-contact-type-filter');
    if (!(select instanceof HTMLSelectElement) || !Array.isArray(typeOptions)) return;

    const currentValue = String(select.value || '');
    select.innerHTML = '';

    const allOption = document.createElement('option');
    allOption.value = '';
    allOption.textContent = t('All');
    select.appendChild(allOption);

    const typeOptionMap = {};
    for (const opt of typeOptions) {
      const id = String(opt.id || '').trim();
      const label = String(opt.description || opt.id || '').trim();
      if (id === '') continue;
      const option = document.createElement('option');
      option.value = id;
      option.textContent = label;
      select.appendChild(option);
      typeOptionMap[id] = label;
    }

    if (currentValue !== '' && Object.prototype.hasOwnProperty.call(typeOptionMap, currentValue)) {
      select.value = currentValue;
    } else {
      select.value = '';
    }

    if (!contactsConfig.filterChipMeta || typeof contactsConfig.filterChipMeta !== 'object') {
      contactsConfig.filterChipMeta = {};
    }

    const currentTypeMeta = (contactsConfig.filterChipMeta.type && typeof contactsConfig.filterChipMeta.type === 'object')
      ? contactsConfig.filterChipMeta.type
      : {};

    contactsConfig.filterChipMeta.type = {
      ...currentTypeMeta,
      type: 'select',
      default: '',
      options: typeOptionMap,
    };
  }

  function formatTemplate(template, params) {
    const tpl = String(template || '');
    const map = (params && typeof params === 'object') ? params : {};

    return tpl.replace(/\{([a-zA-Z0-9_]+)\}/g, (_, key) => {
      const value = map[key];
      if (value === null || value === undefined) {
        return '';
      }

      return String(value);
    });
  }

  const severityMap = {
    escalation_overdue: { cls: 'critical', icon: '🔴' },
    high_risk_aging:    { cls: 'high',     icon: '🟠' },
    unassigned_open:    { cls: 'medium',   icon: '🟡' },
    stale_open:         { cls: 'low',      icon: '🔵' },
    customer_backlog:   { cls: 'low',      icon: '🔵' },
  };

  function renderSystemRecommendations(recommendations) {
    const entries = Array.isArray(recommendations) ? recommendations : [];
    if (entries.length === 0) {
      return renderEmptyState({
        message: t('No system recommendations right now.'),
        size: 'compact',
        align: 'center',
        icon: false,
      });
    }

    const formatRecommendationMessage = (entry) => {
      const messageKey = String(entry.message_key || '');
      const template = t(messageKey);
      const params = (entry.message_params && typeof entry.message_params === 'object')
        ? entry.message_params
        : {};

      if (template && template !== messageKey) {
        return formatTemplate(template, params);
      }

      return messageKey || String(entry.rule_id || '—');
    };

    let html = '<div class="helpdesk-rec-list">';
    for (const entry of entries) {
      const ticketNo = String(entry.ticket_no || '');
      const href = ticketNo ? ticketUrl(ticketNo) : '';
      const ruleId = String(entry.rule_id || '');
      const severity = severityMap[ruleId] || { cls: 'low', icon: '🔵' };
      const ageHours = Number.isFinite(Number(entry.age_hours)) ? Number(entry.age_hours) : null;
      const ageLabel = ageHours !== null ? fmtHours(ageHours) : '';
      const message = formatRecommendationMessage(entry);

      const tag = href ? 'a' : 'div';
      const hrefAttr = href ? ` href="${esc(href)}"` : '';
      html += `<${tag}${hrefAttr} class="helpdesk-rec-card helpdesk-rec-${severity.cls}" tabindex="0">`;
      html += `<div class="helpdesk-rec-card-header">`;
      html += `<span class="helpdesk-rec-card-ticket">${esc(ticketNo || '—')}</span>`;
      if (ageLabel) html += `<span class="helpdesk-rec-card-age">${esc(ageLabel)}</span>`;
      html += `</div>`;
      html += `<div class="helpdesk-rec-card-message">${esc(message)}</div>`;
      html += `</${tag}>`;
    }
    html += '</div>';
    return html;
  }


  function contractTypeLabel(productType) {
    return String(productType || '').trim() || '—';
  }

  // Shared formatter for contract rendering (dialog, timeline)
  const contractAmountFmt = new Intl.NumberFormat(undefined, {
    style: 'currency',
    currency: 'EUR',
    maximumFractionDigits: 2,
  });

  function contractFormatDate(value) {
    const raw = String(value || '').trim();
    if (!raw || raw.startsWith('0001-01-01')) return '—';
    const date = new Date(raw);
    if (Number.isNaN(date.getTime())) return raw;
    return date.toLocaleDateString();
  }

  function contractStateVariant(state) {
    const s = String(state || '').toLowerCase();
    if (s === 'aktiv') return 'success';
    if (s === 'vorbereitung') return 'warning';
    return 'neutral';
  }

  /** Store all contracts for dialog access */
  let salesContracts = [];



  function renderContractLinesTable(lines) {
    if (!Array.isArray(lines) || lines.length === 0) {
      return `<p class="muted">${esc(t('No contract details available.'))}</p>`;
    }

    let html = '<table><thead><tr>';
    html += `<th>${esc(t('Description'))}</th>`;
    html += `<th>${esc(t('Type'))}</th>`;
    html += `<th>${esc(t('Quantity'))}</th>`;
    html += `<th>${esc(t('Unit price'))}</th>`;
    html += `<th>${esc(t('Line amount'))}</th>`;
    html += `<th>${esc(t('Line state'))}</th>`;
    html += '</tr></thead><tbody>';
    for (const line of lines) {
      const lineVariant = String(line.line_state || '').toLowerCase() === 'aktiv' ? 'success' : 'neutral';
      html += '<tr>';
      html += `<td>${esc(line.description || '—')}</td>`;
      html += `<td>${line.type ? `<span class="badge">${esc(line.type)}</span>` : '—'}</td>`;
      html += `<td>${esc(String(line.quantity ?? 0))}</td>`;
      html += `<td>${esc(contractAmountFmt.format(Number(line.unit_price ?? 0)))}</td>`;
      html += `<td>${esc(contractAmountFmt.format(Number(line.line_amount ?? 0)))}</td>`;
      html += `<td><span class="badge" data-variant="${lineVariant}">${esc(line.line_state || '—')}</span></td>`;
      html += '</tr>';
    }
    html += '</tbody></table>';
    return html;
  }

  function openContractDialog(contract) {
    // Remove existing dialog if present
    const existing = document.getElementById('helpdesk-contract-dialog');
    if (existing) existing.remove();

    const lines = Array.isArray(contract.lines) ? contract.lines : [];
    const variant = contractStateVariant(contract.state);
    const typeLabel = contractTypeLabel(contract.product_type);

    let dialogHtml = '<dialog id="helpdesk-contract-dialog">';
    dialogHtml += '<article class="app-dialog-lg helpdesk-contract-dialog-article">';

    // Header — follows core pattern: <h2> then <button class="close">
    dialogHtml += '<header>';
    dialogHtml += `<h2>${esc(contract.description || typeLabel)} <small>${esc(contract.contract_no || '')}</small></h2>`;
    dialogHtml += '<button type="button" class="close" data-dialog-close aria-label="Close"></button>';
    dialogHtml += '</header>';

    // Contract meta
    dialogHtml += '<div class="helpdesk-contract-dialog-meta">';
    const metaItems = [];
    metaItems.push(`<span><strong>${esc(t('Status'))}</strong><span class="badge" data-variant="${variant}">${esc(contract.state || '—')}</span></span>`);
    metaItems.push(`<span><strong>${esc(t('Type'))}</strong>${esc(typeLabel)}</span>`);
    if (contract.starting_date) {
      metaItems.push(`<span><strong>${esc(t('Starting date'))}</strong>${esc(contractFormatDate(contract.starting_date))}</span>`);
    }
    if (contract.invoicing_period) {
      metaItems.push(`<span><strong>${esc(t('Invoicing period'))}</strong>${esc(contract.invoicing_period)}</span>`);
    }
    if (contract.next_invoicing_date) {
      metaItems.push(`<span><strong>${esc(t('Next invoicing'))}</strong>${esc(contractFormatDate(contract.next_invoicing_date))}</span>`);
    }
    if (Number(contract.monthly_amount ?? 0) > 0) {
      metaItems.push(`<span><strong>${esc(t('Monthly volume'))}</strong>${esc(contractAmountFmt.format(contract.monthly_amount))}</span>`);
    }
    if (contract.salesperson_code) {
      metaItems.push(`<span><strong>${esc(t('Salesperson'))}</strong>${esc(contract.salesperson_code)}</span>`);
    }
    if (Number(contract.support_hours ?? 0) > 0) {
      metaItems.push(`<span><strong>${esc(t('Support hours'))}</strong>${esc(String(contract.support_hours))}</span>`);
    }
    dialogHtml += metaItems.join('');
    dialogHtml += '</div>';

    // Lines table
    if (lines.length > 0) {
      dialogHtml += `<h3 class="helpdesk-contract-dialog-section-title">${esc(t('positions'))} (${lines.length})</h3>`;
      dialogHtml += '<div class="helpdesk-contract-dialog-lines">';
      dialogHtml += renderContractLinesTable(lines);
      dialogHtml += '</div>';
    }

    // Footer
    dialogHtml += '<footer>';
    dialogHtml += `<button type="button" class="secondary outline" data-dialog-close>${esc(t('Close'))}</button>`;
    dialogHtml += '</footer>';

    dialogHtml += '</article></dialog>';

    document.body.insertAdjacentHTML('beforeend', dialogHtml);
    const dialog = document.getElementById('helpdesk-contract-dialog');
    on(dialog, 'click', (e) => {
      if (e.target.closest('[data-dialog-close]')) {
        dialog.close();
        dialog.remove();
        return;
      }
      // Close on backdrop click
      if (e.target === dialog) {
        dialog.close();
        dialog.remove();
      }
    });
    on(dialog, 'close', () => { dialog.remove(); });
    dialog.showModal();
  }

  // Delegate click on contract cards and timeline rows
  on(container, 'click', (e) => {
    const target = e.target.closest('.helpdesk-contract-card[data-contract-index], .helpdesk-tl-row-clickable[data-contract-index]');
    if (!target) return;
    const index = Number(target.dataset.contractIndex);
    if (salesContracts[index]) {
      openContractDialog(salesContracts[index]);
    }
  });

  // KPI tile click → scroll to ticket list and apply filter or sort
  on(container, 'click', (e) => {
    const kpi = e.target.closest('.helpdesk-kpi-clickable[data-kpi-filter], .helpdesk-kpi-clickable[data-kpi-sort]');
    if (!kpi) return;

    // Apply status filter if present
    if (kpi.dataset.kpiFilter !== undefined) {
      const filterValue = kpi.dataset.kpiFilter || '';
      const statusSelect = document.getElementById('helpdesk-ticket-status-filter');
      if (statusSelect instanceof HTMLSelectElement) {
        statusSelect.value = filterValue;
        statusSelect.dispatchEvent(new Event('change', { bubbles: true }));
      }
    }

    // Apply sort if present (format: "column:dir")
    if (kpi.dataset.kpiSort) {
      const [col, dir] = kpi.dataset.kpiSort.split(':');
      const url = new URL(window.location.href);
      url.searchParams.set('order', col);
      url.searchParams.set('dir', dir || 'asc');
      // Also set open filter for "avg age" to show only open tickets
      const statusSelect = document.getElementById('helpdesk-ticket-status-filter');
      if (statusSelect instanceof HTMLSelectElement) {
        statusSelect.value = 'open';
        statusSelect.dispatchEvent(new Event('change', { bubbles: true }));
      }
      window.history.replaceState(null, '', url.toString());
    }

    const ticketSection = document.querySelector('.helpdesk-support-ticket-list');
    if (ticketSection) {
      ticketSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }
  });


  function setText(id, value) {
    const el = document.getElementById(id);
    if (el) {
      el.textContent = String(value);
    }
  }

  function setHtml(id, html) {
    const el = document.getElementById(id);
    if (el) {
      el.innerHTML = html;
    }
  }

  function fmtHours(h) {
    const hours = Math.round(h);
    if (hours < 24) return hours + 'h';
    const days = Math.floor(hours / 24);
    const rest = hours % 24;
    return rest > 0 ? days + 'd ' + rest + 'h' : days + 'd';
  }

  function showLoading(id) {
    const el = document.getElementById(id);
    if (el) {
      el.hidden = false;
    }
  }

  function hideLoading(id) {
    const el = document.getElementById(id);
    if (el) {
      el.hidden = true;
    }
  }

  function showContent(id) {
    const el = document.getElementById(id);
    if (el) {
      el.hidden = false;
    }
  }

  on(container, 'click', (e) => {
    if (e.target.closest('a')) return;
    const row = e.target.closest('.app-clickable-row[data-href]');
    if (row) {
      const href = row.dataset.href;
      if (href) {
        window.location.href = href;
      }
    }
  });

  on(container, 'keydown', (e) => {
    if (e.key !== 'Enter' && e.key !== ' ') return;
    const row = e.target.closest('.app-clickable-row[data-href]');
    if (row) {
      e.preventDefault();
      const href = row.dataset.href;
      if (href) {
        window.location.href = href;
      }
      return;
    }
    // Timeline row keyboard activation
    const tlRow = e.target.closest('.helpdesk-tl-row-clickable[data-contract-index]');
    if (tlRow) {
      e.preventDefault();
      const index = Number(tlRow.dataset.contractIndex);
      if (salesContracts[index]) {
        openContractDialog(salesContracts[index]);
      }
    }
  });

  async function initTicketsGrid() {
    if (ticketsGridInitialized || ticketsGridInitializing) return;
    ticketsGridInitializing = true;

    const config = readPageConfig('helpdesk-tickets');
    if (!config) {
      ticketsGridInitializing = false;
      return;
    }

    const labels = config.labels || {};
    const gridjs = window.gridjs;
    if (!gridjs) {
      ticketsGridInitializing = false;
      return;
    }

    const baseEl = document.querySelector('base');
    const appBase = (baseEl && baseEl.href) ? baseEl.href : '/';

    const gridDataUrl = new URL(config.dataUrl || '', appBase);
    gridDataUrl.searchParams.set('customerNo', config.customerNo || customerNo);
    gridDataUrl.searchParams.set('customerName', config.customerName || customerName);
    if (refreshFromUrl) {
      gridDataUrl.searchParams.set('refresh', '1');
    }

    try {
      const filterOptionsResponse = await fetchTicketCategories(config, appBase);
      hydrateSelectFilter(config, '#helpdesk-ticket-category-filter', 'category', filterOptionsResponse?.categories || []);
      hydrateSelectFilter(config, '#helpdesk-ticket-support-filter', 'support', filterOptionsResponse?.support_users || []);
      hydrateSelectFilter(config, '#helpdesk-ticket-contact-filter', 'contact', filterOptionsResponse?.contacts || []);

      const gridOptions = {
        gridjs,
        container: '#helpdesk-tickets-grid',
        dataUrl: gridDataUrl.toString(),
        appBase,
        columns: [
          {
            name: labels.ticketNo || 'Ticket No.',
            sort: true,
            formatter: (cell) => gridjs.html(`<a class="app-grid-link-cell" href="${cell.url}" data-drawer-trigger data-ticket-no="${cell.no || ''}">${cell.no}</a>`),
          },
          { name: labels.description || 'Description', sort: false },
          {
            name: labels.status || 'Status',
            sort: true,
            formatter: (cell) => {
              if (!cell || typeof cell !== 'object') return gridjs.html('-');
              return gridjs.html(`<span class="badge" data-variant="${cell.variant || 'neutral'}">${cell.label || '-'}</span>`);
            },
          },
          { name: labels.category || 'Category', sort: false },
          { name: labels.support || 'Support', sort: false },
          { name: labels.created || 'Created', sort: true },
          { name: labels.lastActivity || 'Last activity', sort: true },
          { name: 'ticket_url', hidden: true },
        ],
        sortColumns: ['No', null, 'Ticket_State', null, null, 'Created_On', 'Last_Activity_Date', null],
        paginationLimit: 10,
        language: config.gridLang || {},
        mapData: (data) => (data.data || []).map((row) => [
          { no: row.No || '', url: row.ticket_url || '#' },
          row.Description || '',
          { variant: row.state_variant || 'neutral', label: row.Ticket_State || '' },
          row.Category_1_Description || '',
          row.Support_User_Name || '',
          row.created_formatted || '',
          row.last_activity_formatted || '',
          row.ticket_url || '',
        ]),
        search: config.gridSearch || null,
        filterSchema: config.filterSchema || [],
        urlSync: true,
        actions: { enabled: false },
        rowDblClick: {
          getUrl: (rowData) => {
            const url = rowData?.cells?.[7]?.data;
            return url || '';
          },
        },
        rowInteraction: { linkColumn: false },
        rowDataset: (row) => ({
          uuid: row?.cells?.[0]?.data?.no || '',
        }),
      };

      const section = initListSection({
        moduleId: 'helpdesk-detail-tickets',
        initErrorMessage: 'Helpdesk tickets grid init failed',
        initOptions: {
        grid: gridOptions,
        filters: {
          mode: 'drawer',
          chipMeta: config.filterChipMeta || {},
          watchInputs: ['#helpdesk-ticket-search'],
        },
        },
      });
      ticketsGridInitialized = true;

      initDetailDrawer({
        gridConfig: section?.gridConfig || null,
        triggerSelector: '[data-drawer-trigger][data-ticket-no]',
        rowUuidAttr: 'uuid',
        fetchUrl: (ticketNo) => new URL(`helpdesk/ticket-fragment/${encodeURIComponent(ticketNo)}?from=${encodeURIComponent(customerNo || '')}`, appBase).toString(),
        fullUrl: (ticketNo) => new URL(`helpdesk/ticket/${encodeURIComponent(ticketNo)}?from=${encodeURIComponent(customerNo || '')}`, appBase).toString(),
        hashPrefix: 'ticket',
        onContentLoaded: (contentEl) => {
          try { initHelpdeskTicket(contentEl); } catch (err) { console.warn('[helpdesk-detail] initHelpdeskTicket failed', err); }
        },
      });
    } finally {
      ticketsGridInitializing = false;
    }
  }

  async function renderSupportTab() {
    if (supportTabRendered) return;

    showLoading('support-loading');
    const result = await fetchSupportDashboard();
    hideLoading('support-loading');
    showContent('support-content');

    const recommendationsEl = document.getElementById('support-system-recommendations');
    if (!result?.ok) {
      setText('support-kpi-open-tickets', '—');
      setText('support-kpi-created-30d', '—');
      setText('support-kpi-closed-30d', '—');
      setText('support-kpi-avg-age', '—');
      if (recommendationsEl) {
        recommendationsEl.innerHTML = errorNotice(t('Could not load tickets.'));
      }
      supportTabRendered = true;
      return;
    }

    const health = (result.health && typeof result.health === 'object') ? result.health : {};
    const openTickets = Number(health.open_tickets ?? 0);
    const created30d = Number(health.created_30d ?? 0);
    const createdPrev30d = Number(health.created_prev_30d ?? 0);
    const closed30d = Number(health.closed_30d ?? 0);
    const closedPrev30d = Number(health.closed_prev_30d ?? 0);
    const avgAgeHours = health.avg_open_age_hours !== null && health.avg_open_age_hours !== undefined
      ? Number(health.avg_open_age_hours) : null;
    const criticalTickets = Number(health.critical_tickets ?? 0);

    // Helper: render trend indicator
    const renderTrend = (current, previous, invertColor) => {
      if (previous === 0 && current === 0) return '';
      const delta = current - previous;
      if (delta === 0) return `<span class="helpdesk-kpi-trend-neutral">— ${t('vs. prev. 30d')}</span>`;
      const arrow = delta > 0 ? '↑' : '↓';
      const abs = Math.abs(delta);
      // For "resolved" more is good (invert), for "created/open" more is bad
      const isPositive = invertColor ? delta > 0 : delta < 0;
      const cls = isPositive ? 'helpdesk-kpi-trend-positive' : 'helpdesk-kpi-trend-negative';
      return `<span class="${cls}">${arrow} ${abs} ${t('vs. prev. 30d')}</span>`;
    };

    // 1. Open tickets + net flow trend
    setText('support-kpi-open-tickets', openTickets);
    const netFlow = created30d - closed30d;
    const openTrendEl = document.getElementById('support-kpi-open-trend');
    if (openTrendEl) {
      if (netFlow > 0) {
        openTrendEl.innerHTML = `<span class="helpdesk-kpi-trend-negative">↑ ${netFlow} ${t('net (30d)')}</span>`;
      } else if (netFlow < 0) {
        openTrendEl.innerHTML = `<span class="helpdesk-kpi-trend-positive">↓ ${Math.abs(netFlow)} ${t('net (30d)')}</span>`;
      } else if (created30d > 0) {
        openTrendEl.innerHTML = `<span class="helpdesk-kpi-trend-neutral">± 0 ${t('net (30d)')}</span>`;
      }
    }

    // 2. Created last 30d + trend vs previous 30d
    setText('support-kpi-created-30d', created30d);
    const createdTrendEl = document.getElementById('support-kpi-created-trend');
    if (createdTrendEl) {
      createdTrendEl.innerHTML = renderTrend(created30d, createdPrev30d, false);
    }

    // 3. Closed last 30d + trend vs previous 30d
    setText('support-kpi-closed-30d', closed30d);
    const closedTrendEl = document.getElementById('support-kpi-closed-trend');
    if (closedTrendEl) {
      closedTrendEl.innerHTML = renderTrend(closed30d, closedPrev30d, true);
    }

    // 4. Avg age of open tickets
    const formatAge = (hours) => {
      if (hours === null) return '—';
      if (hours < 24) return `${hours}h`;
      const days = Math.round(hours / 24);
      return `${days}d`;
    };
    setText('support-kpi-avg-age', formatAge(avgAgeHours));
    const ageHintEl = document.getElementById('support-kpi-age-hint');
    if (ageHintEl && criticalTickets > 0) {
      ageHintEl.innerHTML = `<span class="helpdesk-kpi-trend-negative">${criticalTickets} ${t('critical')}</span>`;
    }


    // Recommendations + escalations merged — only show section when there are entries
    const recommendationsWrapper = document.getElementById('support-recommendations-wrapper');
    if (recommendationsEl) {
      const recommendations = Array.isArray(result.recommendations)
        ? result.recommendations
        : (Array.isArray(result.actions) ? result.actions : []);
      if (recommendations.length > 0) {
        recommendationsEl.innerHTML = renderSystemRecommendations(recommendations);
        if (recommendationsWrapper) recommendationsWrapper.hidden = false;
      } else if (recommendationsWrapper) {
        recommendationsWrapper.hidden = true;
      }
    }

    supportTabRendered = true;
  }

  async function renderSalesTab() {
    if (salesTabRendered) return;

    showLoading('sales-loading');
    const result = await fetchSalesDashboard();
    hideLoading('sales-loading');
    showContent('sales-content');

    if (!result?.ok) {
      setText('sales-kpi-monthly-volume', '—');
      setText('sales-kpi-active-contracts', '—');
      setText('sales-kpi-avg-value', '—');
      setText('sales-kpi-next-invoicing', '—');
      const detailEl = document.getElementById('sales-contracts-detail');
      if (detailEl) detailEl.innerHTML = errorNotice(t('Could not load sales dashboard.'));
      salesTabRendered = true;
      return;
    }

    // Render KPIs
    const kpis = (result.kpis && typeof result.kpis === 'object') ? result.kpis : {};
    const amountFmt = new Intl.NumberFormat(undefined, {
      style: 'currency',
      currency: 'EUR',
      maximumFractionDigits: 0,
    });

    const formatDate = (value) => {
      const raw = String(value || '').trim();
      if (!raw || raw.startsWith('0001-01-01')) return '—';
      const date = new Date(raw);
      if (Number.isNaN(date.getTime())) return raw;
      return date.toLocaleDateString();
    };

    // Trend helper (same pattern as support dashboard)
    const salesTrend = (current, previous, invertColor) => {
      if (previous === 0 && current === 0) return '';
      const delta = current - previous;
      if (delta === 0) return `<span class="helpdesk-kpi-trend-neutral">— ${esc(t('vs. prev. 30d'))}</span>`;
      const arrow = delta > 0 ? '↑' : '↓';
      const abs = Math.abs(delta);
      const isPositive = invertColor ? delta > 0 : delta < 0;
      const cls = isPositive ? 'helpdesk-kpi-trend-positive' : 'helpdesk-kpi-trend-negative';
      return `<span class="${cls}">${arrow} ${abs} ${esc(t('vs. prev. 30d'))}</span>`;
    };

    const started30d = Number(kpis.started_30d ?? 0);
    const startedPrev30d = Number(kpis.started_prev_30d ?? 0);
    const ended30d = Number(kpis.ended_30d ?? 0);
    const endedPrev30d = Number(kpis.ended_prev_30d ?? 0);

    // 1. Monthly volume + net contract flow trend
    setText('sales-kpi-monthly-volume', amountFmt.format(Number(kpis.monthly_volume ?? 0)));
    const netContracts = started30d - ended30d;
    if (started30d > 0 || ended30d > 0) {
      const netCls = netContracts > 0 ? 'helpdesk-kpi-trend-positive' : (netContracts < 0 ? 'helpdesk-kpi-trend-negative' : 'helpdesk-kpi-trend-neutral');
      const netArrow = netContracts > 0 ? '↑' : (netContracts < 0 ? '↓' : '—');
      const netAbs = Math.abs(netContracts);
      setHtml('sales-kpi-monthly-volume-trend',
        `<span class="${netCls}">${netArrow} ${netAbs} ${esc(t('net (30d)'))}</span>`);
    } else {
      setHtml('sales-kpi-monthly-volume-trend', '');
    }

    // 2. Active contracts + trend arrow (started vs previous period)
    setText('sales-kpi-active-contracts', kpis.active_contracts ?? 0);
    const totalContracts = Number(kpis.total_contracts ?? 0);
    const contractsTrendHtml = salesTrend(started30d, startedPrev30d, false);
    const totalHint = totalContracts > 0 ? `<span class="helpdesk-kpi-trend-neutral">${esc(t('of {total} total').replace('{total}', totalContracts))}</span>` : '';
    setHtml('sales-kpi-active-contracts-trend', contractsTrendHtml
      ? contractsTrendHtml + (totalHint ? ' · ' + totalHint : '')
      : totalHint);

    // 3. Avg contract value + largest hint
    setText('sales-kpi-avg-value', amountFmt.format(Number(kpis.avg_contract_value ?? 0)));
    const largest = Number(kpis.largest_contract_volume ?? 0);
    const activeCount = Number(kpis.active_contracts ?? 0);
    const totalPositions = Number(kpis.total_positions ?? 0);
    const avgParts = [];
    if (activeCount > 1 && largest > 0) {
      avgParts.push(`<span class="helpdesk-kpi-trend-neutral">${esc(t('largest'))}: ${esc(amountFmt.format(largest))}</span>`);
    }
    if (totalPositions > 0) {
      avgParts.push(`<span class="helpdesk-kpi-trend-neutral">${esc(totalPositions + ' ' + t('positions'))}</span>`);
    }
    setHtml('sales-kpi-avg-value-trend', avgParts.join(' · '));

    // 4. Next invoicing + countdown
    setText('sales-kpi-next-invoicing', formatDate(kpis.next_invoicing_date));
    const daysUntil = kpis.days_until_next_invoicing;
    if (daysUntil !== null && daysUntil !== undefined) {
      const cls = daysUntil <= 7 ? 'helpdesk-kpi-trend-negative' : (daysUntil <= 30 ? 'helpdesk-kpi-trend-neutral' : 'helpdesk-kpi-trend-positive');
      setHtml('sales-kpi-next-invoicing-trend',
        `<span class="${cls}">${esc(t('in {days} days').replace('{days}', daysUntil))}</span>`);
    }

    // Contract timeline (swimlane view — click on bar opens contract dialog)
    renderContractTimeline(result.contracts || []);

    // Init contacts grid (server-side, same pattern as tickets)
    initContactsGrid();

    // Meetings timeline (async, doesn't block sales tab)
    renderMeetingsSection();

    salesTabRendered = true;
  }

  // All values inserted via esc() which escapes HTML entities — safe innerHTML usage (same pattern as renderContractTimeline, renderSystemRecommendations)
  async function renderMeetingsSection() {
    const loadingEl = document.getElementById('meetings-loading');
    const contentEl = document.getElementById('meetings-content');
    const emptyEl = document.getElementById('meetings-empty');
    const errorEl = document.getElementById('meetings-error');
    if (!loadingEl || !contentEl) return;

    loadingEl.hidden = false;
    contentEl.hidden = true;
    if (emptyEl) emptyEl.hidden = true;
    if (errorEl) errorEl.hidden = true;

    const result = await fetchMeetings();

    loadingEl.hidden = true;

    if (!result?.ok) {
      if (errorEl) {
        errorEl.innerHTML = errorNotice(t('Could not load meetings.')); // raw-html-ok: errorNotice returns escaped HTML
        errorEl.hidden = false;
      }
      return;
    }

    const kpis = result.kpis || {};
    const timeline = result.timeline || {};
    const upcoming = timeline.upcoming || {};
    const past = timeline.past || {};
    const hasEntries = Object.keys(upcoming).length > 0 || Object.keys(past).length > 0;

    if (!hasEntries) {
      if (emptyEl) {
        emptyEl.innerHTML = renderEmptyState(t('No meetings found for this customer.')); // raw-html-ok: renderEmptyState returns escaped HTML
        emptyEl.hidden = false;
      }
      return;
    }

    // Render timeline — all dynamic values passed through esc() which HTML-entity-escapes
    const timelineEl = document.getElementById('meetings-timeline');
    if (!timelineEl) { contentEl.hidden = false; return; }

    let html = '';

    if (Object.keys(upcoming).length > 0) {
      html += `<div class="helpdesk-meetings-group-header">${esc(t('Upcoming meetings'))}</div>`;
      html += renderMeetingGroups(upcoming);
    }

    if (Object.keys(past).length > 0) {
      html += `<div class="helpdesk-meetings-group-header helpdesk-meetings-group-header-past">${esc(t('Past meetings'))}</div>`;
      html += renderMeetingGroups(past);
    }

    timelineEl.innerHTML = html; // raw-html-ok: all values escaped via esc()
    contentEl.hidden = false;
  }

  function renderMeetingGroups(groups) {
    const monthFmt = new Intl.DateTimeFormat(undefined, { year: 'numeric', month: 'long' });
    const dateFmt = new Intl.DateTimeFormat(undefined, { day: 'numeric', month: 'short' });
    let html = '';

    for (const [monthKey, entries] of Object.entries(groups)) {
      const monthDate = new Date(monthKey + '-01');
      const monthLabel = Number.isNaN(monthDate.getTime()) ? monthKey : monthFmt.format(monthDate);
      html += `<div class="helpdesk-meetings-month"><span class="helpdesk-meetings-month-label">${esc(monthLabel)}</span>`;

      for (const entry of entries) {
        const d = new Date(entry.appointment_date);
        const dateStr = Number.isNaN(d.getTime()) ? entry.appointment_date : dateFmt.format(d);
        const statusCls = entry.is_released ? 'helpdesk-meetings-card-released' : 'helpdesk-meetings-card-pending';
        const badgeCls = entry.is_released ? 'helpdesk-meetings-badge-released' : 'helpdesk-meetings-badge-pending';
        const badgeLabel = entry.is_released ? t('Released') : t('Pending');

        html += `<article class="helpdesk-meetings-card ${statusCls}">
          <p class="helpdesk-meetings-card-comment">${esc(entry.comment || '—')}</p>
          <p class="helpdesk-meetings-card-secondary">
            <span class="helpdesk-meetings-card-context">
              <time datetime="${esc(entry.appointment_date)}">${esc(dateStr)}</time>
              <span>·</span>
              <span>${esc(entry.salesperson_code)}</span>
            </span>
            <span class="helpdesk-meetings-badge ${badgeCls}" aria-label="${esc(badgeLabel)}">${esc(badgeLabel)}</span>
          </p>
        </article>`;
      }

      html += '</div>';
    }

    return html;
  }

  function renderContractTimeline(contracts) {
    const chartEl = document.getElementById('sales-trend-chart');
    if (!chartEl || !contracts.length) return;

    // Store contracts for dialog access
    salesContracts = contracts;

    const amountFmt = new Intl.NumberFormat(undefined, {
      style: 'currency',
      currency: 'EUR',
      maximumFractionDigits: 0,
    });

    const nowDate = new Date();

    // Parse dates and compute timeline range
    const items = [];
    let globalMin = Infinity;
    let globalMax = -Infinity;

    for (let i = 0; i < contracts.length; i++) {
      const c = contracts[i];
      const startRaw = String(c.starting_date || '').trim();
      if (!startRaw || startRaw.startsWith('0001')) continue;

      const startTs = new Date(startRaw).getTime();
      if (Number.isNaN(startTs)) continue;

      const stateLower = String(c.state || '').toLowerCase();
      const isActive = stateLower === 'aktiv';

      let endTs;
      if (isActive) {
        endTs = nowDate.getTime();
      } else {
        const endRaw = String(c.next_invoicing_date || '').trim();
        endTs = (endRaw && !endRaw.startsWith('0001')) ? new Date(endRaw).getTime() : startTs;
        if (Number.isNaN(endTs)) endTs = startTs;
      }

      if (startTs < globalMin) globalMin = startTs;
      if (endTs > globalMax) globalMax = endTs;
      if (isActive && nowDate.getTime() > globalMax) globalMax = nowDate.getTime();

      items.push({
        contract_index: i,
        contract_no: String(c.contract_no || ''),
        product_type: String(c.product_type || '').trim(),
        monthly_amount: Number(c.monthly_amount ?? 0),
        is_active: isActive,
        start_ts: startTs,
        end_ts: endTs,
      });
    }

    if (items.length === 0) return;

    // Sort: active first, then by start date
    items.sort((a, b) => {
      if (a.is_active !== b.is_active) return a.is_active ? -1 : 1;
      return a.start_ts - b.start_ts;
    });

    // Extend range to full years so all year ticks are always visible
    const firstYear = new Date(globalMin).getFullYear();
    const lastYear = new Date(globalMax).getFullYear();
    const yearStartMin = new Date(firstYear, 0, 1).getTime();
    const yearStartMax = new Date(lastYear, 0, 1).getTime();
    const rangeMin = Math.min(globalMin, yearStartMin);
    const rangeMax = Math.max(globalMax, yearStartMax);
    const range = rangeMax - rangeMin || 1;
    const padding = range * 0.02;
    const timeMin = rangeMin - padding;
    const timeMax = rangeMax + padding;
    const timeRange = timeMax - timeMin;

    // Build year tick marks + vertical guide lines
    const minYear = new Date(globalMin).getFullYear();
    const maxYear = new Date(globalMax).getFullYear();
    let ticksHtml = '';
    let guidesHtml = '';
    for (let y = minYear; y <= maxYear; y++) {
      const yearTs = new Date(y, 0, 1).getTime();
      const pct = ((yearTs - timeMin) / timeRange) * 100;
      if (pct >= 0 && pct <= 100) {
        ticksHtml += `<div class="helpdesk-tl-tick" style="left:${pct.toFixed(2)}%">
          <span class="helpdesk-tl-tick-label">${y}</span>
        </div>`;
        guidesHtml += `<div class="helpdesk-tl-guide" style="left:${pct.toFixed(2)}%"></div>`;
      }
    }

    // Build swimlane rows
    let rowsHtml = '';
    for (const item of items) {
      const leftPct = ((item.start_ts - timeMin) / timeRange) * 100;
      const widthPct = ((item.end_ts - item.start_ts) / timeRange) * 100;
      const barCls = item.is_active ? 'helpdesk-tl-bar-active' : 'helpdesk-tl-bar-ended';
      const typeLabel = item.product_type ? item.product_type.toUpperCase() : '';
      const amtLabel = item.monthly_amount > 0 ? amountFmt.format(item.monthly_amount) : '';

      const startDate = new Date(item.start_ts).toLocaleDateString();
      const endLabel = item.is_active ? t('active') : new Date(item.end_ts).toLocaleDateString();
      const tooltip = `${item.contract_no} · ${startDate} – ${endLabel}` + (amtLabel ? ` · ${amtLabel}/M` : '');

      rowsHtml += `<div class="helpdesk-tl-row helpdesk-tl-row-clickable" data-contract-index="${item.contract_index}" tabindex="0">
        <div class="helpdesk-tl-label">
          <span class="helpdesk-tl-type">${esc(typeLabel || item.contract_no)}</span>
          <span class="helpdesk-tl-amount">${esc(amtLabel ? amtLabel + '/M' : '')}</span>
        </div>
        <div class="helpdesk-tl-track">
          <div class="helpdesk-tl-bar ${barCls}" style="left:${leftPct.toFixed(2)}%;width:${Math.max(widthPct, 0.5).toFixed(2)}%"
            data-tooltip="${esc(tooltip)}" data-tooltip-pos="top">${item.is_active ? '<span class="helpdesk-tl-bar-arrow"></span>' : ''}</div>
        </div>
      </div>`;
    }

    chartEl.innerHTML = `
      <div class="helpdesk-tl">
        <div class="helpdesk-tl-body">
          <div class="helpdesk-tl-guides">${guidesHtml}</div>
          <div class="helpdesk-tl-rows">${rowsHtml}</div>
        </div>
        <div class="helpdesk-tl-axis">${ticksHtml}</div>
      </div>
      <div class="helpdesk-tl-legend">
        <span class="helpdesk-tl-legend-item">
          <span class="helpdesk-tl-legend-dot helpdesk-tl-dot-active"></span>
          ${esc(t('active'))}
        </span>
        <span class="helpdesk-tl-legend-item">
          <span class="helpdesk-tl-legend-dot helpdesk-tl-dot-ended"></span>
          ${esc(t('ended'))}
        </span>
      </div>
    `;
  }

  async function renderDebitorCommunicationAside() {
    const feedEl = document.getElementById('debitor-communication-feed');
    const loadingEl = document.getElementById('debitor-communication-loading');
    const contentEl = document.getElementById('debitor-communication-content');
    if (!feedEl || !loadingEl || !contentEl) return;

    showLoading('debitor-communication-loading');
    const result = await fetchDebitorCommunication();
    hideLoading('debitor-communication-loading');
    showContent('debitor-communication-content');

    if (!result.ok) {
      feedEl.innerHTML = renderCommunicationError(t('Could not load communication history.'));
      return;
    }

    let html = '';
    if ((result.meta?.soap_partial_failures || 0) > 0) {
      html += renderCommunicationHint(t('Some ticket communications could not be loaded completely.'));
    }
    html += renderCommunicationFeed(result.entries || [], {
      t,
      emptyMessage: 'No communication found.',
      showTicketNo: true,
      ticketBaseUrl,
      fileDownloadUrl,
    });
    feedEl.innerHTML = html;
  }

  // --- Controlling tab ---

  const controllingDashboardUrl = container.dataset.controllingDashboardUrl || '';
  let controllingTabRendered = false;
  let controllingCurrentPeriod = 90;

  async function fetchControllingDashboard(periodDays) {
    const params = buildBaseParams();
    params.set('periodDays', String(periodDays));
    const url = controllingDashboardUrl + '?' + params.toString();
    try {
      return await getJson(url, { signal: requestController.signal });
    } catch {
      return null;
    }
  }

  async function renderControllingTab(periodDays) {
    showLoading('controlling-spinner');
    const contentEl = document.getElementById('controlling-content');
    const errorEl = document.getElementById('controlling-error');
    const emptyEl = document.getElementById('controlling-empty');
    if (contentEl) contentEl.hidden = true;
    if (errorEl) errorEl.hidden = true;
    if (emptyEl) emptyEl.hidden = true;

    const result = await fetchControllingDashboard(periodDays);
    hideLoading('controlling-spinner');

    if (!result?.ok) {
      if (errorEl) {
        errorEl.innerHTML = errorNotice(t('Could not load controlling dashboard.'));
        errorEl.hidden = false;
      }
      controllingTabRendered = true;
      return;
    }

    const kpis = result.kpis || {};
    const meta = result.meta || {};

    if (meta.created_in_period === 0 && meta.created_in_prev === 0 && kpis.backlog_open === 0) {
      if (emptyEl) {
        emptyEl.innerHTML = renderEmptyState({
          message: t('No controlling data available.'),
          size: 'compact',
          icon: false,
        });
        emptyEl.hidden = false;
      }
      controllingTabRendered = true;
      return;
    }

    if (contentEl) contentEl.hidden = false;

    // --- KPI 1: Ticket Volume ---
    const volume = Number(kpis.volume ?? 0);
    const volumePrev = Number(kpis.volume_prev ?? 0);
    const volumeDelta = Number(kpis.volume_delta ?? 0);
    setText('controlling-kpi-volume', String(volume));
    if (volumeDelta !== 0 || volumePrev > 0) {
      const arrow = volumeDelta > 0 ? '↑' : volumeDelta < 0 ? '↓' : '—';
      const abs = Math.abs(volumeDelta);
      // More tickets = more load = negative for controlling
      const cls = volumeDelta > 0 ? 'helpdesk-kpi-trend-negative'
        : volumeDelta < 0 ? 'helpdesk-kpi-trend-positive'
        : 'helpdesk-kpi-trend-neutral';
      setHtml('controlling-kpi-volume-trend',
        `<span class="${cls}">${arrow} ${abs} ${esc(t('vs. prev. period'))}</span>`
      );
    }

    // --- KPI 2: Close-Rate ---
    const closeRate = kpis.close_rate;
    const closeRatePrev = kpis.close_rate_prev;
    setText('controlling-kpi-closerate', closeRate != null ? closeRate.toFixed(1) + '×' : '—');
    if (closeRate != null && closeRatePrev != null) {
      const cls = closeRate >= 1.0 ? 'helpdesk-kpi-trend-positive'
        : 'helpdesk-kpi-trend-negative';
      const prevLabel = closeRatePrev.toFixed(1) + '×';
      setHtml('controlling-kpi-closerate-trend',
        `<span class="${cls}">${esc(t('vs. prev. period'))}: ${prevLabel}</span>`
      );
    } else if (closeRate != null) {
      const cls = closeRate >= 1.0 ? 'helpdesk-kpi-trend-positive' : 'helpdesk-kpi-trend-negative';
      const label = closeRate >= 1.0 ? t('Backlog shrinking') : t('Backlog growing');
      setHtml('controlling-kpi-closerate-trend', `<span class="${cls}">${esc(label)}</span>`);
    }

    // --- KPI 3: Avg. Resolution ---
    const resolution = kpis.resolution_hours;
    const resolutionPrev = kpis.resolution_hours_prev;
    setText('controlling-kpi-resolution', resolution != null ? fmtHours(resolution) : '—');
    if (resolution != null && resolutionPrev != null) {
      const delta = resolution - resolutionPrev;
      if (delta === 0) {
        setHtml('controlling-kpi-resolution-trend',
          `<span class="helpdesk-kpi-trend-neutral">— ${esc(t('vs. prev. period'))}</span>`
        );
      } else {
        const arrow = delta > 0 ? '↑' : '↓';
        const absDelta = Math.abs(Math.round(delta));
        // Lower resolution = better
        const cls = delta < 0 ? 'helpdesk-kpi-trend-positive' : 'helpdesk-kpi-trend-negative';
        setHtml('controlling-kpi-resolution-trend',
          `<span class="${cls}">${arrow} ${fmtHours(absDelta)} ${esc(t('vs. prev. period'))}</span>`
        );
      }
    }

    // --- KPI 4: Open Backlog ---
    const backlog = Number(kpis.backlog_open ?? 0);
    const unassigned = Number(kpis.backlog_unassigned ?? 0);
    const critical = Number(kpis.backlog_critical ?? 0);
    setText('controlling-kpi-backlog', String(backlog));
    const parts = [];
    if (unassigned > 0) parts.push(unassigned + ' ' + t('unassigned'));
    if (critical > 0) parts.push(critical + ' ' + t('critical'));
    if (parts.length > 0) {
      const cls = critical > 0 ? 'helpdesk-kpi-trend-negative' : 'helpdesk-kpi-trend-neutral';
      setHtml('controlling-kpi-backlog-trend', `<span class="${cls}">${esc(parts.join(', '))}</span>`);
    }

    // Trend chart
    renderTrendChart(result.trend || []);



    // Risk indicators
    renderControllingRiskIndicators(result.risk_indicators || [], result.risk_summary || {});

    controllingTabRendered = true;
  }

  function renderTrendChart(trend) {
    const chartEl = document.getElementById('controlling-trend-chart');
    if (!chartEl || !trend.length) return;

    // Find max value for Y-axis scaling
    let max = 0;
    for (const bucket of trend) {
      if (bucket.created > max) max = bucket.created;
      if (bucket.closed > max) max = bucket.closed;
    }
    if (max === 0) max = 1;

    // Compute nice grid lines (2-3 lines)
    const gridStep = max <= 5 ? 1 : max <= 15 ? 5 : max <= 50 ? 10 : Math.ceil(max / 4 / 10) * 10;
    let gridHtml = '';
    for (let v = gridStep; v <= max; v += gridStep) {
      const pct = ((v / max) * 100).toFixed(1);
      gridHtml += `<div class="helpdesk-vchart-grid-line" style="bottom:${pct}%">
        <span class="helpdesk-vchart-grid-label">${v}</span>
      </div>`;
    }

    // Bar groups
    let barsHtml = '';
    for (const bucket of trend) {
      const createdPct = ((bucket.created / max) * 100).toFixed(1);
      const closedPct = ((bucket.closed / max) * 100).toFixed(1);
      barsHtml += `<div class="helpdesk-vchart-group">
        <div class="helpdesk-vchart-bars">
          <div class="helpdesk-vchart-bar helpdesk-vchart-bar-created" style="height:${createdPct}%" data-tooltip="${bucket.created}" data-tooltip-pos="top"></div>
          <div class="helpdesk-vchart-bar helpdesk-vchart-bar-closed" style="height:${closedPct}%" data-tooltip="${bucket.closed}" data-tooltip-pos="top"></div>
        </div>
        <span class="helpdesk-vchart-label">${esc(bucket.label)}</span>
      </div>`;
    }

    chartEl.innerHTML = `
      <div class="helpdesk-vchart-area">
        ${gridHtml}
        <div class="helpdesk-vchart-groups">${barsHtml}</div>
      </div>
      <div class="helpdesk-vchart-legend">
        <span class="helpdesk-vchart-legend-item">
          <span class="helpdesk-vchart-legend-dot helpdesk-vchart-dot-created"></span>
          ${esc(t('Created'))}
        </span>
        <span class="helpdesk-vchart-legend-item">
          <span class="helpdesk-vchart-legend-dot helpdesk-vchart-dot-closed"></span>
          ${esc(t('Closed'))}
        </span>
      </div>
    `;
  }

  function renderControllingRiskIndicators(indicators, summary) {
    const el = document.getElementById('controlling-risk-indicators');
    if (!el || !indicators.length) return;

    // Count triggered from actual indicator data (single source of truth)
    const total = indicators.length;
    const triggered = indicators.filter(ind => !!ind.triggered).length;
    const summaryLabel = triggered === 0
      ? t('All checks passed')
      : triggered + ' ' + t('of') + ' ' + total + ' ' + t('checks flagged');
    const summaryCls = triggered === 0 ? 'helpdesk-risk-summary-ok'
      : triggered <= 2 ? 'helpdesk-risk-summary-warn'
      : 'helpdesk-risk-summary-critical';

    let html = `<div class="helpdesk-risk-header">
      <span class="helpdesk-risk-header-icon ${summaryCls}">${triggered === 0 ? '✓' : '⚠'}</span>
      <span class="helpdesk-risk-header-label">${esc(summaryLabel)}</span>
    </div>`;
    html += '<div class="helpdesk-risk-checks">';
    for (const ind of indicators) {
      const icon = ind.triggered ? '⚠' : '✓';
      const iconCls = ind.triggered ? 'helpdesk-risk-icon-warn' : 'helpdesk-risk-icon-ok';
      const barCls = ind.triggered ? 'helpdesk-risk-bar-over' : 'helpdesk-risk-bar-ok';

      // Threshold bar: normalised to whichever is larger (value or threshold)
      // so the bar always fits inside the track and the limit marker stays visible.
      const rawVal = ind.raw_value ?? 0;
      const thresholdVal = ind.threshold_value ?? 1;
      const scale = Math.max(rawVal, thresholdVal, 1); // prevent division by 0
      const fillPct = Math.min(100, (rawVal / scale) * 100);
      const limitPct = Math.min(100, (thresholdVal / scale) * 100);

      // Format display value with fmtHours for hour-based units
      const displayValue = ind.unit === 'h' && typeof rawVal === 'number' && rawVal > 0
        ? fmtHours(rawVal)
        : esc(ind.value);
      const limitLabel = ind.unit === 'h' ? t('Limit') + ' ' + fmtHours(thresholdVal) : t('Limit') + ' ' + thresholdVal + ind.unit;

      const descText = ind.description ? t(ind.description) : '';

      html += `<div class="helpdesk-risk-check">
        <div class="helpdesk-risk-check-header">
          <span class="helpdesk-risk-icon ${iconCls}">${icon}</span>
          <span class="helpdesk-risk-check-label">${esc(t(ind.label) || ind.label)}</span>
        </div>
        <div class="helpdesk-risk-check-value">${displayValue}</div>
        ${descText ? `<div class="helpdesk-risk-check-desc">${esc(descText)}</div>` : ''}
        <div class="helpdesk-risk-bar-wrapper">
          <div class="helpdesk-risk-bar-track">
            <div class="helpdesk-risk-bar-fill ${barCls}" style="width:${fillPct}%"></div>
          </div>
          <div class="helpdesk-risk-bar-limit" style="left:${limitPct}%"></div>
        </div>
        <div class="helpdesk-risk-bar-legend">${esc(limitLabel)}</div>
      </div>`;
    }
    html += '</div>';
    el.innerHTML = html;
  }

  // Period selector (radio inputs)
  const periodSelector = document.getElementById('controlling-period-selector');
  on(periodSelector, 'change', (e) => {
    const radio = e.target.closest('input[name="controlling-period"]');
    if (!radio) return;
    const period = parseInt(radio.value, 10);
    if (!period || period === controllingCurrentPeriod) return;

    controllingCurrentPeriod = period;
    controllingTabRendered = false;
    void renderControllingTab(period);
  });

  const tabPanels = container.querySelectorAll('[data-tab-panel]');
  const observer = new MutationObserver(() => {
    for (const panel of tabPanels) {
      if (panel.hidden) continue;
      const tabName = panel.dataset.tabPanel;
      if (tabName === 'support') {
        renderSupportTab();
        initTicketsGrid();
      } else if (tabName === 'sales') {
        renderSalesTab();
      } else if (tabName === 'controlling') {
        if (!controllingTabRendered) {
          renderControllingTab(controllingCurrentPeriod);
        }
      }
    }
  });

  for (const panel of tabPanels) {
    observer.observe(panel, { attributes: true, attributeFilter: ['hidden'] });
  }

  const refreshButton = container.querySelector('button[name="support-refresh"]')
    || document.getElementById('support-refresh-button');
  on(refreshButton, 'click', () => {
    refreshButton.setAttribute('aria-busy', 'true');
    refreshButton.setAttribute('disabled', 'disabled');

    const url = new URL(window.location.href);
    url.searchParams.set('refresh', '1');
    window.location.assign(url.toString());
  });

  renderSupportTab();
  void initTicketsGrid();
  void renderDebitorCommunicationAside();

  return {
    destroy: () => {
      listenerController.abort();
      requestController.abort();
      observer.disconnect();
      const dialog = document.getElementById('helpdesk-contract-dialog');
      if (dialog) {
        dialog.remove();
      }
    },
  };
}
