/*
 * Copyright © 2025-26 l5yth & contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Helpers used by the long-name link click router and overlay name fallback.
 *
 * @module main/long-link-router
 */

import { canonicalNodeIdentifier, normalizeNodeNameValue } from '../node-rendering.js';

/**
 * Determine whether a long name link should trigger the overlay behaviour.
 *
 * @param {?Element} link Anchor element.
 * @returns {boolean} ``true`` when the link participates in overlays.
 */
export function shouldHandleNodeLongLink(link) {
  if (!link || !link.dataset) return false;
  if ('nodeDetailLink' in link.dataset && link.dataset.nodeDetailLink === 'false') {
    return false;
  }
  return true;
}

/**
 * Extract the canonical identifier from a node detail hyperlink.
 *
 * @param {string} href Link href attribute.
 * @returns {string} Canonical identifier or ``''``.
 */
export function extractIdentifierFromHref(href) {
  if (typeof href !== 'string' || href.length === 0) {
    return '';
  }
  const match = href.match(/\/nodes\/(![^/?#]+)/i);
  if (!match || !match[1]) {
    return '';
  }
  try {
    const decoded = decodeURIComponent(match[1]);
    return canonicalNodeIdentifier(decoded) ?? '';
  } catch {
    return canonicalNodeIdentifier(match[1]) ?? '';
  }
}

/**
 * Extract the canonical node identifier from the provided link element.
 *
 * @param {?Element} link Anchor element.
 * @returns {string} Canonical node identifier or ``''`` when unavailable.
 */
export function getNodeIdentifierFromLink(link) {
  if (!link) return '';
  const datasetIdentifier = link.dataset && typeof link.dataset.nodeId === 'string'
    ? canonicalNodeIdentifier(link.dataset.nodeId)
    : null;
  if (datasetIdentifier) {
    return datasetIdentifier;
  }
  if (typeof link.getAttribute === 'function') {
    const attrHref = link.getAttribute('href');
    const canonicalFromAttr = extractIdentifierFromHref(attrHref);
    if (canonicalFromAttr) {
      return canonicalFromAttr;
    }
  }
  if (typeof link.href === 'string') {
    const canonicalFromProperty = extractIdentifierFromHref(link.href);
    if (canonicalFromProperty) {
      return canonicalFromProperty;
    }
  }
  return '';
}

/**
 * Determine the preferred display name for overlay content.
 *
 * @param {Object} node Node payload.
 * @returns {string} Friendly display name.
 */
export function getNodeDisplayNameForOverlay(node) {
  if (!node || typeof node !== 'object') return '';
  return (
    normalizeNodeNameValue(node.long_name ?? node.longName) ||
    normalizeNodeNameValue(node.short_name ?? node.shortName) ||
    (typeof node.node_id === 'string' ? node.node_id : '')
  );
}

/**
 * Map a node-payload ``protocol`` field to the display label used in fallback
 * long names.  Unknown or absent protocol values resolve to ``"Unknown"``
 * rather than defaulting to a specific protocol — a missing stamp means the
 * sender's protocol genuinely cannot be determined here, and guessing
 * silently mislabels cross-protocol chat senders.  ``message-node-hydrator.js``
 * threads ``message.protocol`` onto the placeholder before invoking the
 * fallback so chat lookups for 404'd senders pick the right label.
 *
 * @param {*} protocol Raw protocol value from the node payload.
 * @returns {string} Display label such as ``Meshtastic``, ``Meshcore``, or
 *   ``Unknown``.
 */
function protocolFallbackLabel(protocol) {
  if (protocol == null) return 'Unknown';
  const normalized = String(protocol).trim().toLowerCase();
  if (normalized === 'meshcore') return 'Meshcore';
  if (normalized === 'meshtastic') return 'Meshtastic';
  return 'Unknown';
}

/**
 * Build a minimal placeholder node ready for {@link applyNodeNameFallback},
 * carrying any ``protocol`` field discoverable on the surrounding payload
 * (a chat message, a neighbor record, etc.).  Threading the protocol through
 * here is what keeps the fallback long-name label aligned with the source
 * radio instead of collapsing to the neutral ``"Unknown"`` fallback.
 *
 * @param {string} nodeId Canonical node identifier for the placeholder.
 * @param {Object|null|undefined} [source] Optional surrounding payload whose
 *   ``protocol`` field should be inherited (e.g. message, neighbor entry).
 * @returns {{node_id: string, protocol?: string}} Placeholder node ready for
 *   downstream fallback population.
 */
export function buildNodePlaceholder(nodeId, source) {
  const placeholder = { node_id: nodeId };
  if (source && typeof source === 'object' && source.protocol != null) {
    placeholder.protocol = source.protocol;
  }
  return placeholder;
}

/**
 * Populate missing node name fields with sensible defaults.
 *
 * The fallback label respects ``node.protocol`` when present so MeshCore chat
 * senders rendered from a hydrator placeholder are not mislabelled as
 * Meshtastic.  Callers without protocol context (e.g. neighbour overlays)
 * may leave the field unset and accept the neutral ``"Unknown"`` label.
 *
 * @param {Object} node Node payload.
 * @returns {void}
 */
export function applyNodeNameFallback(node) {
  if (!node || typeof node !== 'object') return;
  const short = normalizeNodeNameValue(node.short_name ?? node.shortName);
  const long = normalizeNodeNameValue(node.long_name ?? node.longName);
  if (short || long) return;
  const nodeId = normalizeNodeNameValue(node.node_id ?? node.nodeId);
  if (!nodeId) return;
  const fallbackShort = nodeId.slice(-4);
  const fallbackLong = `${protocolFallbackLabel(node.protocol)} ${nodeId}`;
  node.short_name = fallbackShort;
  node.long_name = fallbackLong;
  if ('shortName' in node) node.shortName = fallbackShort;
  if ('longName' in node) node.longName = fallbackLong;
}
