Source: core/primitive/diagnosticReference.js

/*
 * Copyright 2026 The Ray Optics Simulation authors and 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
 */

/**
 * Unicode diagnostic delimiters keep references distinct from JSON, i18next
 * interpolation, HTML, and the Markdown-like link syntax used by the app.
 * The short `pc` type tag keeps raw console/integration warnings readable.
 */
export const PRIMITIVE_CURVE_REFERENCE_PREFIX = '⟦pc:'
export const PRIMITIVE_CURVE_REFERENCE_SUFFIX = '⟧'

/**
 * Format a processed primitive curve ID for detection by diagnostic UIs.
 *
 * @param {number} curveId
 * @returns {string}
 */
export function formatPrimitiveCurveReference(curveId) {
  if (!Number.isSafeInteger(curveId) || curveId < 0) {
    throw new TypeError(`Invalid primitive curve ID: ${curveId}`)
  }
  return `${PRIMITIVE_CURVE_REFERENCE_PREFIX}${curveId}${PRIMITIVE_CURVE_REFERENCE_SUFFIX}`
}

/**
 * Find namespaced primitive-curve references in a diagnostic message.
 *
 * @param {string} message
 * @returns {Array<{start: number, end: number, curveId: number, reference: string}>}
 */
export function findPrimitiveCurveReferenceSpans(message) {
  const spans = []
  let searchStart = 0
  while (searchStart < message.length) {
    const start = message.indexOf(
      PRIMITIVE_CURVE_REFERENCE_PREFIX,
      searchStart
    )
    if (start === -1) break

    const idStart = start + PRIMITIVE_CURVE_REFERENCE_PREFIX.length
    const suffixStart = message.indexOf(
      PRIMITIVE_CURVE_REFERENCE_SUFFIX,
      idStart
    )
    if (suffixStart === -1) break

    const idText = message.slice(idStart, suffixStart)
    const curveId = Number(idText)
    const isCanonicalId =
      /^(0|[1-9]\d*)$/.test(idText) &&
      Number.isSafeInteger(curveId)
    if (isCanonicalId) {
      spans.push({
        start,
        end: suffixStart,
        curveId,
        reference: message.slice(start, suffixStart + 1)
      })
      searchStart = suffixStart + 1
    } else {
      // Resume after this prefix so malformed text cannot hide a later valid
      // reference which happens to precede its closing delimiter.
      searchStart = idStart
    }
  }
  return spans
}