"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateSeedExcelTemplate = generateSeedExcelTemplate;
const Excel = __importStar(require("exceljs"));
const utils_1 = require("@postxl/utils");
/**
 * Creates an Excel template file that can be used to manage Seed data.
 * The template file contains a sheet for each model, with the column for each field.
 */
async function generateSeedExcelTemplate({ context }) {
    const wb = initializeWorkbook();
    for (const model of [...context.models.values()].sort((a, b) => a.name.localeCompare(b.name))) {
        addModel({ model, wb, context });
    }
    const buffer = Buffer.from(await wb.xlsx.writeBuffer());
    return buffer;
}
function initializeWorkbook() {
    const wb = new Excel.Workbook();
    wb.creator = 'PostXL';
    wb.lastModifiedBy = 'PostXL';
    wb.created = new Date();
    wb.modified = new Date();
    return wb;
}
function addModel({ model, wb, context }) {
    // Sheets
    const ws = wb.addWorksheet(model.seedData.excel.sheetName);
    ws.views = [{ state: 'frozen', xSplit: 1, ySplit: 6, activeCell: 'B7' }];
    //Header row
    const headerCell = ws.getCell(1, 1);
    headerCell.value = model.seedData.excel.tableName;
    headerCell.font = { bold: true, size: 14 };
    // Description
    ws.getCell(3, 1).value = model.description;
    const { tableColumns, columnConfigs, blankRow, relationFields } = getModelColumns({ model });
    const rows = getModelRows({ model });
    adjustColumnWidths({ columnConfigs, rows });
    // Labels
    for (const [index, columnConfig] of columnConfigs.entries()) {
        const columnIndex = index + 1;
        ws.getCell(1, columnIndex).border = { bottom: { style: 'thick' } };
        const column = ws.getColumn(columnIndex);
        if (columnConfig.isHidden) {
            column.hidden = true;
        }
        column.width = columnConfig.width;
    }
    // Table
    const table = ws.addTable({
        name: model.seedData.excel.tableName,
        ref: 'A5',
        headerRow: true,
        totalsRow: false,
        style: {
            theme: 'TableStyleMedium2',
            showRowStripes: true,
        },
        columns: tableColumns,
        rows: rows.length > 0 ? rows : [blankRow],
    });
    table.commit();
    // Add data validation for relation fields
    addRelationFieldValidations({ ws, model, relationFields, context, rowCount: rows.length });
}
/**
 * Adds data validation to relation field columns so that they can only contain values
 * that exist in the referenced model's key column.
 */
function addRelationFieldValidations({ ws, relationFields, context, rowCount, }) {
    for (const relationField of relationFields) {
        const referencedModel = context.models.get(relationField.referencedModelName);
        if (!referencedModel) {
            continue;
        }
        // Find the column index of the key field in the referenced model
        const keyFieldColumnIndex = findKeyFieldColumnIndex(referencedModel);
        if (keyFieldColumnIndex === null) {
            continue;
        }
        const referencedSheetName = referencedModel.seedData.excel.sheetName;
        const referencedKeyColumnLetter = toExcelColumnLetter(keyFieldColumnIndex);
        // Calculate the data range in the referenced sheet
        // Tables start at row 5 (header), data starts at row 6
        const referencedDataStartRow = 6;
        const referencedDataEndRow = Math.max(referencedDataStartRow + referencedModel.seedData.data.length - 1, referencedDataStartRow + 100);
        // Create a formula using direct cell references
        // Format: ='SheetName'!A6:A100
        const formula = `='${referencedSheetName}'!${referencedKeyColumnLetter}${referencedDataStartRow}:${referencedKeyColumnLetter}${referencedDataEndRow}`;
        // Apply data validation to the relation field column (starting from row 6, which is the first data row after the table header)
        const columnLetter = toExcelColumnLetter(relationField.columnIndex);
        const startRow = 6; // First data row (row 5 is header, row 6 is first data row)
        const endRow = Math.max(startRow + rowCount, startRow + 100); // At least 100 rows for future entries
        // Create the data validation object
        ws.getCell(`${columnLetter}${startRow}`).dataValidation = {
            type: 'list',
            allowBlank: true,
            formulae: [formula],
            showErrorMessage: true,
            errorStyle: 'warning',
            errorTitle: 'Invalid Reference',
            error: `Please select a valid ${referencedModel.userFriendlyName} from the dropdown list.`,
        };
        // Copy data validation to all cells in the range
        for (let row = startRow + 1; row <= endRow; row++) {
            ws.getCell(`${columnLetter}${row}`).dataValidation = ws.getCell(`${columnLetter}${startRow}`).dataValidation;
        }
    }
}
/**
 * Finds the column index (1-based) of the key field in a model.
 * Returns null if the key field cannot be found.
 */
function findKeyFieldColumnIndex(model) {
    const keyFieldIsIdField = model.keyField.name === model.idField.name;
    let columnIndex = 0;
    for (const field of model.fields.values()) {
        // Skip the id field when keyField is a different field
        if (field.kind === 'id' && !keyFieldIsIdField) {
            continue;
        }
        columnIndex++;
        if (field.kind === 'discriminatedUnion') {
            // For discriminated unions, count all columns
            columnIndex += field.commonFields.size;
            for (const member of field.members.values()) {
                columnIndex += member.fields.size;
            }
        }
        if (field.name === model.keyField.name) {
            return columnIndex;
        }
    }
    return null;
}
/**
 * Converts a column index (1-based) to an Excel column letter (A, B, C, ..., Z, AA, AB, ...).
 */
function toExcelColumnLetter(columnIndex) {
    let letter = '';
    let num = columnIndex;
    while (num > 0) {
        const remainder = (num - 1) % 26;
        letter = String.fromCharCode(65 + remainder) + letter;
        num = Math.floor((num - 1) / 26);
    }
    return letter;
}
function getModelColumns({ model }) {
    const tableColumns = [];
    const columnConfigs = [];
    const relationFields = [];
    // Determine if we should skip the id column.
    // If keyField is different from id field, the id will be auto-generated during import.
    const keyFieldIsIdField = model.keyField.name === model.idField.name;
    for (const field of model.fields.values()) {
        // Skip the id field when keyField is a different field
        if (field.kind === 'id' && !keyFieldIsIdField) {
            continue;
        }
        if (field.kind === 'discriminatedUnion') {
            addDiscriminatedUnionColumns({ field, tableColumns, columnConfigs, relationFields });
        }
        else {
            tableColumns.push({ name: (0, utils_1.toExcelColumnName)(field.excelName, field.name) });
            columnConfigs.push(getColumnConfig(field));
            // Track relation fields for data validation
            if (field.kind === 'relation') {
                relationFields.push({
                    columnIndex: columnConfigs.length, // 1-indexed column
                    referencedModelName: field.referencedModelName,
                });
            }
        }
    }
    const blankRow = columnConfigs.map(() => null);
    return { tableColumns, columnConfigs, blankRow, relationFields };
}
function getColumnConfig(field) {
    const isHidden = field.kind === 'scalar' && (field.isCreatedAt || field.isUpdatedAt);
    const label = field.label;
    switch (field.kind) {
        case 'id':
            return { label, isHidden, width: 20, type: 'id' };
        case 'scalar':
            switch (field.type) {
                case 'string':
                    return { label, isHidden, width: 80, type: field.type };
                case 'number':
                    return { label, isHidden, width: 20, type: field.type };
                case 'boolean':
                    return { label, isHidden, width: 10, type: field.type };
                case 'Date':
                    return { label, isHidden, width: 20, type: field.type };
                default:
                    throw new utils_1.ExhaustiveSwitchCheck(field);
            }
        case 'json':
            return { label, isHidden, width: 100, type: 'string' };
        case 'relation':
            return { label, isHidden, width: 20, type: 'id' };
        case 'enum':
            return { label, isHidden, width: 40, type: 'string' };
        case 'discriminatedUnion':
            return { label, isHidden, width: 100, type: 'string' };
        default:
            throw new utils_1.ExhaustiveSwitchCheck(field);
    }
}
function addDiscriminatedUnionColumns({ field, tableColumns, columnConfigs, relationFields, }) {
    tableColumns.push({ name: (0, utils_1.toExcelColumnName)(field.excelName, field.name) });
    columnConfigs.push(getColumnConfig(field));
    for (const f of field.commonFields.values()) {
        tableColumns.push({ name: (0, utils_1.toExcelColumnName)(f.excelName, f.name) });
        columnConfigs.push(getColumnConfig(f));
        if (f.kind === 'relation') {
            relationFields.push({
                columnIndex: columnConfigs.length,
                referencedModelName: f.referencedModelName,
            });
        }
    }
    for (const member of field.members.values()) {
        for (const f of member.fields.values()) {
            tableColumns.push({ name: (0, utils_1.toExcelColumnName)(f.excelName, f.name) });
            columnConfigs.push(getColumnConfig(f));
            if (f.kind === 'relation') {
                relationFields.push({
                    columnIndex: columnConfigs.length,
                    referencedModelName: f.referencedModelName,
                });
            }
        }
    }
}
function getModelRows({ model }) {
    if (model.seedData.data.length === 0) {
        return [];
    }
    return model.seedData.data.map((row) => getModelRow({ model, row }));
}
function getModelRow({ model, row }) {
    const result = [];
    // Determine if we should skip the id column.
    // If keyField is different from id field, the id will be auto-generated during import.
    const keyFieldIsIdField = model.keyField.name === model.idField.name;
    for (const field of model.fields.values()) {
        // Skip the id field when keyField is a different field
        if (field.kind === 'id' && !keyFieldIsIdField) {
            continue;
        }
        if (field.kind === 'discriminatedUnion') {
            addDiscriminatedUnionRowValue({ field, value: row[field.name], result });
        }
        else {
            result.push(toExcelCellValue({ field, value: row[field.name] }));
        }
    }
    return result;
}
function toExcelCellValue({ field, value }) {
    if (value === undefined || value === null) {
        return null;
    }
    if (field.kind === 'json') {
        try {
            return JSON.stringify(value, null, 2);
        }
        catch {
            return null;
        }
    }
    return value;
}
function addDiscriminatedUnionRowValue({ field, value, result, }) {
    result.push(value[field.discriminatorFieldName]);
    for (const f of field.commonFields.values()) {
        result.push(value[f.name] ?? null);
    }
    for (const member of field.members.values()) {
        for (const f of member.fields.values()) {
            result.push(value[f.name] ?? null);
        }
    }
}
const maxWidth = 100;
function adjustColumnWidths({ columnConfigs, rows }) {
    for (let index = 0; index < columnConfigs.length; index++) {
        adjustColumnWidth({ columnConfig: columnConfigs[index], rows, index });
    }
}
function adjustColumnWidth({ columnConfig, rows, index, }) {
    switch (columnConfig.type) {
        case 'string':
        case 'number':
            adjustNumberOrStringColumnWidth({ columnConfig, rows, index });
            break;
        case 'boolean':
        case 'Date':
        case 'id':
            // No need to do anything
            break;
        default:
            throw new utils_1.ExhaustiveSwitchCheck(columnConfig.type);
    }
}
function adjustNumberOrStringColumnWidth({ columnConfig, rows, index, }) {
    const width = Math.max(length(columnConfig.label), ...rows.map((row) => length(row[index]))) * 1.5;
    columnConfig.width = Math.min(width, maxWidth);
}
function length(value) {
    if (typeof value === 'number') {
        return (Math.round(value * 100) / 100).toString().length;
    }
    if (typeof value === 'string') {
        return value.length;
    }
    return 0;
}
//# sourceMappingURL=excel-template.generator.js.map