/**
 * @module utils/callerLocation
 * @description Stack-frame extraction utility used by error/warning messages
 * across Brainy. The first non-Brainy frame on the stack is the consumer's
 * actual call site — the one that's useful to point at in diagnostics.
 *
 * Used by:
 * - Subtype enforcement errors in `brainy.ts` (7.30.1+) — guides consumers
 *   from `requireSubtype()` rejections back to the offending call site.
 * - Query-limit enforcement in `paramValidation.ts` (7.30.2+) — pairs with
 *   the recalibrated limit cap so consumers see which `find({ limit })`
 *   call triggered the warn-or-throw.
 *
 * No runtime dependencies. Pure stack walking + string matching.
 */

/**
 * Extract the first non-Brainy frame from the current stack so error and
 * warning messages can point at the consumer's call site instead of Brainy
 * internals.
 *
 * The function walks `new Error().stack`, skips any frame inside Brainy's own
 * source or compiled output, and returns the first remaining frame stripped of
 * the leading `at ` token so the caller can compose the rendered line however
 * it likes.
 *
 * @param extraSkipPatterns Optional list of substrings — frames matching ANY
 *   of these are also skipped. Use for in-module helpers that show up between
 *   the public API and the consumer (e.g. the formatter that builds the
 *   error message itself).
 * @returns The caller's location string (e.g. `"OrderService.create (/app/src/orders/service.ts:42:23)"`)
 *   or `null` when the stack isn't available or only contains Brainy frames.
 */
export function findCallerLocation(extraSkipPatterns: string[] = []): string | null {
  const stack = new Error().stack
  if (!stack) return null
  const lines = stack.split('\n').slice(1)  // drop the `Error` line
  for (const raw of lines) {
    const line = raw.trim()
    // Always skip Brainy's own source + compiled output. Consumers need their
    // own call site, not a line inside `brainy.ts` or `dist/brainy.js`.
    if (line.includes('/src/brainy.ts') || line.includes('/dist/brainy.js')) continue
    // Skip the validation + diagnostic helpers regardless of which file they
    // live in — they're plumbing between the public API and the consumer.
    if (line.includes('findCallerLocation')) continue
    if (line.includes('paramValidation') && line.includes('validate')) continue
    if (line.includes('enforceSubtypeOn') || line.includes('formatSubtypeError')) continue
    // Caller-supplied patterns (e.g. specific formatter names)
    if (extraSkipPatterns.some(p => line.includes(p))) continue
    // Strip leading `at ` if present so the caller can format consistently.
    return line.replace(/^at /, '')
  }
  return null
}
