import loconfig from '../helpers/config.js';
import glob, { supportsGlob } from '../helpers/glob.js';
import message from '../helpers/message.js';
import notification from '../helpers/notification.js';
import resolve from '../helpers/template.js';
import { merge } from '../utils/index.js';
import concat from 'concat';
import {
    basename,
    normalize,
} from 'node:path';

/**
 * @const {object} defaultGlobOptions     - The default shared glob options.
 * @const {object} developmentGlobOptions - The predefined glob options for development.
 * @const {object} productionGlobOptions  - The predefined glob options for production.
 */
export const defaultGlobOptions = {
};
export const developmentGlobOptions = Object.assign({}, defaultGlobOptions);
export const productionGlobOptions  = Object.assign({}, defaultGlobOptions);

/**
 * @typedef  {object} ConcatOptions
 * @property {boolean} removeDuplicates - Removes duplicate paths from
 *     the array of matching files and folders.
 *     Only the first occurrence of each path is kept.
 */

/**
 * @const {ConcatOptions} defaultConcatOptions     - The default shared concatenation options.
 * @const {ConcatOptions} developmentConcatOptions - The predefined concatenation options for development.
 * @const {ConcatOptions} productionConcatOptions  - The predefined concatenation options for production.
 */
export const defaultConcatOptions = {
    removeDuplicates: true,
};
export const developmentConcatOptions = Object.assign({}, defaultConcatOptions);
export const productionConcatOptions  = Object.assign({}, defaultConcatOptions);

/**
 * @const {object} developmentConcatFilesArgs - The predefined `concatFiles()` options for development.
 * @const {object} productionConcatFilesArgs  - The predefined `concatFiles()` options for production.
 */
export const developmentConcatFilesArgs = [
    developmentGlobOptions,
    developmentConcatOptions,
];
export const productionConcatFilesArgs  = [
    productionGlobOptions,
    productionConcatOptions,
];

/**
 * Concatenates groups of files.
 *
 * @todo Add support for minification.
 *
 * @async
 * @param  {object|boolean} [globOptions=null]   - Customize the glob options.
 *     If `null`, default production options are used.
 *     If `false`, the glob function will be ignored.
 * @param  {object}         [concatOptions=null] - Customize the concatenation options.
 *     If `null`, default production options are used.
 * @return {Promise}
 */
export default async function concatFiles(globOptions = null, concatOptions = null) {
    if (supportsGlob) {
        if (globOptions == null) {
            globOptions = productionGlobOptions;
        } else if (
            globOptions !== false &&
            globOptions !== developmentGlobOptions &&
            globOptions !== productionGlobOptions
        ) {
            globOptions = merge({}, defaultGlobOptions, globOptions);
        }
    }

    if (concatOptions == null) {
        concatOptions = productionConcatOptions;
    } else if (
        concatOptions !== developmentConcatOptions &&
        concatOptions !== productionConcatOptions
    ) {
        concatOptions = merge({}, defaultConcatOptions, concatOptions);
    }

    /**
     * @async
     * @param  {object}   entry          - The entrypoint to process.
     * @param  {string[]} entry.includes - One or more paths to process.
     * @param  {string}   entry.outfile  - The file to write to.
     * @param  {?string}  [entry.label]  - The task label.
     *     Defaults to the outfile name.
     * @return {Promise}
     */
    loconfig.tasks.concats?.forEach(async ({
        includes,
        outfile,
        label = null
    }) => {
        if (!label) {
            label = basename(outfile || 'undefined');
        }

        const timeLabel = `${label} concatenated in`;
        console.time(timeLabel);

        try {
            if (!Array.isArray(includes)) {
                includes = [ includes ];
            }

            includes = resolve(includes);
            outfile  = resolve(outfile);

            if (supportsGlob && globOptions) {
                includes = await glob(includes, globOptions);
            }

            if (concatOptions.removeDuplicates) {
                includes = includes.map((path) => normalize(path));
                includes = [ ...new Set(includes) ];
            }

            await concat(includes, outfile);

            if (includes.length) {
                message(`${label} concatenated`, 'success', timeLabel);
            } else {
                message(`${label} is empty`, 'notice', timeLabel);
            }
        } catch (err) {
            message(`Error concatenating ${label}`, 'error');
            message(err);

            notification({
                title:   `${label} concatenation failed 🚨`,
                message: `${err.name}: ${err.message}`
            });
        }
    });
};
