// ==========================================================================
// Gulp build script
// ==========================================================================
/* global require, __dirname */
/* eslint no-console: "off" */

const path = require('path');
const gulp = require('gulp');
// ------------------------------------
// JavaScript
// ------------------------------------
const terser = require('gulp-terser');
const rollup = require('gulp-better-rollup');
const babel = require('rollup-plugin-babel');
const commonjs = require('rollup-plugin-commonjs');
const resolve = require('rollup-plugin-node-resolve');
// ------------------------------------
// CSS
// ------------------------------------
const sass = require('gulp-sass');
const clean = require('gulp-clean-css');
const prefix = require('gulp-autoprefixer');
// ------------------------------------
// Images
// ------------------------------------
const svgstore = require('gulp-svgstore');
const imagemin = require('gulp-imagemin');
// ------------------------------------
// Utils
// ------------------------------------
const del = require('del');
const filter = require('gulp-filter');
const header = require('gulp-header');
const gitbranch = require('git-branch');
const rename = require('gulp-rename');
const replace = require('gulp-replace');
const ansi = require('ansi-colors');
const log = require('fancy-log');
const open = require('gulp-open');
const plumber = require('gulp-plumber');
const size = require('gulp-size');
const sourcemaps = require('gulp-sourcemaps');
const through = require('through2');
// ------------------------------------
// Deployment
// ------------------------------------
const aws = require('aws-sdk');
const publish = require('gulp-awspublish');
const FastlyPurge = require('fastly-purge');
// ------------------------------------
// Configs
// ------------------------------------
const pkg = require('./package.json');
const build = require('./build.json');
const deploy = require('./deploy.json');
// ------------------------------------
// Info from package
// ------------------------------------
const { browserslist: browsers, version } = pkg;
const minSuffix = '.min';

// Get AWS config
Object.values(deploy).forEach(target => {
    Object.assign(target, {
        publisher: publish.create({
            region: target.region,
            params: {
                Bucket: target.bucket,
            },
            credentials: new aws.SharedIniFileCredentials({ profile: 'plyr' }),
        }),
    });
});

// Paths
const paths = {
    plyr: {
        // Source paths
        src: {
            sass: path.join(__dirname, 'src/sass/**/*.scss'),
            js: path.join(__dirname, 'src/js/**/*.js'),
            sprite: path.join(__dirname, 'src/sprite/*.svg'),
        },

        // Output paths
        output: path.join(__dirname, 'dist/'),
    },
    demo: {
        // Source paths
        src: {
            sass: path.join(__dirname, 'demo/src/sass/**/*.scss'),
            js: path.join(__dirname, 'demo/src/js/**/*.js'),
        },

        // Output paths
        output: path.join(__dirname, 'demo/dist/'),

        // Demo
        root: path.join(__dirname, 'demo/'),
    },
    upload: [
        path.join(__dirname, `dist/*${minSuffix}.*`),
        path.join(__dirname, 'dist/*.css'),
        path.join(__dirname, 'dist/*.svg'),
        path.join(__dirname, `demo/dist/*${minSuffix}.*`),
        path.join(__dirname, 'demo/dist/*.css'),
        path.join(__dirname, 'demo/dist/*.svg'),
    ],
};

// Task arrays
const tasks = {
    css: [],
    js: [],
    sprite: [],
    clean: 'clean',
};

// Size plugin
const sizeOptions = { showFiles: true, gzip: true };

// Clean out /dist
gulp.task(tasks.clean, done => {
    const dirs = [paths.plyr.output, paths.demo.output].map(dir => path.join(dir, '**/*'));

    // Don't delete the mp4
    dirs.push(`!${path.join(paths.plyr.output, '**/*.mp4')}`);

    del(dirs);

    done();
});

// JavaScript
Object.entries(build.js).forEach(([filename, entry]) => {
    const { dist, formats, namespace, polyfill, src } = entry;

    formats.forEach(format => {
        const name = `js:${filename}:${format}`;
        const extension = format === 'es' ? 'mjs' : 'js';
        tasks.js.push(name);

        gulp.task(name, () =>
            gulp
                .src(src)
                .pipe(plumber())
                .pipe(sourcemaps.init())
                .pipe(
                    rollup(
                        {
                            plugins: [
                                resolve(),
                                commonjs(),
                                babel({
                                    presets: [
                                        [
                                            '@babel/env',
                                            {
                                                // debug: true,
                                                useBuiltIns: polyfill ? 'usage' : false,
                                                corejs: polyfill ? 3 : undefined,
                                            },
                                        ],
                                    ],
                                    babelrc: false,
                                    exclude: [/\/core-js\//],
                                }),
                            ],
                        },
                        {
                            name: namespace,
                            format,
                        },
                    ),
                )
                .pipe(header('typeof navigator === "object" && ')) // "Support" SSR (#935)
                .pipe(
                    rename({
                        extname: `.${extension}`,
                    }),
                )
                .pipe(gulp.dest(dist))
                .pipe(filter(`**/*.${extension}`))
                .pipe(terser())
                .pipe(rename({ suffix: minSuffix }))
                .pipe(size(sizeOptions))
                .pipe(sourcemaps.write(''))
                .pipe(gulp.dest(dist)),
        );
    });
});

// CSS
Object.entries(build.css).forEach(([filename, entry]) => {
    const { dist, src } = entry;
    const name = `css:${filename}`;
    tasks.css.push(name);

    gulp.task(name, () =>
        gulp
            .src(src)
            .pipe(plumber())
            .pipe(sass())
            .pipe(
                prefix(browsers, {
                    cascade: false,
                }),
            )
            .pipe(clean())
            .pipe(size(sizeOptions))
            .pipe(gulp.dest(dist)),
    );
});

// SVG Sprites
Object.entries(build.sprite).forEach(([filename, entry]) => {
    const { dist, src } = entry;
    const name = `sprite:${filename}`;
    tasks.sprite.push(name);

    gulp.task(name, () =>
        gulp
            .src(src)
            .pipe(plumber())
            .pipe(imagemin())
            .pipe(svgstore())
            .pipe(rename({ basename: path.parse(filename).name }))
            .pipe(size(sizeOptions))
            .pipe(gulp.dest(dist)),
    );
});

// Build all JS
gulp.task('js', () => gulp.parallel(...tasks.js));

// Watch for file changes
gulp.task('watch', () => {
    // Plyr core
    gulp.watch(paths.plyr.src.js, gulp.parallel(...tasks.js));
    gulp.watch(paths.plyr.src.sass, gulp.parallel(...tasks.css));
    gulp.watch(paths.plyr.src.sprite, gulp.parallel(...tasks.sprite));

    // Demo
    gulp.watch(paths.demo.src.js, gulp.parallel(...tasks.js));
    gulp.watch(paths.demo.src.sass, gulp.parallel(...tasks.css));
});

// Build distribution
gulp.task('build', gulp.series(tasks.clean, gulp.parallel(...tasks.js, ...tasks.css, ...tasks.sprite)));

// Default gulp task
gulp.task('default', gulp.series('build', 'watch'));

// Publish a version to CDN and demo
// --------------------------------------------
// Get deployment config
let credentials = {};
try {
    credentials = require('./credentials.json'); //eslint-disable-line
} catch (e) {
    // Do nothing
}

// Get branch info
const branch = {
    current: gitbranch.sync(),
    master: 'master',
    beta: 'beta',
};

const maxAge = 31536000; // 1 year
const options = {
    cdn: {
        headers: {
            'Cache-Control': `max-age=${maxAge}`,
        },
    },
    demo: {
        uploadPath: branch.current === branch.beta ? 'beta' : null,
        headers: {
            'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
        },
    },
    symlinks(ver, filename) {
        return {
            headers: {
                // http://stackoverflow.com/questions/2272835/amazon-s3-object-redirect
                'x-amz-website-redirect-location': `/${ver}/${filename}`,
                'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
            },
        };
    },
};

const regex =
    '(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*).(?:0|[1-9][0-9]*)(?:-[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?';
const semver = new RegExp(`v${regex}`, 'gi');
const localPath = new RegExp('(../)?dist', 'gi');
const versionPath = `https://${deploy.cdn.domain}/${version}`;
const cdnpath = new RegExp(`${deploy.cdn.domain}/${regex}/`, 'gi');

const renameFile = rename(p => {
    p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line
    p.dirname = p.dirname.replace('.', version); // eslint-disable-line
});

// Check we're on the correct branch to deploy
const canDeploy = () => {
    const allowed = [branch.master, branch.beta];

    if (!allowed.includes(branch.current)) {
        console.error(`Must be on ${allowed.join(', ')} to publish! (current: ${branch.current})`);

        return false;
    }

    return true;
};

gulp.task('version', done => {
    if (!canDeploy()) {
        done();
        return null;
    }

    const { domain } = deploy.cdn;

    log(`Uploading ${ansi.green.bold(version)} to ${ansi.cyan(domain)}...`);

    // Replace versioned URLs in source
    const files = ['plyr.js', 'plyr.polyfilled.js', 'config/defaults.js'];

    return gulp
        .src(files.map(file => path.join(__dirname, `src/js/${file}`)), { base: '.' })
        .pipe(replace(semver, `v${version}`))
        .pipe(replace(cdnpath, `${domain}/${version}/`))
        .pipe(gulp.dest('./'));
});

// Publish version to CDN bucket
gulp.task('cdn', done => {
    if (!canDeploy()) {
        done();
        return null;
    }

    const { domain, publisher } = deploy.cdn;

    if (!publisher) {
        throw new Error('No publisher instance. Check AWS configuration.');
    }

    log(`Uploading ${ansi.green.bold(pkg.version)} to ${ansi.cyan(domain)}...`);

    // Upload to CDN
    return (
        gulp
            .src(paths.upload)
            .pipe(renameFile)
            // Remove min suffix from source map URL
            .pipe(
                replace(
                    /sourceMappingURL=([\w-?.]+)/,
                    (match, filename) => `sourceMappingURL=${filename.replace(minSuffix, '')}`,
                ),
            )
            .pipe(size(sizeOptions))
            .pipe(replace(localPath, versionPath))
            .pipe(publisher.publish(options.cdn.headers))
            .pipe(publish.reporter())
    );
});

// Purge the fastly cache incase any 403/404 are cached
gulp.task('purge', () => {
    if (!Object.keys(credentials).includes('fastly')) {
        throw new Error('Fastly credentials required to purge cache.');
    }

    const { fastly } = credentials;
    const list = [];

    return gulp
        .src(paths.upload)
        .pipe(
            through.obj((file, enc, cb) => {
                const filename = file.path.split('/').pop();
                list.push(`${versionPath}/${filename.replace(minSuffix, '')}`);
                cb(null);
            }),
        )
        .on('end', () => {
            const purge = new FastlyPurge(fastly.token);

            list.forEach(url => {
                log(`Purging ${ansi.cyan(url)}...`);

                purge.url(url, (error, result) => {
                    if (error) {
                        log.error(error);
                    } else if (result) {
                        log(result);
                    }
                });
            });
        });
});

// Publish to demo bucket
gulp.task('demo', done => {
    if (!canDeploy()) {
        done();
        return null;
    }

    const { publisher } = deploy.demo;
    const { domain } = deploy.cdn;

    if (!publisher) {
        throw new Error('No publisher instance. Check AWS configuration.');
    }

    log(`Uploading ${ansi.green.bold(pkg.version)} to ${ansi.cyan(domain)}...`);

    // Replace versioned files in readme.md
    gulp.src([`${__dirname}/readme.md`])
        .pipe(replace(cdnpath, `${domain}/${version}/`))
        .pipe(gulp.dest(__dirname));

    // Replace local file paths with remote paths in demo HTML
    // e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
    const index = `${paths.demo.root}index.html`;
    const error = `${paths.demo.root}error.html`;
    const pages = [index];

    if (branch.current === branch.master) {
        pages.push(error);
    }

    return gulp
        .src(pages)
        .pipe(replace(localPath, versionPath))
        .pipe(publisher.publish(options.demo.headers))
        .pipe(publish.reporter());
});

gulp.task('error', done => {
    // Only update CDN for master (prod)
    if (!canDeploy() || branch.current !== branch.master) {
        done();
        return null;
    }

    const { publisher } = deploy.cdn;

    if (!publisher) {
        throw new Error('No publisher instance. Check AWS configuration.');
    }

    // Replace local file paths with remote paths in demo HTML
    // e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
    // Upload error.html to cdn
    return gulp
        .src(`${paths.demo.root}error.html`)
        .pipe(replace(localPath, versionPath))
        .pipe(publisher.publish(options.demo.headers))
        .pipe(publish.reporter());
});

// Open the demo site to check it's ok
gulp.task('open', () => {
    const { domain } = deploy.demo;

    return gulp.src(__filename).pipe(
        open({
            uri: `https://${domain}/${branch.current === branch.beta ? 'beta' : ''}`,
        }),
    );
});

// Do everything
gulp.task(
    'deploy',
    gulp.series(
        'version',
        tasks.clean,
        gulp.parallel(...tasks.js, ...tasks.css, ...tasks.sprite),
        'cdn',
        'demo',
        'purge',
        'open',
    ),
);
