/**
 * @fileoverview Helper utilities for error inspection and normalization.
 * Enhanced with cause chain extraction and circular reference detection.
 * @module src/utils/internal/error-handler/helpers
 */
import { ZodError } from 'zod';
import { McpError } from '../../../types-global/errors.js';
import { isAggregateError } from '../../../utils/types/guards.js';
/**
 * Formats a ZodError as a human-readable sentence.
 *
 * `ZodError.message` is a serialized JSON array of `ZodIssue` objects — useful for
 * debugging but unreadable in logs, client error messages, and UI surfaces. This
 * helper extracts the first issue's message (typically the most actionable) and
 * appends path + overflow count so `error.message` reads as prose.
 *
 * Pair with `ErrorHandler.classifyOnly` (which returns `data: { issues }`) to
 * preserve the structured issue array for clients that can render field-level
 * errors.
 *
 * @param err - The ZodError to format.
 * @returns A single-line sentence, e.g. `"Expected string at nctId (+2 more)"`.
 */
export function formatZodErrorMessage(err) {
    const issues = err.issues;
    const first = issues[0];
    if (!first)
        return 'Validation failed';
    const path = first.path.length > 0 ? ` at ${first.path.map(String).join('.')}` : '';
    const rest = issues.length - 1;
    const tail = rest > 0 ? ` (+${rest} more)` : '';
    return `${first.message}${path}${tail}`;
}
/**
 * Retrieves a descriptive name for an error object or value.
 *
 * - `Error` instances → `error.name` (e.g. `'TypeError'`), falling back to `'Error'`.
 * - `null` → `'NullValueEncountered'`
 * - `undefined` → `'UndefinedValueEncountered'`
 * - Non-plain objects with a named constructor → `'<ConstructorName>Encountered'`
 * - Everything else → `'<typeof value>Encountered'` (e.g. `'stringEncountered'`)
 *
 * @param error - The error object or value.
 * @returns A stable, human-readable string identifying the error's type.
 *
 * @example
 * ```ts
 * getErrorName(new TypeError('bad'));   // → 'TypeError'
 * getErrorName('oops');                 // → 'stringEncountered'
 * getErrorName(null);                   // → 'NullValueEncountered'
 * ```
 */
export function getErrorName(error) {
    if (error instanceof Error) {
        return error.name || 'Error';
    }
    if (error === null) {
        return 'NullValueEncountered';
    }
    if (error === undefined) {
        return 'UndefinedValueEncountered';
    }
    if (typeof error === 'object' &&
        error !== null &&
        error.constructor &&
        typeof error.constructor.name === 'string' &&
        error.constructor.name !== 'Object') {
        return `${error.constructor.name}Encountered`;
    }
    return `${typeof error}Encountered`;
}
/**
 * Extracts a human-readable message string from any thrown value.
 *
 * Handles every JavaScript type so that `catch (e)` blocks never produce `[object Object]`:
 * - `AggregateError` → combines up to 3 inner error messages after the outer message.
 * - `Error` → `error.message`
 * - `null` / `undefined` → descriptive literal strings.
 * - Primitives (`string`, `number`, `boolean`, `bigint`, `symbol`) → string-coerced value.
 * - Functions → `[function <name>]`
 * - Objects → JSON-serialized if possible; otherwise constructor name fallback.
 * - If conversion itself throws, returns a safe fallback describing the conversion error.
 *
 * @param error - The thrown value to extract a message from.
 * @returns A non-empty string describing the error.
 *
 * @example
 * ```ts
 * getErrorMessage(new Error('oops'));              // → 'oops'
 * getErrorMessage('string thrown');               // → 'string thrown'
 * getErrorMessage(42);                            // → '42'
 * getErrorMessage(null);                          // → 'Null value encountered as error'
 * getErrorMessage(new AggregateError([new Error('a'), new Error('b')], 'multi'));
 * // → 'multi: a; b'
 * ```
 */
export function getErrorMessage(error) {
    try {
        if (error instanceof ZodError) {
            return formatZodErrorMessage(error);
        }
        if (error instanceof Error) {
            // AggregateError should surface combined messages succinctly
            if (isAggregateError(error)) {
                const inner = error.errors
                    .map((e) => (e instanceof Error ? e.message : String(e)))
                    .filter(Boolean)
                    .slice(0, 3)
                    .join('; ');
                return inner ? `${error.message}: ${inner}` : error.message;
            }
            return error.message;
        }
        if (error === null) {
            return 'Null value encountered as error';
        }
        if (error === undefined) {
            return 'Undefined value encountered as error';
        }
        if (typeof error === 'string') {
            return error;
        }
        if (typeof error === 'number' || typeof error === 'boolean') {
            return String(error);
        }
        if (typeof error === 'bigint') {
            return error.toString();
        }
        if (typeof error === 'function') {
            return `[function ${error.name || 'anonymous'}]`;
        }
        if (typeof error === 'object') {
            try {
                const json = JSON.stringify(error);
                if (json && json !== '{}')
                    return json;
            }
            catch {
                // fall through
            }
            const ctor = error.constructor?.name;
            return `Non-Error object encountered (constructor: ${ctor || 'Object'})`;
        }
        if (typeof error === 'symbol') {
            return error.toString();
        }
        // c8 ignore next
        return '[unrepresentable error]';
    }
    catch (conversionError) {
        return `Error converting error to string: ${conversionError instanceof Error ? conversionError.message : 'Unknown conversion error'}`;
    }
}
/**
 * Extracts the complete error cause chain into a flat array of `ErrorCauseNode` objects.
 *
 * Starts at `error` (depth 0) and follows `error.cause` links until:
 * - a non-Error value is encountered (appended as the terminal node, then stops),
 * - a circular reference is detected (sentinel node appended, then stops),
 * - or `maxDepth` is reached (sentinel node appended, then stops).
 *
 * String causes are treated as terminal `StringError` nodes.
 * `McpError` nodes include the `data` property when present.
 * Circular references are detected via `WeakSet` identity tracking.
 *
 * @param error - The outermost error to start traversal from.
 * @param maxDepth - Maximum number of nodes to traverse before stopping. Defaults to `20`.
 * @returns An array of `ErrorCauseNode` objects ordered from outermost (depth 0) to deepest cause.
 *          An empty array is returned if `error` itself is falsy.
 *
 * @example
 * ```ts
 * const inner = new Error('db connection failed');
 * const outer = new Error('user lookup failed', { cause: inner });
 * const chain = extractErrorCauseChain(outer);
 * // → [
 * //   { name: 'Error', message: 'user lookup failed', depth: 0, stack: '...' },
 * //   { name: 'Error', message: 'db connection failed', depth: 1, stack: '...' },
 * // ]
 * ```
 */
export function extractErrorCauseChain(error, maxDepth = 20) {
    const chain = [];
    const seen = new WeakSet();
    let current = error;
    let depth = 0;
    while (current && depth < maxDepth) {
        // Circular reference detection
        if (typeof current === 'object' && current !== null) {
            if (seen.has(current)) {
                chain.push({
                    name: 'CircularReference',
                    message: 'Circular reference detected in error cause chain',
                    depth,
                });
                break;
            }
            seen.add(current);
        }
        if (current instanceof Error) {
            const node = {
                name: current.name,
                message: current.message,
                depth,
                // Only include stack if it exists (exact optional property types)
                ...(current.stack !== undefined ? { stack: current.stack } : {}),
            };
            // Extract data from McpError instances
            if (current instanceof McpError && current.data) {
                node.data = current.data;
            }
            chain.push(node);
            // Continue traversing cause chain
            current = current.cause;
        }
        else if (typeof current === 'string') {
            chain.push({
                name: 'StringError',
                message: current,
                depth,
            });
            break;
        }
        else {
            chain.push({
                name: getErrorName(current),
                message: getErrorMessage(current),
                depth,
            });
            break;
        }
        depth++;
    }
    if (depth >= maxDepth) {
        chain.push({
            name: 'MaxDepthExceeded',
            message: `Error cause chain exceeded maximum depth of ${maxDepth}`,
            depth,
        });
    }
    return chain;
}
//# sourceMappingURL=helpers.js.map