"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isValidJSON = isValidJSON;
exports.guessSequenceType = guessSequenceType;
exports.needLoadData = needLoadData;
exports.loadData = loadData;
exports.getAssembly = getAssembly;
exports.exists = exists;
exports.resolveTargetPath = resolveTargetPath;
exports.enhanceAssembly = enhanceAssembly;
exports.createDefaultConfig = createDefaultConfig;
exports.loadOrCreateConfig = loadOrCreateConfig;
exports.addAssemblyToConfig = addAssemblyToConfig;
exports.saveConfigAndReport = saveConfigAndReport;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const utils_1 = require("../utils");
const { rename, copyFile, symlink } = fs_1.default.promises;
function isValidJSON(string) {
    try {
        JSON.parse(string);
        return true;
    }
    catch (error) {
        return false;
    }
}
function guessSequenceType(sequence) {
    if (sequence.endsWith('.fa') ||
        sequence.endsWith('.fna') ||
        sequence.endsWith('.fasta')) {
        return 'indexedFasta';
    }
    if (sequence.endsWith('.fa.gz') ||
        sequence.endsWith('.fna.gz') ||
        sequence.endsWith('.fasta.gz')) {
        return 'bgzipFasta';
    }
    if (sequence.endsWith('.2bit')) {
        return 'twoBit';
    }
    if (sequence.endsWith('.chrom.sizes')) {
        return 'chromSizes';
    }
    if (sequence.endsWith('.json')) {
        return 'custom';
    }
    if (isValidJSON(sequence)) {
        return 'custom';
    }
    throw new Error('Could not determine sequence type automatically, add --type to specify it');
}
function needLoadData(location) {
    let locationUrl;
    try {
        locationUrl = new URL(location);
    }
    catch (error) {
        // ignore
    }
    if (locationUrl) {
        return false;
    }
    return true;
}
async function loadData({ load, filePaths, destination, }) {
    let locationUrl;
    try {
        locationUrl = new URL(filePaths[0]);
    }
    catch (error) {
        // ignore
    }
    if (locationUrl) {
        return false;
    }
    switch (load) {
        case 'copy': {
            await Promise.all(filePaths.map(async (filePath) => {
                if (!filePath) {
                    return undefined;
                }
                return copyFile(filePath, path_1.default.join(path_1.default.dirname(destination), path_1.default.basename(filePath)));
            }));
            return true;
        }
        case 'symlink': {
            await Promise.all(filePaths.map(async (filePath) => {
                if (!filePath) {
                    return undefined;
                }
                return symlink(path_1.default.resolve(filePath), path_1.default.join(path_1.default.dirname(destination), path_1.default.basename(filePath)));
            }));
            return true;
        }
        case 'move': {
            await Promise.all(filePaths.map(async (filePath) => {
                if (!filePath) {
                    return undefined;
                }
                return rename(filePath, path_1.default.join(path_1.default.dirname(destination), path_1.default.basename(filePath)));
            }));
            return true;
        }
        case 'inPlace': {
            return false;
        }
    }
    return false;
}
async function getAssembly({ runFlags, argsSequence, target, }) {
    let sequence;
    if (needLoadData(argsSequence) && !runFlags.load) {
        throw new Error('Please specify the loading operation for this file with --load copy|symlink|move|inPlace');
    }
    else if (!needLoadData(argsSequence) && runFlags.load) {
        throw new Error('URL detected with --load flag. Please rerun the function without the --load flag');
    }
    let { name } = runFlags;
    let { type } = runFlags;
    if (type) {
        (0, utils_1.debug)(`Type is: ${type}`);
    }
    else {
        type = guessSequenceType(argsSequence);
        (0, utils_1.debug)(`No type specified, guessing type: ${type}`);
    }
    if (name) {
        (0, utils_1.debug)(`Name is: ${name}`);
    }
    switch (type) {
        case 'indexedFasta': {
            const { skipCheck, force, load, faiLocation } = runFlags;
            let sequenceLocation = await (0, utils_1.resolveFileLocation)(argsSequence, !(skipCheck || force), load === 'inPlace');
            (0, utils_1.debug)(`FASTA location resolved to: ${sequenceLocation}`);
            let indexLocation = await (0, utils_1.resolveFileLocation)(faiLocation || `${argsSequence}.fai`, !(skipCheck || force), load === 'inPlace');
            (0, utils_1.debug)(`FASTA index location resolved to: ${indexLocation}`);
            if (!name) {
                name = path_1.default.basename(sequenceLocation, sequenceLocation.endsWith('.fasta') ? '.fasta' : '.fa');
                (0, utils_1.debug)(`Guessing name: ${name}`);
            }
            const loaded = load
                ? await loadData({
                    load,
                    filePaths: [sequenceLocation, indexLocation],
                    destination: target,
                })
                : false;
            if (loaded) {
                sequenceLocation = path_1.default.basename(sequenceLocation);
                indexLocation = path_1.default.basename(indexLocation);
            }
            sequence = {
                type: 'ReferenceSequenceTrack',
                trackId: `${name}-ReferenceSequenceTrack`,
                adapter: {
                    type: 'IndexedFastaAdapter',
                    fastaLocation: {
                        uri: sequenceLocation,
                        locationType: 'UriLocation',
                    },
                    faiLocation: { uri: indexLocation, locationType: 'UriLocation' },
                },
            };
            break;
        }
        case 'bgzipFasta': {
            let sequenceLocation = await (0, utils_1.resolveFileLocation)(argsSequence, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
            (0, utils_1.debug)(`compressed FASTA location resolved to: ${sequenceLocation}`);
            let indexLocation = await (0, utils_1.resolveFileLocation)(runFlags.faiLocation || `${sequenceLocation}.fai`, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
            (0, utils_1.debug)(`compressed FASTA index location resolved to: ${indexLocation}`);
            let bgzipIndexLocation = await (0, utils_1.resolveFileLocation)(runFlags.gziLocation || `${sequenceLocation}.gzi`, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
            (0, utils_1.debug)(`bgzip index location resolved to: ${bgzipIndexLocation}`);
            if (!name) {
                name = path_1.default.basename(sequenceLocation, sequenceLocation.endsWith('.fasta.gz') ? '.fasta.gz' : '.fa.gz');
                (0, utils_1.debug)(`Guessing name: ${name}`);
            }
            const loaded = runFlags.load
                ? await loadData({
                    load: runFlags.load,
                    filePaths: [sequenceLocation, indexLocation, bgzipIndexLocation],
                    destination: target,
                })
                : false;
            if (loaded) {
                sequenceLocation = path_1.default.basename(sequenceLocation);
                indexLocation = path_1.default.basename(indexLocation);
                bgzipIndexLocation = path_1.default.basename(bgzipIndexLocation);
            }
            sequence = {
                type: 'ReferenceSequenceTrack',
                trackId: `${name}-ReferenceSequenceTrack`,
                adapter: {
                    type: 'BgzipFastaAdapter',
                    fastaLocation: {
                        uri: sequenceLocation,
                        locationType: 'UriLocation',
                    },
                    faiLocation: { uri: indexLocation, locationType: 'UriLocation' },
                    gziLocation: {
                        uri: bgzipIndexLocation,
                        locationType: 'UriLocation',
                    },
                },
            };
            break;
        }
        case 'twoBit': {
            let sequenceLocation = await (0, utils_1.resolveFileLocation)(argsSequence, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
            (0, utils_1.debug)(`2bit location resolved to: ${sequenceLocation}`);
            if (!name) {
                name = path_1.default.basename(sequenceLocation, '.2bit');
                (0, utils_1.debug)(`Guessing name: ${name}`);
            }
            const loaded = runFlags.load
                ? await loadData({
                    load: runFlags.load,
                    filePaths: [sequenceLocation],
                    destination: target,
                })
                : false;
            if (loaded) {
                sequenceLocation = path_1.default.basename(sequenceLocation);
            }
            sequence = {
                type: 'ReferenceSequenceTrack',
                trackId: `${name}-ReferenceSequenceTrack`,
                adapter: {
                    type: 'TwoBitAdapter',
                    twoBitLocation: {
                        uri: sequenceLocation,
                        locationType: 'UriLocation',
                    },
                },
            };
            break;
        }
        case 'chromSizes': {
            let sequenceLocation = await (0, utils_1.resolveFileLocation)(argsSequence, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
            (0, utils_1.debug)(`chrom.sizes location resolved to: ${sequenceLocation}`);
            if (!name) {
                name = path_1.default.basename(sequenceLocation, '.chrom.sizes');
                (0, utils_1.debug)(`Guessing name: ${name}`);
            }
            const loaded = runFlags.load
                ? await loadData({
                    load: runFlags.load,
                    filePaths: [sequenceLocation],
                    destination: target,
                })
                : false;
            if (loaded) {
                sequenceLocation = path_1.default.basename(sequenceLocation);
            }
            sequence = {
                type: 'ReferenceSequenceTrack',
                trackId: `${name}-ReferenceSequenceTrack`,
                adapter: {
                    type: 'ChromSizesAdapter',
                    chromSizesLocation: {
                        uri: sequenceLocation,
                        locationType: 'UriLocation',
                    },
                },
            };
            break;
        }
        case 'custom': {
            const adapter = await (0, utils_1.readInlineOrFileJson)(argsSequence);
            (0, utils_1.debug)(`Custom adapter: ${JSON.stringify(adapter)}`);
            if (!name) {
                if (isValidJSON(argsSequence)) {
                    throw new Error('Must provide --name when using custom inline JSON sequence');
                }
                else {
                    name = path_1.default.basename(argsSequence, '.json');
                }
                (0, utils_1.debug)(`Guessing name: ${name}`);
            }
            if (!('type' in adapter)) {
                throw new Error(`No "type" specified in sequence adapter "${JSON.stringify(adapter)}"`);
            }
            sequence = {
                type: 'ReferenceSequenceTrack',
                trackId: `${name}-ReferenceSequenceTrack`,
                adapter,
            };
            break;
        }
    }
    return { name, sequence };
}
async function exists(s) {
    try {
        await fs_1.default.promises.access(s, fs_1.default.constants.F_OK);
        return true;
    }
    catch {
        return false;
    }
}
async function resolveTargetPath(output) {
    if (!(await exists(output))) {
        const dir = output.endsWith('.json') ? path_1.default.dirname(output) : output;
        await fs_1.default.promises.mkdir(dir, { recursive: true });
    }
    let isDir = false;
    try {
        isDir = fs_1.default.statSync(output).isDirectory();
    }
    catch (e) { }
    return isDir ? path_1.default.join(output, 'config.json') : output;
}
async function enhanceAssembly(assembly, runFlags) {
    const enhancedAssembly = { ...assembly };
    if (runFlags.alias?.length) {
        (0, utils_1.debug)(`Adding assembly aliases: ${runFlags.alias}`);
        enhancedAssembly.aliases = runFlags.alias;
    }
    if (runFlags.refNameColors) {
        enhancedAssembly.refNameColors = runFlags.refNameColors
            .split(',')
            .map((color) => color.trim());
    }
    if (runFlags.refNameAliases) {
        if (runFlags.refNameAliasesType &&
            runFlags.refNameAliasesType === 'custom') {
            const refNameAliasesConfig = await (0, utils_1.readInlineOrFileJson)(runFlags.refNameAliases);
            if (!refNameAliasesConfig.type) {
                throw new Error(`No "type" specified in refNameAliases adapter "${JSON.stringify(refNameAliasesConfig)}"`);
            }
            (0, utils_1.debug)(`Adding custom refNameAliases config: ${JSON.stringify(refNameAliasesConfig)}`);
            enhancedAssembly.refNameAliases = {
                adapter: refNameAliasesConfig,
            };
        }
        else {
            const refNameAliasesLocation = await (0, utils_1.resolveFileLocation)(runFlags.refNameAliases, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
            (0, utils_1.debug)(`refName aliases file location resolved to: ${refNameAliasesLocation}`);
            enhancedAssembly.refNameAliases = {
                adapter: {
                    type: 'RefNameAliasAdapter',
                    location: {
                        uri: refNameAliasesLocation,
                        locationType: 'UriLocation',
                    },
                },
            };
        }
    }
    if (runFlags.displayName) {
        enhancedAssembly.displayName = runFlags.displayName;
    }
    return enhancedAssembly;
}
function createDefaultConfig() {
    return {
        assemblies: [],
        configuration: {},
        connections: [],
        defaultSession: {
            name: 'New Session',
        },
        tracks: [],
    };
}
async function loadOrCreateConfig(target) {
    const defaultConfig = createDefaultConfig();
    if (fs_1.default.existsSync(target)) {
        (0, utils_1.debug)(`Found existing config file ${target}`);
        return {
            ...defaultConfig,
            ...(await (0, utils_1.readJsonFile)(target)),
        };
    }
    else {
        (0, utils_1.debug)(`Creating config file ${target}`);
        return { ...defaultConfig };
    }
}
async function addAssemblyToConfig({ config, assembly, runFlags, }) {
    const updatedConfig = { ...config };
    if (!updatedConfig.assemblies) {
        updatedConfig.assemblies = [];
    }
    const idx = updatedConfig.assemblies.findIndex(configAssembly => configAssembly.name === assembly.name);
    if (idx !== -1) {
        (0, utils_1.debug)(`Found existing assembly ${assembly.name} in configuration`);
        if (runFlags.overwrite || runFlags.force) {
            (0, utils_1.debug)(`Overwriting assembly ${assembly.name} in configuration`);
            updatedConfig.assemblies[idx] = assembly;
            return { config: updatedConfig, wasOverwritten: true };
        }
        else {
            throw new Error(`Cannot add assembly with name ${assembly.name}, an assembly with that name already exists`);
        }
    }
    else {
        updatedConfig.assemblies.push(assembly);
        return { config: updatedConfig, wasOverwritten: false };
    }
}
async function saveConfigAndReport({ config, target, assembly, wasOverwritten, }) {
    (0, utils_1.debug)(`Writing configuration to file ${target}`);
    await (0, utils_1.writeJsonFile)(target, config);
    console.log(`${wasOverwritten ? 'Overwrote' : 'Added'} assembly "${assembly.name}" ${wasOverwritten ? 'in' : 'to'} ${target}`);
}
