/**
 * CSV class - Cross-Platform Base Implementation
 *
 * Provides CSV read/write functionality for both Node.js and Browser.
 * Node.js version (csv.ts) extends this with file system support.
 */
import { DateParser, DateFormatter } from "../../utils/datetime.js";
import { parseCsv, formatCsv } from "./csv-core.js";
import { CsvParserStream, CsvFormatterStream } from "./csv-stream.js";
import { parseNumberFromCsv } from "./csv-number.js";
import { pipeline } from "../stream/index.js";
// =============================================================================
// Constants
// =============================================================================
const DEFAULT_DATE_FORMATS = [
    "YYYY-MM-DD[T]HH:mm:ssZ",
    "YYYY-MM-DD[T]HH:mm:ss",
    "MM-DD-YYYY",
    "YYYY-MM-DD"
];
// =============================================================================
// Value Mappers
// =============================================================================
const SpecialValues = {
    true: true,
    false: false,
    "#N/A": { error: "#N/A" },
    "#REF!": { error: "#REF!" },
    "#NAME?": { error: "#NAME?" },
    "#DIV/0!": { error: "#DIV/0!" },
    "#NULL!": { error: "#NULL!" },
    "#VALUE!": { error: "#VALUE!" },
    "#NUM!": { error: "#NUM!" }
};
export function createDefaultValueMapper(dateFormats, options) {
    const dateParser = DateParser.create(dateFormats);
    const decimalSeparator = options?.decimalSeparator ?? ".";
    return function mapValue(datum) {
        if (datum === "") {
            return null;
        }
        if (typeof datum === "string") {
            const datumNumber = parseNumberFromCsv(datum, decimalSeparator);
            if (!Number.isNaN(datumNumber) && datumNumber !== Infinity) {
                return datumNumber;
            }
        }
        else {
            const datumNumber = Number(datum);
            if (!Number.isNaN(datumNumber) && datumNumber !== Infinity) {
                return datumNumber;
            }
        }
        const date = dateParser.parse(datum);
        if (date) {
            return date;
        }
        const special = SpecialValues[datum];
        if (special !== undefined) {
            return special;
        }
        return datum;
    };
}
export function createDefaultWriteMapper(dateFormat, dateUTC) {
    const formatter = dateFormat
        ? DateFormatter.create(dateFormat, { utc: dateUTC })
        : DateFormatter.iso(dateUTC);
    return function mapValue(value) {
        if (value) {
            if (value.text || value.hyperlink) {
                return value.hyperlink || value.text || "";
            }
            if (value.formula || value.result) {
                return value.result || "";
            }
            if (value instanceof Date) {
                return formatter.format(value);
            }
            if (value.error) {
                return value.error;
            }
            if (typeof value === "object") {
                return JSON.stringify(value);
            }
        }
        return value;
    };
}
// =============================================================================
// CSV Class
// =============================================================================
class CSV {
    constructor(workbook) {
        this.workbook = workbook;
    }
    // ---------------------------------------------------------------------------
    // In-Memory Operations
    // ---------------------------------------------------------------------------
    load(content, options) {
        let str;
        if (typeof content === "string") {
            str = content;
        }
        else if (content instanceof ArrayBuffer || content instanceof Uint8Array) {
            str = new TextDecoder().decode(content);
        }
        else {
            str = String(content);
        }
        const worksheet = this.workbook.addWorksheet(options?.sheetName);
        const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
        const map = options?.map ||
            createDefaultValueMapper(dateFormats, {
                decimalSeparator: options?.valueMapperOptions?.decimalSeparator
            });
        const rows = parseCsv(str, options?.parserOptions);
        for (const row of rows) {
            worksheet.addRow(row.map(map));
        }
        return worksheet;
    }
    writeString(options) {
        const worksheet = this.workbook.getWorksheet(options?.sheetName || options?.sheetId);
        if (!worksheet) {
            return "";
        }
        const { dateFormat, dateUTC } = options || {};
        const map = options?.map || createDefaultWriteMapper(dateFormat, dateUTC);
        const includeEmptyRows = options?.includeEmptyRows !== false;
        const rows = [];
        let lastRow = 1;
        worksheet.eachRow((row, rowNumber) => {
            if (includeEmptyRows) {
                while (lastRow++ < rowNumber - 1) {
                    rows.push([]);
                }
            }
            const { values } = row;
            values.shift();
            rows.push(values.map(map));
            lastRow = rowNumber;
        });
        return formatCsv(rows, options?.formatterOptions);
    }
    async writeBuffer(options) {
        return new TextEncoder().encode(this.writeString(options));
    }
    // ---------------------------------------------------------------------------
    // Stream Operations
    // ---------------------------------------------------------------------------
    async read(stream, options) {
        const worksheet = this.workbook.addWorksheet(options?.sheetName);
        const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
        const map = options?.map ||
            createDefaultValueMapper(dateFormats, {
                decimalSeparator: options?.valueMapperOptions?.decimalSeparator
            });
        const parser = new CsvParserStream(options?.parserOptions);
        return new Promise((resolve, reject) => {
            parser.on("data", (row) => worksheet.addRow(row.map(map)));
            pipeline(stream, parser)
                .then(() => resolve(worksheet))
                .catch(reject);
        });
    }
    async write(stream, options) {
        const worksheet = this.workbook.getWorksheet(options?.sheetName || options?.sheetId);
        if (!worksheet) {
            stream.end();
            return;
        }
        const { dateFormat, dateUTC } = options || {};
        const map = options?.map || createDefaultWriteMapper(dateFormat, dateUTC);
        const includeEmptyRows = options?.includeEmptyRows !== false;
        const formatterOptions = { ...options?.formatterOptions };
        const formatter = new CsvFormatterStream(formatterOptions);
        const pipelinePromise = pipeline(formatter, stream);
        let lastRow = 1;
        worksheet.eachRow((row, rowNumber) => {
            if (includeEmptyRows) {
                while (lastRow++ < rowNumber - 1) {
                    formatter.write([]);
                }
            }
            const { values } = row;
            values.shift();
            formatter.write(values.map(map));
            lastRow = rowNumber;
        });
        formatter.end();
        await pipelinePromise;
    }
    createReadStream(options) {
        const worksheet = this.workbook.getWorksheet(options?.sheetName || options?.sheetId);
        const { dateFormat, dateUTC } = options || {};
        const map = options?.map || createDefaultWriteMapper(dateFormat, dateUTC);
        const includeEmptyRows = options?.includeEmptyRows !== false;
        const formatter = new CsvFormatterStream({ ...options?.formatterOptions });
        if (worksheet) {
            setTimeout(() => {
                let lastRow = 1;
                worksheet.eachRow((row, rowNumber) => {
                    if (includeEmptyRows) {
                        while (lastRow++ < rowNumber - 1) {
                            formatter.write([]);
                        }
                    }
                    const { values } = row;
                    values.shift();
                    formatter.write(values.map(map));
                    lastRow = rowNumber;
                });
                formatter.end();
            }, 0);
        }
        else {
            setTimeout(() => formatter.end(), 0);
        }
        return formatter;
    }
    createWriteStream(options) {
        const worksheet = this.workbook.addWorksheet(options?.sheetName);
        const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
        const map = options?.map ||
            createDefaultValueMapper(dateFormats, {
                decimalSeparator: options?.valueMapperOptions?.decimalSeparator
            });
        const parser = new CsvParserStream(options?.parserOptions);
        parser.on("data", (row) => worksheet.addRow(row.map(map)));
        return parser;
    }
    // ---------------------------------------------------------------------------
    // File Operations (Browser stubs - overridden in Node.js)
    // ---------------------------------------------------------------------------
    async readFile(_filename, _options) {
        throw new Error("csv.readFile() is not available in browser.\n" +
            "Use csv.load(csvString) or csv.read(stream) instead.\n" +
            "Example: const response = await fetch('/data.csv');\n" +
            "         workbook.csv.load(await response.text());");
    }
    async writeFile(_filename, _options) {
        throw new Error("csv.writeFile() is not available in browser.\n" +
            "Use csv.writeBuffer() and trigger a download instead.\n" +
            "Example: const buffer = await workbook.csv.writeBuffer();\n" +
            "         download(new Blob([buffer]), 'output.csv');");
    }
}
// =============================================================================
// Standalone Functions (for backward compatibility)
// =============================================================================
export function parseCsvToWorksheet(content, workbook, options) {
    const csv = new CSV(workbook);
    return csv.load(content, options);
}
export function formatWorksheetToCsv(worksheet, options) {
    if (!worksheet) {
        return "";
    }
    const csv = new CSV(worksheet.workbook);
    return csv.writeString({ ...options, sheetId: worksheet.id });
}
export { CSV };
export { CsvParserStream, CsvFormatterStream } from "./csv-stream.js";
export { parseCsv, formatCsv } from "./csv-core.js";
