"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.writePrismaApplication = writePrismaApplication;
const utils_1 = require("@autobe/utils");
const crypto_1 = __importDefault(require("crypto"));
const ArrayUtil_1 = require("../utils/ArrayUtil");
const MapUtil_1 = require("../utils/MapUtil");
function writePrismaApplication(props) {
    for (const file of props.application.files)
        for (const model of file.models)
            fillMappingName(model);
    return Object.assign(Object.assign({}, Object.fromEntries(props.application.files
        .filter((file) => file.filename !== "main.prisma")
        .map((file) => [
        file.filename,
        writeFile(Object.assign(Object.assign({}, props), { file })),
    ]))), { "main.prisma": props.dbms === "postgres" ? POSTGRES_MAIN_FILE : SQLITE_MAIN_FILE });
}
function writeFile(props) {
    return props.file.models
        .map((model) => writeModel(Object.assign(Object.assign({}, props), { model })))
        .join("\n\n");
}
function writeModel(props) {
    return [
        writeComment([
            props.model.description,
            "",
            ...(props.model.material ? [] : [`@namespace ${props.file.namespace}`]),
            "@author AutoBE - https://github.com/wrtnlabs/autobe",
        ].join("\n")),
        `model ${props.model.name} {`,
        indent(ArrayUtil_1.ArrayUtil.paddle([writeColumns(props), writeRelations(props)]).join("\n")),
        "}",
    ].join("\n");
}
function fillMappingName(model) {
    const group = new Map();
    for (const ff of model.foreignFields) {
        MapUtil_1.MapUtil.take(group, ff.relation.targetModel, () => []).push(ff);
        if (ff.relation.targetModel == model.name)
            ff.relation.mappingName = "recursive";
    }
    for (const array of group.values())
        if (array.length !== 1)
            for (const ff of array)
                ff.relation.mappingName = shortName(`${model.name}_of_${ff.name}`);
}
/* -----------------------------------------------------------
  COLUMNS
----------------------------------------------------------- */
function writeColumns(props) {
    return [
        "//----",
        "// COLUMNS",
        "//----",
        writePrimary({
            dbms: props.dbms,
            field: props.model.primaryField,
        }),
        ...props.model.foreignFields
            .map((x) => [
            "",
            writeField({
                dbms: props.dbms,
                field: x,
            }),
        ])
            .flat(),
        ...props.model.plainFields
            .map((x) => [
            "",
            writeField({
                dbms: props.dbms,
                field: x,
            }),
        ])
            .flat(),
    ];
}
function writePrimary(props) {
    const type = props.dbms === "postgres" ? POSTGRES_PHYSICAL_TYPES.uuid : undefined;
    return [
        writeComment(props.field.description),
        `${props.field.name} String @id${type ? ` ${type}` : ""}`,
    ].join("\n");
}
function writeField(props) {
    const logical = LOGICAL_TYPES[props.field.type];
    const physical = props.dbms === "postgres"
        ? POSTGRES_PHYSICAL_TYPES[props.field.type]
        : undefined;
    return [
        writeComment(props.field.description),
        [
            props.field.name,
            `${logical}${props.field.nullable ? "?" : ""}`,
            ...(physical ? [physical] : []),
        ].join(" "),
    ].join("\n");
}
/* -----------------------------------------------------------
  RELATIONS
----------------------------------------------------------- */
function writeRelations(props) {
    const hasRelationships = props.application.files
        .map((otherFile) => otherFile.models.map((otherModel) => otherModel.foreignFields
        .filter((otherForeign) => otherForeign.relation.targetModel === props.model.name)
        .map((otherForeign) => ({
        modelName: otherModel.name,
        unique: otherForeign.unique,
        mappingName: otherForeign.relation.mappingName,
    }))))
        .flat(2);
    const foreignIndexes = props.model.foreignFields.filter((f) => {
        if (f.unique === true)
            return props.model.uniqueIndexes.every((u) => u.fieldNames.length !== 1 || u.fieldNames[0] !== f.name);
        return (props.model.uniqueIndexes.every((u) => u.fieldNames[0] !== f.name) &&
            props.model.plainIndexes.every((p) => p.fieldNames[0] !== f.name));
    });
    const contents = [
        props.model.foreignFields.map((foreign) => writeConstraint({
            dbms: props.dbms,
            model: props.model,
            foreign,
        })),
        hasRelationships.map((r) => {
            var _a;
            return [
                (_a = r.mappingName) !== null && _a !== void 0 ? _a : r.modelName,
                `${r.modelName}${r.unique ? "?" : "[]"}`,
                ...(r.mappingName ? [`@relation("${r.mappingName}")`] : []),
            ].join(" ");
        }),
        foreignIndexes.map((field) => writeForeignIndex({
            model: props.model,
            field,
        })),
        [
            ...props.model.uniqueIndexes.map((unique) => writeUniqueIndex({
                model: props.model,
                unique,
            })),
            ...props.model.plainIndexes.map((plain) => writePlainIndex({
                model: props.model,
                plain,
            })),
            ...(props.dbms === "postgres"
                ? props.model.ginIndexes.map((gin) => writeGinIndex({
                    model: props.model,
                    gin,
                }))
                : []),
        ],
    ];
    if (contents.every((c) => c.length === 0))
        return [];
    return [
        "//----",
        "// RELATIONS",
        "//----",
        // paddled content
        ...ArrayUtil_1.ArrayUtil.paddle(contents),
    ];
}
function writeConstraint(props) {
    // spellchecker:ignore-next-line
    const name = `${props.model.name}_${props.foreign.name}_rela`;
    return [
        props.foreign.relation.name,
        `${props.foreign.relation.targetModel}${props.foreign.nullable ? "?" : ""}`,
        `@relation(${[
            ...(props.foreign.relation.mappingName
                ? [`"${props.foreign.relation.mappingName}"`]
                : []),
            `fields: [${props.foreign.name}]`,
            `references: [id]`,
            `onDelete: Cascade`,
            ...(props.dbms === "sqlite" || name.length <= MAX_IDENTIFIER_LENGTH
                ? []
                : [`map: "${shortName(name)}"`]),
        ].join(", ")})`,
    ].join(" ");
}
function writeForeignIndex(props) {
    const name = `${props.model.name}_${props.field.name}_fkey`;
    const prefix = `@@${props.field.unique === true ? "unique" : "index"}([${props.field.name}]`;
    if (name.length <= MAX_IDENTIFIER_LENGTH)
        return `${prefix})`;
    return `${prefix}, map: "${shortName(name)}")`;
}
function writeUniqueIndex(props) {
    const name = `${props.model.name}_${props.unique.fieldNames.join("_")}_key`;
    const prefix = `@@unique([${props.unique.fieldNames.join(", ")}]`;
    if (name.length <= MAX_IDENTIFIER_LENGTH)
        return `${prefix})`;
    return `${prefix}, map: "${shortName(name)}")`;
}
function writePlainIndex(props) {
    const name = `${props.model.name}_${props.plain.fieldNames.join("_")}_idx`;
    const prefix = `@@index([${props.plain.fieldNames.join(", ")}]`;
    if (name.length <= MAX_IDENTIFIER_LENGTH)
        return `${prefix})`;
    return `${prefix}, map: "${shortName(name)}")`;
}
function writeGinIndex(props) {
    const name = `${props.model.name}_${props.gin.fieldName}_idx`;
    const prefix = `@@index([${props.gin.fieldName}(ops: raw("gin_trgm_ops"))], type: Gin`;
    if (name.length <= MAX_IDENTIFIER_LENGTH)
        return `${prefix})`;
    return `${prefix}, map: "${shortName(name)}")`;
}
/* -----------------------------------------------------------
  BACKGROUND
----------------------------------------------------------- */
function writeComment(content) {
    return content
        .split("\r\n")
        .join("\n")
        .split("\n")
        .map((str) => `///${str.length ? ` ${str}` : ""}`)
        .join("\n")
        .trim();
}
function indent(content) {
    return content
        .split("\r\n")
        .join("\n")
        .split("\n")
        .map((str) => `  ${str}`)
        .join("\n");
}
function shortName(name) {
    if (name.length <= MAX_IDENTIFIER_LENGTH)
        return name;
    const hash = crypto_1.default
        .createHash("md5")
        .update(name)
        .digest("hex")
        .substring(0, HASH_TRUNCATION_LENGTH);
    return `${name.substring(0, MAX_IDENTIFIER_LENGTH - HASH_TRUNCATION_LENGTH - 1)}_${hash}`;
}
const LOGICAL_TYPES = {
    // native types
    boolean: "Boolean",
    int: "Int",
    double: "Float",
    string: "String",
    // formats
    datetime: "DateTime",
    uuid: "String",
    uri: "String",
};
const POSTGRES_PHYSICAL_TYPES = {
    int: "@db.Integer",
    double: "@db.DoublePrecision",
    uuid: "@db.Uuid",
    datetime: "@db.Timestamptz",
    uri: "@db.VarChar(80000)",
};
const POSTGRES_MAIN_FILE = utils_1.StringUtil.trim `
  generator client {
    provider        = "prisma-client-js"
    previewFeatures = ["postgresqlExtensions", "views"]
    binaryTargets   = ["native"]
  }
  datasource db {
    provider   = "postgresql"
    url        = env("DATABASE_URL")
    extensions = []
  }
  generator markdown {
    provider = "prisma-markdown"
    output   = "../docs/ERD.md"
  }
`;
const SQLITE_MAIN_FILE = utils_1.StringUtil.trim `
  generator client {
    provider = "prisma-client-js"
  }
  datasource db {
    provider = "sqlite"
    url      = "file:../../data.db"
  }
  generator markdown {
    provider = "prisma-markdown"
    output   = "../docs/ERD.md"
  }
`;
const MAX_IDENTIFIER_LENGTH = 63;
const HASH_TRUNCATION_LENGTH = 8;
//# sourceMappingURL=writePrismaApplication.js.map