"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const knex_1 = require("knex");
const datasource_1 = require("../../definitions/datasource");
const utils_1 = require("../utils");
const { FieldTypes, RelationshipTypes } = require("../../constants");
function generateSchema(schema, table, tables, oldTable = null, renamed) {
    let primaryKey = table && table.primary ? table.primary[0] : null;
    const columns = Object.values(table.schema);
    // all columns in a junction table will be meta
    let metaCols = columns.filter(col => col.meta);
    let isJunction = metaCols.length === columns.length;
    // can't change primary once its set for now
    if (primaryKey && !oldTable && !isJunction) {
        schema.increments(primaryKey).primary();
    }
    else if (!oldTable && isJunction) {
        schema.primary(metaCols.map(col => col.name));
    }
    // check if any columns need added
    const foreignKeys = Object.values(table.schema).map(col => col.foreignKey);
    for (let [key, column] of Object.entries(table.schema)) {
        // skip things that are already correct
        const oldColumn = oldTable ? oldTable.schema[key] : null;
        if ((oldColumn && oldColumn.type) ||
            (primaryKey === key && !isJunction) ||
            (renamed === null || renamed === void 0 ? void 0 : renamed.updated) === key) {
            continue;
        }
        switch (column.type) {
            case FieldTypes.STRING:
            case FieldTypes.OPTIONS:
            case FieldTypes.LONGFORM:
                schema.text(key);
                break;
            case FieldTypes.NUMBER:
                // if meta is specified then this is a junction table entry
                if (column.meta && column.meta.toKey && column.meta.toTable) {
                    const { toKey, toTable } = column.meta;
                    schema.integer(key).unsigned();
                    schema.foreign(key).references(`${toTable}.${toKey}`);
                }
                else if (foreignKeys.indexOf(key) === -1) {
                    schema.float(key);
                }
                break;
            case FieldTypes.BOOLEAN:
                schema.boolean(key);
                break;
            case FieldTypes.DATETIME:
                schema.datetime(key, {
                    useTz: !column.ignoreTimezones,
                });
                break;
            case FieldTypes.ARRAY:
                schema.json(key);
                break;
            case FieldTypes.LINK:
                // this side of the relationship doesn't need any SQL work
                if (column.relationshipType !== RelationshipTypes.MANY_TO_ONE &&
                    column.relationshipType !== RelationshipTypes.MANY_TO_MANY) {
                    if (!column.foreignKey || !column.tableId) {
                        throw "Invalid relationship schema";
                    }
                    const { tableName } = (0, utils_1.breakExternalTableId)(column.tableId);
                    // @ts-ignore
                    const relatedTable = tables[tableName];
                    if (!relatedTable) {
                        throw "Referenced table doesn't exist";
                    }
                    schema.integer(column.foreignKey).unsigned();
                    schema
                        .foreign(column.foreignKey)
                        .references(`${tableName}.${relatedTable.primary[0]}`);
                }
                break;
        }
    }
    if (renamed) {
        schema.renameColumn(renamed.old, renamed.updated);
    }
    // need to check if any columns have been deleted
    if (oldTable) {
        const deletedColumns = Object.entries(oldTable.schema)
            .filter(([key, schema]) => schema.type !== FieldTypes.LINK && table.schema[key] == null)
            .map(([key]) => key);
        deletedColumns.forEach(key => {
            if ((renamed === null || renamed === void 0 ? void 0 : renamed.old) === key) {
                return;
            }
            if (oldTable.constrained && oldTable.constrained.indexOf(key) !== -1) {
                schema.dropForeign(key);
            }
            schema.dropColumn(key);
        });
    }
    return schema;
}
function buildCreateTable(knex, table, tables) {
    return knex.createTable(table.name, schema => {
        generateSchema(schema, table, tables);
    });
}
function buildUpdateTable(knex, table, tables, oldTable, renamed) {
    return knex.alterTable(table.name, schema => {
        generateSchema(schema, table, tables, oldTable, renamed);
    });
}
function buildDeleteTable(knex, table) {
    return knex.dropTable(table.name);
}
class SqlTableQueryBuilder {
    // pass through client to get flavour of SQL
    constructor(client) {
        this.sqlClient = client;
    }
    getSqlClient() {
        return this.sqlClient;
    }
    /**
     * @param json the input JSON structure from which an SQL query will be built.
     * @return {string} the operation that was found in the JSON.
     */
    _operation(json) {
        return json.endpoint.operation;
    }
    _tableQuery(json) {
        var _a;
        let client = (0, knex_1.knex)({ client: this.sqlClient }).schema;
        if ((_a = json === null || json === void 0 ? void 0 : json.endpoint) === null || _a === void 0 ? void 0 : _a.schema) {
            client = client.withSchema(json.endpoint.schema);
        }
        let query;
        if (!json.table || !json.meta || !json.meta.tables) {
            throw "Cannot execute without table being specified";
        }
        switch (this._operation(json)) {
            case datasource_1.Operation.CREATE_TABLE:
                query = buildCreateTable(client, json.table, json.meta.tables);
                break;
            case datasource_1.Operation.UPDATE_TABLE:
                if (!json.meta || !json.meta.table) {
                    throw "Must specify old table for update";
                }
                query = buildUpdateTable(client, json.table, json.meta.tables, json.meta.table, json.meta.renamed);
                break;
            case datasource_1.Operation.DELETE_TABLE:
                query = buildDeleteTable(client, json.table);
                break;
            default:
                throw "Table operation is of unknown type";
        }
        return query.toSQL();
    }
}
exports.default = SqlTableQueryBuilder;
module.exports = SqlTableQueryBuilder;
