"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateSeedDataForModels = generateSeedDataForModels;
exports.generateModelSeedDataJson = generateModelSeedDataJson;
exports.generateModelSeedDataTypescript = generateModelSeedDataTypescript;
const faker_1 = require("@faker-js/faker");
const utils_1 = require("@postxl/utils");
function generateSeedDataForModels(context) {
    const fakers = new Map();
    // First pass: generate ids and keyField values for all models
    const modelSeedRefs = new Map();
    for (const [modelName, model] of context.models) {
        const faker = new faker_1.Faker({ seed: model.faker?.seed ?? 0, locale: [faker_1.base, faker_1.en] });
        fakers.set(modelName, faker);
        modelSeedRefs.set(modelName, generateModelSeedRefs({ faker, model }));
    }
    // Second pass: generate full seed data using keyField values for relations
    const models = new Map();
    for (const [modelName, model] of context.models) {
        const faker = fakers.get(modelName);
        const data = generateModelSeedData({ context, faker, modelSeedRefs, model });
        models.set(modelName, data);
    }
    return models;
}
/**
 * Generates both id and keyField value references for each seed record.
 * These are used to generate relation field values using the keyField value.
 */
function generateModelSeedRefs({ faker, model }) {
    const refs = [];
    const seenIds = new Set();
    const seenKeys = new Set();
    processExplicitSeedEntries({ faker, model, refs, seenIds, seenKeys });
    processFakerGeneratedEntries({ faker, model, refs, seenIds, seenKeys });
    return refs;
}
/**
 * Processes explicit seed entries defined in the schema.
 */
function processExplicitSeedEntries({ faker, model, refs, seenIds, seenKeys, }) {
    if (!model.seed || model.seed.length === 0) {
        return;
    }
    for (const [index, seedEntry] of model.seed.entries()) {
        const id = seedEntry.id ?? generateId({ faker, model, index });
        const key = getKeyFieldValue({ faker, model, seedEntry, index, id });
        validateUniqueId(id, seenIds, model.name);
        validateUniqueKey(key, seenKeys, model.name);
        seenIds.add(id);
        seenKeys.add(key);
        refs.push({ id, key });
    }
}
/**
 * Processes faker-generated seed entries.
 */
function processFakerGeneratedEntries({ faker, model, refs, seenIds, seenKeys, }) {
    if (!model.faker?.items) {
        return;
    }
    for (let i = 0; i < model.faker.items; i++) {
        const index = refs.length;
        const id = generateUniqueId({ faker, model, index, seenIds });
        const key = generateKeyFieldValue({ faker, model, index, id });
        seenIds.add(id);
        seenKeys.add(key);
        refs.push({ id, key });
    }
}
/**
 * Validates that an ID is unique within the model's seed data.
 */
function validateUniqueId(id, seenIds, modelName) {
    if (seenIds.has(id)) {
        throw new Error(`Duplicate seed entry id: ${(0, utils_1.yellow)(id.toString())} for model ${(0, utils_1.yellow)(modelName)}`);
    }
}
/**
 * Validates that a keyField value is unique within the model's seed data.
 */
function validateUniqueKey(key, seenKeys, modelName) {
    if (seenKeys.has(key)) {
        throw new Error(`Duplicate seed entry keyField value: ${(0, utils_1.yellow)(key.toString())} for model ${(0, utils_1.yellow)(modelName)}`);
    }
}
/**
 * Generates a unique ID with retry logic to avoid collisions.
 */
function generateUniqueId({ faker, model, index, seenIds, }) {
    const maxRetries = 10;
    let id = generateId({ faker, model, index });
    let retries = 0;
    while (seenIds.has(id)) {
        id = generateId({ faker, model, index });
        retries++;
        if (retries > maxRetries) {
            throw new Error(`Failed to generate unique ID after ${maxRetries} attempts for model ${model.name}`);
        }
    }
    return id;
}
/**
 * Gets the keyField value from an explicit seed entry, or generates one if not provided.
 */
function getKeyFieldValue({ faker, model, seedEntry, index, id, }) {
    const keyFieldName = model.keyField.name;
    // If keyField is the id field, use the id
    if (keyFieldName === model.idField.name) {
        return id;
    }
    // Check if keyField value is explicitly provided in seed entry
    if (seedEntry[keyFieldName] !== undefined && seedEntry[keyFieldName] !== null) {
        return seedEntry[keyFieldName];
    }
    // Generate a keyField value
    return generateKeyFieldValue({ faker, model, index, id });
}
/**
 * Generates a keyField value for a seed record.
 */
function generateKeyFieldValue({ faker, model, index, id, }) {
    const keyField = model.keyField;
    // If keyField is the id field, use the id
    if (keyField.name === model.idField.name) {
        return id;
    }
    // Generate based on field type and seed config
    if (keyField.kind === 'scalar') {
        if (keyField.seed.type !== 'default') {
            return generateFromSeedConfig(faker, keyField.seed);
        }
        // Generate a unique key based on model name and index
        return `fake${model._conjugated.PascalCase}Key-${index}`;
    }
    // Fallback
    return `fake${model._conjugated.PascalCase}Key-${index}`;
}
function generateId({ faker, model, index }) {
    const field = model.idField;
    if (field.seed.type !== 'default') {
        return generateFromSeedConfig(faker, field.seed);
    }
    switch (field.idType) {
        case 'Increment':
            return index;
        case 'UUID':
            return `fake${model._conjugated.PascalCase}Id-${index}`;
        // return faker.string.uuid()
        default:
            throw new utils_1.ExhaustiveSwitchCheck(field.idType);
    }
}
function generateFromSeedConfig(faker, seed) {
    switch (seed.type) {
        case 'default':
            throw new Error('Please handle default case outside!');
        case 'const':
            return seed.seed;
        case 'array':
            return faker.helpers.arrayElement(seed.seed);
        case 'faker':
            return fakeEval(faker, seed.faker);
        default:
            throw new utils_1.ExhaustiveSwitchCheck(seed);
    }
}
function fakeEval(faker, expression) {
    const exp = expression.replace('faker.', '').trim();
    return faker.helpers.fake(`{{${exp}}}`);
}
function generateModelSeedData({ context, faker, modelSeedRefs, model, }) {
    const data = [];
    const ownRefs = modelSeedRefs.get(model.name);
    // Process explicit seed entries
    if (model.seed && model.seed.length > 0) {
        data.push(...model.seed);
    }
    // Generate faker entries
    let index = data.length;
    if (model.faker?.items) {
        for (let i = 0; i < model.faker.items; i++) {
            const ref = ownRefs[index];
            index++;
            data.push(generateSeedDataRecord({ context, faker, modelSeedRefs, model, ref }));
        }
    }
    return data;
}
function generateSeedDataRecord({ context, faker, modelSeedRefs, model, ref, }) {
    const data = {};
    // Determine if we should skip the id field in seed data.
    // If keyField is different from id field, the id will be auto-generated during import,
    // so we only need to include the keyField for identifying records.
    const keyFieldIsIdField = model.keyField.name === model.idField.name;
    for (const field of model.fields.values()) {
        if (field.kind === 'scalar' && (field.isCreatedAt || field.isUpdatedAt)) {
            continue;
        }
        // Skip the id field when keyField is a different field
        // The id will be auto-generated during import
        if (field.kind === 'id' && !keyFieldIsIdField) {
            continue;
        }
        data[field.name] = generateSeedDataField({ context, faker, modelSeedRefs, model, ref, field });
    }
    return data;
}
function generateSeedDataField({ context, faker, modelSeedRefs, model, ref, field, }) {
    try {
        if (field.kind === 'discriminatedUnion') {
            return generateDiscriminatedUnionField({ context, faker, modelSeedRefs, model, ref, field });
        }
        else {
            return generateSeedDataFieldNative({ context, faker, modelSeedRefs, model, ref, field });
        }
    }
    catch (error) {
        console.error(`Error generating seed data for field ${field.name} in model ${model.name}:`, error);
        return undefined;
    }
}
function generateSeedDataFieldNative({ context, faker, modelSeedRefs, model, ref, field, }) {
    switch (field.kind) {
        case 'id':
            return ref.id;
        case 'scalar':
            // If this is the keyField, return the key value from ref
            if (field.name === model.keyField.name) {
                return ref.key;
            }
            return generateScalarField({ faker, field });
        case 'relation':
            return generateRelationField({ context, faker, modelSeedRefs, model, ref, field });
        case 'enum':
            return generateEnumField({ context, faker, field });
        case 'json':
            return generateJsonField({ field });
        default:
            throw new utils_1.ExhaustiveSwitchCheck(field);
    }
}
function generateJsonField({ field }) {
    if (!field.isRequired) {
        return undefined;
    }
    return {};
}
function generateScalarField({ faker, field }) {
    if (field.seed.type !== 'default') {
        if (field.type === 'number') {
            return Number(generateFromSeedConfig(faker, field.seed));
        }
        else {
            return generateFromSeedConfig(faker, field.seed);
        }
    }
    switch (field.databaseType) {
        case 'String':
            return faker.lorem.words(3);
        case 'Int':
            return faker.number.int({ min: 1, max: 1000 });
        case 'BigInt':
            return faker.number.int({ min: 1, max: 100000 });
        case 'Boolean':
            return faker.datatype.boolean();
        case 'DateTime':
            return faker.date.between({ from: '2024-01-01T00:00:00.000Z', to: '2025-02-01T00:00:00.000Z' });
        case 'Decimal':
            return faker.number.float({ min: 1, max: 100000, multipleOf: 0.01 });
        case 'Float':
            return faker.number.float({ min: 0, max: 1 });
        case 'Json':
            throw new Error('Json type is not supported for seed data');
        case 'Bytes':
            throw new Error('Bytes type is not supported for seed data');
        default:
            throw new utils_1.ExhaustiveSwitchCheck(field.databaseType);
    }
}
/**
 * Generates a relation field value using the keyField value of the related model.
 * This allows foreign key resolution during import using the keyMap.
 */
function generateRelationField({ faker, modelSeedRefs, model, ref, field, }) {
    const relatedModelRefs = modelSeedRefs.get(field.referencedModelName);
    if (model.name === field.referencedModelName && relatedModelRefs.length === 1) {
        if (!field.isRequired) {
            return undefined;
        }
        throw new Error(`Self-referencing model ${model.name} must have at least 2 seed data entries!`);
    }
    if (relatedModelRefs.length === 0) {
        if (!field.isRequired) {
            return undefined;
        }
        throw new Error(`Model ${field.referencedModelName} is not seeded but required for field ${field.modelName}.${field.name}!`);
    }
    // Pick a random related record, avoiding self-reference
    while (true) {
        const relatedRef = faker.helpers.arrayElement(relatedModelRefs);
        // Avoid self-reference by comparing ids
        if (relatedRef.id !== ref.id) {
            // Return the keyField value of the related model for use in import
            return relatedRef.key;
        }
    }
}
function generateEnumField({ context, faker, field, }) {
    const enum_ = context.enums.get(field.enumName);
    const values = Array.from(enum_.members.values()).map((m) => m.value);
    return faker.helpers.arrayElement(values);
}
function generateDiscriminatedUnionField({ context, faker, modelSeedRefs, model, ref, field, }) {
    const discriminantValues = Array.from(field.members.keys());
    const member = faker.helpers.arrayElement(discriminantValues);
    const result = {
        [field.discriminatorFieldName]: member,
    };
    for (const f of field.commonFields.values()) {
        result[f.name] = generateSeedDataFieldNative({ context, faker, modelSeedRefs, model, ref, field: f });
    }
    for (const f of field.members.get(member).fields.values()) {
        result[f.name] = generateSeedDataFieldNative({ context, faker, modelSeedRefs, model, ref, field: f });
    }
    return result;
}
function generateModelSeedDataJson({ model }) {
    return JSON.stringify(model.seedData.data, null, 2);
}
function generateModelSeedDataTypescript({ model }) {
    const splitNames = model.seedData.dataVariable.location.split('/');
    const jsonLocation = splitNames[splitNames.length - 1];
    return /* ts */ `
  import * as data from './${jsonLocation}.json'

  export const ${model.seedData.dataVariable.name} = (data as any).default`;
}
//# sourceMappingURL=model-seed-data.generator.js.map