// Pure utility functions extracted from app.js
// These have zero DOM coupling and zero global state.

/**
 * Escape HTML special characters to safe entities.
 * @param {string} str
 * @returns {string}
 */
export function escapeHtml(str) {
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
}

/**
 * Format a Unix timestamp (seconds) into a relative time string.
 * @param {number|null|undefined} ts
 * @returns {string}
 */
export function formatTimestamp(ts) {
  if (ts == null) return '';
  const diff = Math.floor(Date.now() / 1000 - ts);
  if (diff < 60) return `${diff}s ago`;
  if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
  return `${Math.floor(diff / 3600)}h ago`;
}

/**
 * Return the priority label for a session object.
 * @param {object} session
 * @returns {'bell'|'active'|'idle'}
 */
export function sessionPriority(session) {
  const bell = session && session.bell;
  if (bell && bell.unseen_count > 0 && (bell.seen_at === null || bell.last_fired_at > bell.seen_at)) {
    return 'bell';
  }
  const activity = session && session.last_activity_at;
  if (activity && (Date.now() / 1000 - activity) < 300) {
    return 'active';
  }
  return 'idle';
}

/** Priority rank map. Lower = higher priority. */
export const PRIORITY_RANK = { bell: 0, active: 1, idle: 2 };

/**
 * Sort sessions by priority (ascending rank). Returns new array.
 * @param {object[]} sessions
 * @returns {object[]}
 */
export function sortByPriority(sessions) {
  return sessions.slice().sort((a, b) => {
    const rankA = PRIORITY_RANK[sessionPriority(a)] ?? 2;
    const rankB = PRIORITY_RANK[sessionPriority(b)] ?? 2;
    return rankA - rankB;
  });
}

/**
 * Filter sessions by search query (case-insensitive substring on name).
 * @param {object[]} sessions
 * @param {string|null} query
 * @returns {object[]}
 */
export function filterByQuery(sessions, query) {
  if (!query) return sessions;
  const q = query.toLowerCase();
  return sessions.filter((s) => (s.name || '').toLowerCase().includes(q));
}

/**
 * Detect sessions that transitioned to new/increased bell state.
 * @param {object[]} prev
 * @param {object[]} next
 * @returns {string[]}
 */
export function detectBellTransitions(prev, next) {
  const prevMap = new Map(
    (prev || []).map((s) => [s.sessionKey || s.name, (s.bell && s.bell.unseen_count) || 0]),
  );
  return (next || [])
    .filter((s) => {
      const unseen = s.bell && s.bell.unseen_count;
      if (!unseen || unseen <= 0) return false;
      const key = s.sessionKey || s.name;
      const prevCount = prevMap.has(key) ? prevMap.get(key) : 0;
      return unseen > prevCount;
    })
    .map((s) => s.name);
}

/**
 * Generate a pseudo-random device ID string.
 * @returns {string}
 */
export function generateDeviceId() {
  return 'd-' + Math.random().toString(36).padEnd(10, '0').slice(2, 10);
}

/**
 * Build a heartbeat payload object.
 * @param {string} device_id
 * @param {string|null} viewing_session
 * @param {string} view_mode
 * @param {number} last_interaction_at
 * @returns {object}
 */
export function buildHeartbeatPayload(device_id, viewing_session, view_mode, last_interaction_at) {
  const label =
    typeof navigator !== 'undefined' && navigator.userAgent
      ? navigator.userAgent.slice(0, 50)
      : 'unknown';
  return { device_id, label, viewing_session, view_mode, last_interaction_at };
}

// ---------------------------------------------------------------------------
// ANSI escape -> HTML span converter (SGR codes only)
// ---------------------------------------------------------------------------

const ANSI_COLORS = [
  '#2e3436','#cc0000','#4e9a06','#c4a000','#3465a4','#75507b','#06989a','#d3d7cf',
  '#555753','#ef2929','#8ae234','#fce94f','#729fcf','#ad7fa8','#34e2e2','#eeeeec'
];

function ansi256Color(n) {
  if (n < 16) return ANSI_COLORS[n];
  if (n >= 232) { const g = 8 + (n - 232) * 10; return `rgb(${g},${g},${g})`; }
  n -= 16;
  const r = Math.floor(n / 36) * 51;
  const g2 = Math.floor((n % 36) / 6) * 51;
  const b = (n % 6) * 51;
  return `rgb(${r},${g2},${b})`;
}

function ansiParamsToStyle(params) {
  const styles = [];
  let k = 0;
  while (k < params.length) {
    const p = parseInt(params[k], 10) || 0;
    if (p === 0) return 'reset';
    if (p === 1) styles.push('font-weight:bold');
    else if (p === 2) styles.push('opacity:0.7');
    else if (p === 3) styles.push('font-style:italic');
    else if (p === 4) styles.push('text-decoration:underline');
    else if (p === 7) styles.push('filter:invert(1)');
    else if (p === 9) styles.push('text-decoration:line-through');
    else if (p >= 30 && p <= 37) styles.push('color:' + ANSI_COLORS[p - 30]);
    else if (p === 38 && params[k + 1] === '5') {
      styles.push('color:' + ansi256Color(parseInt(params[k + 2], 10) || 0));
      k += 2;
    }
    else if (p === 39) styles.push('color:inherit');
    else if (p >= 40 && p <= 47) styles.push('background:' + ANSI_COLORS[p - 40]);
    else if (p === 48 && params[k + 1] === '5') {
      styles.push('background:' + ansi256Color(parseInt(params[k + 2], 10) || 0));
      k += 2;
    }
    else if (p === 49) styles.push('background:inherit');
    else if (p >= 90 && p <= 97) styles.push('color:' + ANSI_COLORS[p - 90 + 8]);
    else if (p >= 100 && p <= 107) styles.push('background:' + ANSI_COLORS[p - 100 + 8]);
    k++;
  }
  return styles.length ? styles.join(';') : '';
}

/**
 * Convert ANSI SGR escape sequences to HTML spans with inline styles.
 * @param {string} raw
 * @returns {string}
 */
export function ansiToHtml(raw) {
  if (!raw) return '';
  let out = '';
  let spans = 0;
  let i = 0;
  const len = raw.length;

  while (i < len) {
    if (raw[i] === '\x1b' && raw[i + 1] === '[') {
      let j = i + 2;
      while (j < len && raw[j] !== 'm' && j - i < 20) j++;
      if (j < len && raw[j] === 'm') {
        const params = raw.substring(i + 2, j).split(';');
        const style = ansiParamsToStyle(params);
        if (style === 'reset') {
          while (spans > 0) { out += '</span>'; spans--; }
        } else if (style) {
          out += '<span style="' + style + '">';
          spans++;
        }
        i = j + 1;
        continue;
      }
    }
    const ch = raw[i];
    if (ch === '<') out += '&lt;';
    else if (ch === '>') out += '&gt;';
    else if (ch === '&') out += '&amp;';
    else if (ch === '"') out += '&quot;';
    else out += ch;
    i++;
  }
  while (spans > 0) { out += '</span>'; spans--; }
  return out;
}