

// financialExtractor.ts

/**
 * This module is responsible for extracting structured financial data from the raw text of a CIM.
 * It uses targeted regular expressions to find and parse financial tables, which is more reliable
 * for this kind of structured data than general LLM prompting.
 */

// A flexible interface to hold the extracted financial data.
// The keys are the financial metrics (e.g., "Revenue", "EBITDA").
// The values are objects where keys are the year/period (e.g., "FY21", "LTM") and values are the numbers.
export interface FinancialTable {
  [metric: string]: {
    [period: string]: number | string; // Using string for now to accommodate notes, will be number in final form
  };
}

// A simpler structure for the final, cleaned data.
export interface CleanedFinancials {
    periods: string[];
    metrics: {
        name: string;
        values: (number | null)[];
    }[];
}

const METRIC_KEYWORDS = ['Revenue', 'Sales', 'EBITDA', 'Gross Profit', 'Adj. EBITDA'];
const PERIOD_REGEX = /(FY|CY|LTM|YTD|\b(20\d{2})\b)/gi;

/**
 * Cleans a string value from a financial table, converting it to a number.
 * Handles currency symbols, parentheses for negatives, and abbreviations (M, K).
 * @param value The string value to clean.
 * @returns A number, or null if parsing fails.
 */
const cleanFinancialValue = (value: string): number | null => {
    if (!value) return null;
    let numStr = value.trim();

    // Handle parentheses for negative numbers
    const isNegative = numStr.startsWith('(') && numStr.endsWith(')');
    if (isNegative) {
        numStr = '-' + numStr.substring(1, numStr.length - 1);
    }

    // Remove currency symbols, commas, and whitespace
    numStr = numStr.replace(/[$,\s]/g, '');

    let multiplier = 1;
    if (numStr.toLowerCase().endsWith('m')) {
        multiplier = 1000000;
        numStr = numStr.slice(0, -1);
    } else if (numStr.toLowerCase().endsWith('k')) {
        multiplier = 1000;
        numStr = numStr.slice(0, -1);
    }

    const num = parseFloat(numStr);
    if (isNaN(num)) {
        return null;
    }

    return num * multiplier;
};

/**
 * Searches through the document text to find and extract the primary financial summary table.
 * @param cimText The entire text content of the CIM document.
 * @returns A structured object containing the cleaned financial data, or null if no table is found.
 */
export const extractFinancials = (cimText: string): CleanedFinancials | null => {
    const lines = cimText.split('\n');

    let headerLine = '';
    let tableLines: string[] = [];
    let tableStarted = false;

    // Find the table by looking for a header row with years and metric rows with keywords
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i] || '';
        const nextLine = lines[i+1] || '';

        const hasPeriod = PERIOD_REGEX.test(line);
        const hasKeyword = METRIC_KEYWORDS.some(kw => new RegExp(`\\b${kw}\\b`, 'i').test(nextLine));

        // A likely header is one that contains years/periods and is followed by a line with a metric keyword
        if (hasPeriod && hasKeyword && !tableStarted) {
            headerLine = line;
            tableStarted = true;
            // Assume the table continues for a reasonable number of lines
            for (let j = 1; j <= 10; j++) { // Look ahead 10 lines for metrics
                const tableLine = lines[i+j];
                if (!tableLine) break;
                if (METRIC_KEYWORDS.some(kw => new RegExp(`\\b${kw}\\b`, 'i').test(tableLine))) {
                    tableLines.push(tableLine);
                } else if (tableLine.trim() === '') {
                    // Stop at a blank line, which often signifies the end of a table
                    break;
                }
            }
            break; // Found the table, stop searching
        }
    }

    if (!headerLine || tableLines.length === 0) {
        return null; // No financial table found
    }

    // Extract periods from the header line
    // This regex is more robust for splitting columns
    const periods = headerLine.split(/\s{2,}|\t/).map(p => p.trim()).filter(p => p && PERIOD_REGEX.test(p));
    if (periods.length === 0) return null;

    const metrics: CleanedFinancials['metrics'] = [];

    for (const line of tableLines) {
        const parts = line.split(/\s{2,}|\t/).map(p => p.trim()).filter(p => p);
        if (parts.length < 2) continue;

        const metricName = parts[0];
        const potentialValues = parts.slice(1);

        // Basic alignment check
        if (potentialValues.length < periods.length) continue;

        const values = potentialValues.slice(0, periods.length).map(cleanFinancialValue);

        metrics.push({
            name: metricName || 'Unknown Metric',
            values: values,
        });
    }

    if (metrics.length === 0) return null;

    return {
        periods,
        metrics,
    };
};

// Example Usage:
/*
const sampleText = `
Financial Summary
(in thousands)

                        FY21A      FY22A      FY23E      LTM
Revenue                 $15,000    $17,500    $20,000    $21,000
Gross Profit            8,000      9,500      11,000     11,500
Adj. EBITDA             3,000      3,500      4,000      4,200
`;

const financials = extractFinancials(sampleText);
console.log(JSON.stringify(financials, null, 2));
*/
