#!/usr/bin/env node

// Adapted from https://github.com/hashicorp/next-remote-watch
// A copy of next-remote-watch with an additional ws reload emitter.
// The app listens to the event and triggers a client-side router refresh
// see components/ClientReload.js

const chalk = require('chalk');
const chokidar = require('chokidar');
const program = require('commander');
const http = require('http');
const SocketIO = require('socket.io');
const express = require('express');
const spawn = require('child_process').spawn;
const next = require('next');
const path = require('path');
const { parse } = require('url');

const pkg = require('../package.json');

const defaultWatchEvent = 'change';

program.storeOptionsAsProperties().version(pkg.version);
program
  .option('-r, --root [dir]', 'root directory of your nextjs app')
  .option(
    '-s, --script [path]',
    'path to the script you want to trigger on a watcher event',
    false
  )
  .option('-c, --command [cmd]', 'command to execute on a watcher event', false)
  .option(
    '-e, --event [name]',
    `name of event to watch, defaults to ${defaultWatchEvent}`,
    defaultWatchEvent
  )
  .option(
    '-p, --polling [name]',
    `use polling for the watcher, defaults to false`,
    false
  )
  .parse(process.argv);

const shell = process.env.SHELL;
const app = next({ dev: true, dir: program.root || process.cwd() });
const port = parseInt(process.env.PORT, 10) || 3000;
const handle = app.getRequestHandler();

app.prepare().then(() => {
  // if directories are provided, watch them for changes and trigger reload
  if (program.args.length > 0) {
    chokidar
      .watch(program.args, { usePolling: Boolean(program.polling) })
      .on(
        program.event,
        async (filePathContext, eventContext = defaultWatchEvent) => {
          // Emit changes via socketio
          io.sockets.emit('reload', filePathContext);
          app.server.hotReloader.send('building');

          if (program.command) {
            // Use spawn here so that we can pipe stdio from the command without buffering
            spawn(
              shell,
              [
                '-c',
                program.command
                  .replace(/\{event\}/gi, filePathContext)
                  .replace(/\{path\}/gi, eventContext),
              ],
              {
                stdio: 'inherit',
              }
            );
          }

          if (program.script) {
            try {
              // find the path of your --script script
              const scriptPath = path.join(
                process.cwd(),
                program.script.toString()
              );

              // require your --script script
              const executeFile = require(scriptPath);

              // run the exported function from your --script script
              executeFile(filePathContext, eventContext);
            } catch (e) {
              console.error('Remote script failed');
              console.error(e);
              return e;
            }
          }

          app.server.hotReloader.send('reloadPage');
        }
      );
  }

  // create an express server
  const expressApp = express();
  const server = http.createServer(expressApp);

  // watch files with socketIO
  const io = SocketIO(server);

  // special handling for mdx reload route
  const reloadRoute = express.Router();
  reloadRoute.use(express.json());
  reloadRoute.all('/', (req, res) => {
    // log message if present
    const msg = req.body.message;
    const color = req.body.color;
    msg && console.log(color ? chalk[color](msg) : msg);

    // reload the nextjs app
    app.server.hotReloader.send('building');
    app.server.hotReloader.send('reloadPage');
    res.end('Reload initiated');
  });

  expressApp.use('/__next_reload', reloadRoute);

  // handle all other routes with next.js
  expressApp.all('*', (req, res) => handle(req, res, parse(req.url, true)));

  // fire it up
  server.listen(port, (err) => {
    if (err) throw err;
    console.log(`> Ready on http://localhost:${port}`);
  });
});
