/**
 * @fileoverview Table formatter utility for creating tables in multiple formats.
 * Supports markdown, ASCII, grid (Unicode), and compact table styles with configurable
 * alignment, truncation, and formatting options.
 * @module src/utils/formatting/tableFormatter
 */
import { JsonRpcErrorCode, McpError, validationError } from '../../types-global/errors.js';
import { logger } from '../../utils/internal/logger.js';
import { requestContextService } from '../../utils/internal/requestContext.js';
/**
 * Utility class for formatting tabular data into various table styles.
 *
 * Provides two rendering methods:
 * - {@link format} — accepts an array of objects and extracts headers automatically.
 * - {@link formatRaw} — accepts explicit headers and a 2D string array for full control.
 *
 * Column widths are computed from the widest value in each column, clamped by
 * `minWidth` and `maxWidth`. Content that exceeds `maxWidth` is truncated with
 * an ellipsis (`...`) when `truncate` is `true` (the default).
 *
 * @example
 * ```typescript
 * const data = [
 *   { name: 'Alice', score: 42 },
 *   { name: 'Bob',   score: 7  },
 * ];
 *
 * // Markdown (default style)
 * tableFormatter.format(data);
 * // | name  | score |
 * // | ----- | ----- |
 * // | Alice | 42    |
 * // | Bob   | 7     |
 *
 * // Unicode grid with right-aligned score column
 * tableFormatter.format(data, { style: 'grid', alignment: { score: 'right' } });
 * ```
 */
export class TableFormatter {
    /**
     * Default formatting options.
     * @private
     */
    defaultOptions = {
        style: 'markdown',
        alignment: {},
        maxWidth: 50,
        truncate: true,
        headerStyle: 'none',
        minWidth: 3,
        padding: 1,
    };
    /**
     * Format an array of objects as a table.
     *
     * Headers are derived from the keys of the first object. Every value is
     * converted to a string via an internal `stringify` helper that handles
     * primitives, arrays (JSON), objects (JSON), and special values (`null`,
     * `undefined`, functions, symbols).
     *
     * @template T - Type of objects in the array; must extend `Record<string, unknown>`.
     * @param data - Array of objects to render. Returns `''` immediately if empty.
     * @param options - Table formatting options. All fields are optional; defaults apply.
     * @param context - Optional request context used for correlated debug logging.
     *   A new context is created automatically when omitted.
     * @returns Formatted table string in the requested style, or `''` if `data` is empty.
     * @throws {McpError} With `ValidationError` code if `data` is not an array.
     * @throws {McpError} With `InternalError` code if rendering fails unexpectedly.
     *
     * @example
     * ```typescript
     * const data = [
     *   { name: 'Alice', age: 30, role: 'Engineer' },
     *   { name: 'Bob',   age: 25, role: 'Designer' },
     * ];
     *
     * // Default markdown output
     * tableFormatter.format(data);
     *
     * // Unicode grid with right-aligned age column
     * tableFormatter.format(data, { style: 'grid', alignment: { age: 'right' } });
     * ```
     */
    format(data, options, context) {
        const logContext = context ||
            requestContextService.createRequestContext({
                operation: 'TableFormatter.format',
            });
        if (!Array.isArray(data)) {
            throw validationError('Data must be an array', logContext);
        }
        if (data.length === 0) {
            logger.debug('Empty data array provided to table formatter', logContext);
            return '';
        }
        // Extract headers from first object (safe: length checked above)
        const firstRow = data[0];
        const headers = Object.keys(firstRow);
        // Convert objects to 2D array
        const rows = data.map((obj) => headers.map((header) => this.stringify(obj[header])));
        logger.debug('Formatting table from object array', {
            ...logContext,
            rowCount: rows.length,
            columnCount: headers.length,
        });
        return this.formatRaw(headers, rows, options, context);
    }
    /**
     * Format a raw 2D string array with explicit headers.
     *
     * Unlike {@link format}, no automatic key extraction or value stringification
     * occurs — callers supply pre-serialized strings. All rows must have exactly
     * the same number of columns as `headers`; a mismatch throws immediately.
     *
     * @param headers - Non-empty array of column header strings.
     * @param rows - 2D array of string cell values. Every inner array must have
     *   the same length as `headers`. Returns `''` immediately if `rows` is empty.
     * @param options - Table formatting options. All fields are optional; defaults apply.
     * @param context - Optional request context used for correlated debug logging.
     *   A new context is created automatically when omitted.
     * @returns Formatted table string in the requested style, or `''` if `rows` is empty.
     * @throws {McpError} With `ValidationError` code if `headers` is empty or not an array,
     *   `rows` is not an array, or any row's column count differs from `headers.length`.
     * @throws {McpError} With `InternalError` code if rendering fails unexpectedly.
     *
     * @example
     * ```typescript
     * const headers = ['Name', 'Age', 'Role'];
     * const rows = [
     *   ['Alice', '30', 'Engineer'],
     *   ['Bob',   '25', 'Designer'],
     * ];
     * const table = tableFormatter.formatRaw(headers, rows, { style: 'ascii' });
     * ```
     */
    formatRaw(headers, rows, options, context) {
        const logContext = context ||
            requestContextService.createRequestContext({
                operation: 'TableFormatter.formatRaw',
            });
        // Validate inputs
        if (!Array.isArray(headers) || headers.length === 0) {
            throw validationError('Headers must be a non-empty array', logContext);
        }
        if (!Array.isArray(rows)) {
            throw validationError('Rows must be an array', logContext);
        }
        if (rows.length === 0) {
            logger.debug('Empty rows array provided to table formatter', logContext);
            return '';
        }
        // Validate row lengths
        const columnCount = headers.length;
        for (let i = 0; i < rows.length; i++) {
            if (rows[i]?.length !== columnCount) {
                throw validationError(`Row ${i} has ${rows[i]?.length} columns but expected ${columnCount}`, { ...logContext, rowIndex: i, expectedColumns: columnCount });
            }
        }
        // Merge options with defaults
        const opts = {
            ...this.defaultOptions,
            ...options,
            alignment: { ...this.defaultOptions.alignment, ...options?.alignment },
        };
        // Apply header styling
        const styledHeaders = this.applyHeaderStyle(headers, opts.headerStyle);
        // Calculate column widths and metadata
        const columns = this.calculateColumns(styledHeaders, rows, opts, logContext);
        // Render table based on style
        try {
            const result = this.renderTable(columns, styledHeaders, rows, opts);
            logger.debug('Table formatted successfully', {
                ...logContext,
                style: opts.style,
                rows: rows.length,
                columns: columns.length,
            });
            return result;
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            const stack = error instanceof Error ? error.stack : undefined;
            logger.error('Failed to render table', {
                ...logContext,
                error: message,
            });
            throw new McpError(JsonRpcErrorCode.InternalError, `Failed to render table: ${message}`, {
                ...logContext,
                originalError: stack,
            });
        }
    }
    /**
     * Apply header styling transformations.
     * @private
     */
    applyHeaderStyle(headers, style) {
        switch (style) {
            case 'bold':
                return headers.map((h) => `**${h}**`);
            case 'uppercase':
                return headers.map((h) => h.toUpperCase());
            default:
                return headers;
        }
    }
    /**
     * Calculate column widths and metadata.
     * @private
     */
    calculateColumns(headers, rows, options, context) {
        const columns = headers.map((header, index) => {
            // Determine alignment (use configured or default to left)
            const alignment = options.alignment[header] || options.alignment[index.toString()] || 'left';
            // Calculate max content width for this column
            const headerWidth = header.length;
            const maxContentWidth = Math.max(...rows.map((row) => (row[index] || '').length));
            // Determine final width (respecting min/max constraints)
            let width = Math.max(headerWidth, maxContentWidth);
            width = Math.max(width, options.minWidth);
            if (options.maxWidth && width > options.maxWidth) {
                width = options.maxWidth;
            }
            return { name: header, width, alignment };
        });
        logger.debug('Calculated column widths', {
            ...context,
            columns: columns.map((c) => ({ name: c.name, width: c.width })),
        });
        return columns;
    }
    /**
     * Render table using the specified style.
     * @private
     */
    renderTable(columns, headers, rows, options) {
        switch (options.style) {
            case 'markdown':
                return this.renderMarkdown(columns, headers, rows, options);
            case 'ascii':
                return this.renderAscii(columns, headers, rows, options);
            case 'grid':
                return this.renderGrid(columns, headers, rows, options);
            case 'compact':
                return this.renderCompact(columns, headers, rows, options);
            default:
                throw new Error(`Unknown table style: ${String(options.style)}`);
        }
    }
    /**
     * Render markdown-style table.
     * @private
     */
    renderMarkdown(columns, headers, rows, options) {
        const lines = [];
        const pad = ' '.repeat(options.padding);
        // Header row
        const headerCells = headers.map((header, i) => this.formatCell(header, columns[i], options));
        lines.push(`|${pad}${headerCells.join(`${pad}|${pad}`)}${pad}|`);
        // Separator row (with alignment indicators for markdown)
        const separators = columns.map((col) => {
            switch (col.alignment) {
                case 'right':
                    return `${'-'.repeat(Math.max(col.width - 1, 1))}:`;
                case 'center':
                    return `:${'-'.repeat(Math.max(col.width - 2, 1))}:`;
                default:
                    return '-'.repeat(col.width);
            }
        });
        lines.push(`|${pad}${separators.join(`${pad}|${pad}`)}${pad}|`);
        // Data rows
        for (const row of rows) {
            const cells = row.map((cell, i) => this.formatCell(cell, columns[i], options));
            lines.push(`|${pad}${cells.join(`${pad}|${pad}`)}${pad}|`);
        }
        return lines.join('\n');
    }
    /**
     * Render ASCII box-drawing table.
     * @private
     */
    renderAscii(columns, headers, rows, options) {
        const lines = [];
        const pad = ' '.repeat(options.padding);
        // Top border
        const topBorder = columns.map((col) => '-'.repeat(col.width + options.padding * 2)).join('+');
        lines.push(`+${topBorder}+`);
        // Header row
        const headerCells = headers.map((header, i) => this.formatCell(header, columns[i], options));
        lines.push(`|${pad}${headerCells.join(`${pad}|${pad}`)}${pad}|`);
        // Header separator
        const headerSep = columns.map((col) => '-'.repeat(col.width + options.padding * 2)).join('+');
        lines.push(`+${headerSep}+`);
        // Data rows
        for (const row of rows) {
            const cells = row.map((cell, i) => this.formatCell(cell, columns[i], options));
            lines.push(`|${pad}${cells.join(`${pad}|${pad}`)}${pad}|`);
        }
        // Bottom border
        const bottomBorder = columns
            .map((col) => '-'.repeat(col.width + options.padding * 2))
            .join('+');
        lines.push(`+${bottomBorder}+`);
        return lines.join('\n');
    }
    /**
     * Render Unicode grid table.
     * @private
     */
    renderGrid(columns, headers, rows, options) {
        const lines = [];
        const pad = ' '.repeat(options.padding);
        // Top border
        const topBorder = columns.map((col) => '─'.repeat(col.width + options.padding * 2)).join('┬');
        lines.push(`┌${topBorder}┐`);
        // Header row
        const headerCells = headers.map((header, i) => this.formatCell(header, columns[i], options));
        lines.push(`│${pad}${headerCells.join(`${pad}│${pad}`)}${pad}│`);
        // Header separator
        const headerSep = columns.map((col) => '─'.repeat(col.width + options.padding * 2)).join('┼');
        lines.push(`├${headerSep}┤`);
        // Data rows
        for (const row of rows) {
            const cells = row.map((cell, i) => this.formatCell(cell, columns[i], options));
            lines.push(`│${pad}${cells.join(`${pad}│${pad}`)}${pad}│`);
        }
        // Bottom border
        const bottomBorder = columns
            .map((col) => '─'.repeat(col.width + options.padding * 2))
            .join('┴');
        lines.push(`└${bottomBorder}┘`);
        return lines.join('\n');
    }
    /**
     * Render compact space-separated table.
     * @private
     */
    renderCompact(columns, headers, rows, options) {
        const lines = [];
        const pad = ' '.repeat(options.padding * 2);
        // Header row
        const headerCells = headers.map((header, i) => this.formatCell(header, columns[i], options));
        lines.push(headerCells.join(pad));
        // Data rows
        for (const row of rows) {
            const cells = row.map((cell, i) => this.formatCell(cell, columns[i], options));
            lines.push(cells.join(pad));
        }
        return lines.join('\n');
    }
    /**
     * Format a single cell with alignment and truncation.
     * @private
     */
    formatCell(content, column, options) {
        let text = content;
        // Truncate if needed
        if (options.truncate && text.length > column.width) {
            const visibleChars = Math.max(column.width - 3, 0);
            text = `${text.substring(0, visibleChars)}...`;
        }
        // Apply alignment padding
        const padding = column.width - text.length;
        if (padding <= 0) {
            return text;
        }
        switch (column.alignment) {
            case 'left':
                return text + ' '.repeat(padding);
            case 'right':
                return ' '.repeat(padding) + text;
            case 'center': {
                const leftPad = Math.floor(padding / 2);
                const rightPad = padding - leftPad;
                return ' '.repeat(leftPad) + text + ' '.repeat(rightPad);
            }
            default:
                return text;
        }
    }
    /**
     * Convert any value to a string for display in table.
     * @private
     */
    stringify(value) {
        if (value === null)
            return 'null';
        if (value === undefined)
            return 'undefined';
        if (typeof value === 'string')
            return value;
        if (typeof value === 'number')
            return value.toString();
        if (typeof value === 'boolean')
            return value.toString();
        if (typeof value === 'bigint')
            return value.toString();
        if (typeof value === 'symbol')
            return value.toString();
        if (typeof value === 'function')
            return '[Function]';
        if (Array.isArray(value))
            return JSON.stringify(value);
        if (typeof value === 'object') {
            try {
                return JSON.stringify(value);
            }
            catch {
                return '[Object]';
            }
        }
        return '[Unknown]';
    }
}
/**
 * Singleton instance of {@link TableFormatter}.
 *
 * Prefer this over constructing `new TableFormatter()` directly. Use
 * {@link TableFormatter.format} for object arrays and
 * {@link TableFormatter.formatRaw} for pre-serialized 2D string arrays.
 *
 * @example
 * ```typescript
 * import { tableFormatter } from '../../utils/formatting/tableFormatter.js';
 *
 * const data = [
 *   { name: 'Alice', age: 30, role: 'Engineer' },
 *   { name: 'Bob',   age: 25, role: 'Designer' },
 * ];
 *
 * // Markdown table (default style)
 * console.log(tableFormatter.format(data));
 *
 * // Unicode grid with right-aligned age column
 * console.log(tableFormatter.format(data, {
 *   style: 'grid',
 *   alignment: { age: 'right' },
 * }));
 *
 * // Compact borderless table with uppercase headers
 * console.log(tableFormatter.format(data, {
 *   style: 'compact',
 *   headerStyle: 'uppercase',
 * }));
 *
 * // Raw 2D array (no automatic key extraction)
 * console.log(tableFormatter.formatRaw(
 *   ['Name', 'Age'],
 *   [['Alice', '30'], ['Bob', '25']],
 *   { style: 'ascii' },
 * ));
 * ```
 */
export const tableFormatter = new TableFormatter();
//# sourceMappingURL=tableFormatter.js.map