const gulp = require("gulp");
const fs = require("fs/promises");
const path = require("path");
const sass = require("sass");
const CleanCSS = require("clean-css");
const postcss = require("postcss");
const autoprefixer = require("autoprefixer");
const browserSync = require("browser-sync").create();
const fileInclude = require("gulp-file-include");
const esbuild = require("esbuild");

const paths = {
  styles: {
    watch: "src/scss/**/*.scss",
    entry: ["src/scss/kit.scss", "src/scss/demo.scss"],
    dest: "dist/css"
  },
  scripts: {
    watch: "src/js/**/*.js",
    entry: "src/js/index.js",
    outfile: "dist/js/vmk-ui-kit.js",
    esmOutfile: "dist/js/vmk-ui-kit.esm.js"
  },
  vue: {
    watch: "src/vue/**/*.js",
    entry: "src/vue/index.js",
    outfile: "dist/vue/index.js"
  },
  html: {
    watch: "demo/**/*.html",
    entry: "demo/*.html",
    dest: "dist"
  },
  assets: {
    src: "public/**/*",
    dest: "dist"
  }
};

async function compileStyle(entry) {
  const result = await sass.compileAsync(entry, {
    style: "expanded",
    sourceMap: true,
    sourceMapIncludeSources: true
  });

  const filename = `${path.basename(entry, path.extname(entry))}.css`;
  const outfile = path.join(paths.styles.dest, filename);
  const mapfile = `${outfile}.map`;

  const prefixed = await postcss([autoprefixer]).process(result.css, {
    from: path.resolve(entry),
    to: path.resolve(outfile),
    map: {
      prev: result.sourceMap,
      inline: false,
      annotation: false
    }
  });

  const minified = new CleanCSS({ sourceMap: true }).minify(
    prefixed.css,
    prefixed.map && prefixed.map.toString()
  );

  if (minified.errors.length) {
    throw new Error(minified.errors.join("\n"));
  }

  await fs.mkdir(paths.styles.dest, { recursive: true });
  await fs.writeFile(outfile, `${minified.styles}\n/*# sourceMappingURL=${path.basename(mapfile)} */\n`);

  if (minified.sourceMap) {
    await fs.writeFile(mapfile, minified.sourceMap.toString());
  }
}

async function styles() {
  await Promise.all(paths.styles.entry.map(compileStyle));
  browserSync.reload("*.css");
}

function scripts() {
  return esbuild.build({
    entryPoints: [paths.scripts.entry],
    bundle: true,
    minify: true,
    sourcemap: true,
    outfile: paths.scripts.outfile,
    target: ["es2018"],
    platform: "browser",
    format: "iife",
    globalName: "VMKUIKit"
  }).then(() => {
    browserSync.reload();
  });
}

function scriptsEsm() {
  return esbuild.build({
    entryPoints: [paths.scripts.entry],
    bundle: true,
    minify: true,
    sourcemap: true,
    outfile: paths.scripts.esmOutfile,
    target: ["es2018"],
    platform: "browser",
    format: "esm"
  }).then(() => {
    browserSync.reload();
  });
}

function vue() {
  return esbuild.build({
    entryPoints: [paths.vue.entry],
    bundle: true,
    minify: false,
    sourcemap: true,
    outfile: paths.vue.outfile,
    target: ["es2018"],
    platform: "browser",
    format: "esm",
    external: ["vue"]
  }).then(() => {
    browserSync.reload();
  });
}

function html() {
  return gulp.src(paths.html.entry)
    .pipe(fileInclude({
      prefix: "@@",
      basepath: "@file"
    }))
    .pipe(gulp.dest(paths.html.dest))
    .pipe(browserSync.stream());
}

function assets() {
  return gulp.src(paths.assets.src, { encoding: false })
    .pipe(gulp.dest(paths.assets.dest));
}

function watchFiles() {
  gulp.watch(paths.styles.watch, styles);
  gulp.watch(paths.scripts.watch, gulp.parallel(scripts, scriptsEsm));
  gulp.watch(paths.vue.watch, vue);
  gulp.watch(paths.html.watch, html);
  gulp.watch(paths.assets.src, gulp.series(assets, reload));
}

function reload(done) {
  browserSync.reload();
  done();
}

function serve(done) {
  browserSync.init({
    server: {
      baseDir: "dist"
    },
    open: false,
    notify: false,
    ghostMode: false
  });

  done();
}

const build = gulp.parallel(styles, scripts, scriptsEsm, vue, html, assets);

exports.styles = styles;
exports.scripts = scripts;
exports.scriptsEsm = scriptsEsm;
exports.vue = vue;
exports.html = html;
exports.assets = assets;
exports.build = build;
exports.serve = gulp.series(build, serve, watchFiles);
exports.default = exports.serve;
