#!/usr/bin/env node
const fs = require('fs');
const { promises: fsp } = fs;
const path = require('path');
const glob = require('glob');
const util = require('util');
const args = require('minimist')(process.argv);
const chokidar = require('chokidar');

const componentIndex = path.join('src', 'component-index.js');
const componentIndexTmp = componentIndex+".tmp";
const componentsDir = path.join('src', 'components');
const componentJsGlob = '**/*.js';
const componentTsGlob = '**/*.tsx';
let prevFiles = [];

async function reskindex() {
    const jsFiles = glob.sync(componentJsGlob, {cwd: componentsDir}).sort();
    const tsFiles = glob.sync(componentTsGlob, {cwd: componentsDir}).sort();
    const files = [...tsFiles, ...jsFiles];
    if (!filesHaveChanged(files, prevFiles)) {
        return;
    }
    prevFiles = files;

    const header = args.h || args.header;

    const strm = fs.createWriteStream(componentIndexTmp);
    // Wait for the open event to ensure the file descriptor is set
    await new Promise(resolve => strm.once("open", resolve));

    if (header) {
       strm.write(fs.readFileSync(header));
       strm.write('\n');
    }

    strm.write("/*\n");
    strm.write(" * THIS FILE IS AUTO-GENERATED\n");
    strm.write(" * You can edit it you like, but your changes will be overwritten,\n");
    strm.write(" * so you'd just be trying to swim upstream like a salmon.\n");
    strm.write(" * You are not a salmon.\n");
    strm.write(" */\n\n");
    strm.write("let components = {};\n");

    for (let i = 0; i < files.length; ++i) {
        const file = files[i].replace('.js', '').replace('.tsx', '');

        const moduleName = (file.replace(/\//g, '.'));
        const importName = moduleName.replace(/\./g, "$");

        strm.write("import " + importName + " from './components/" + file + "';\n");
        strm.write(importName + " && (components['"+moduleName+"'] = " + importName + ");");
        strm.write('\n');
        strm.uncork();
    }

    strm.write("export {components};\n");
    // Ensure the file has been fully written to disk before proceeding
    await util.promisify(fs.fsync)(strm.fd);
    await util.promisify(strm.end);
    await fsp.rename(componentIndexTmp, componentIndex);
}

// Expects both arrays of file names to be sorted
function filesHaveChanged(files, prevFiles) {
    if (files.length !== prevFiles.length) {
        return true;
    }
    // Check for name changes
    for (let i = 0; i < files.length; i++) {
        if (prevFiles[i] !== files[i]) {
            return true;
        }
    }
    return false;
}

// Wrapper since await at the top level is not well supported yet
function run() {
    (async function() {
        await reskindex();
        console.log("Reskindex completed");
    })();
}

// -w indicates watch mode where any FS events will trigger reskindex
if (!args.w) {
    run();
    return;
}

let watchDebouncer = null;
chokidar.watch(path.join(componentsDir, componentJsGlob)).on('all', (event, path) => {
    if (path === componentIndex) return;
    if (watchDebouncer) clearTimeout(watchDebouncer);
    watchDebouncer = setTimeout(run, 1000);
});