import exec from "../util/exec";
import fs from "fs";
import { merge } from "merge-anything";
import { mkTempFile } from "../util/tempdir";
import parallel from "../util/parallel";
import path from "path";
import PluginError from "plugin-error";
import { promisify } from "util";

const writeFile = promisify(fs.writeFile);
const exists = promisify(fs.exists);

const PLUGIN_NAME = "pandoc";

const DEFAULTS = {
  slideLevel: 2,
  tocDepth: 2,
  revealJSURL: "reveal.js",
  template: path.normalize(__dirname + "/../../templates/revealjs.html"),
  variables: {
    width: 960,
    height: 700,
    navigationMode: "linear",
    theme: "inrae",
    slideNumber: true,
    history: true,
    fragmentInURL: true,
  },
};

/**
 * @param {string} url
 * @return {function}
 */
export function getRevealJSResolver(url) {
  if (url.match(/^(https?:)?\/\//i)) {
    return () => url;
  }
  return (output) => path.relative(path.dirname(output.relative), url);
}

/**
 * @param {import("vinyl")} input
 * @param {string} path
 * @return {Promise<void>|never}
 * @throws PluginError
 */
async function writeToDisk(input, path) {
  if (input.isStream()) {
    const write = fs.createWriteStream(path);
    const done = new Promise((resolve, reject) => {
      write.on("close", () => resolve());
      input.on("error", reject);
      write.on("error", reject);
    });
    input.contents.pipe(write);
    return done;
  }

  if (input.isBuffer()) {
    return writeFile(path, input.contents, {});
  }

  throw new PluginError(PLUGIN_NAME, `cannot handle input ${input.inspect()}`);
}

/**
 * @param {import("vinyl")} input
 * @return {import("vinyl")}
 * @throws PluginError
 */
async function onDisk(input) {
  if (await exists(input.path)) {
    return input;
  }

  const tmpFile = await mkTempFile(input);
  await writeToDisk(input, tmpFile.path);
  return tmpFile;
}

export default function pandoc(options = {}) {
  const { slideLevel, tocDepth, revealJSURL, template, variables } = merge(
    {},
    DEFAULTS,
    options
  );
  const revealJSURLResolver = getRevealJSResolver(revealJSURL);

  return parallel(
    /**
     * @param {import("vinyl")} input
     */
    async function (input) {
      const output = input.clone({ contents: false });
      output.extname = ".html";
      output.contents = null;

      const src = await onDisk(input);

      const args = [
        "--from=markdown+backtick_code_blocks+pandoc_title_block+yaml_metadata_block",
        "--to=revealjs",
        "--standalone",
        `--template=${template}`,
        `--slide-level=${slideLevel}`,
        "--toc",
        `--toc-depth=${tocDepth}`,
        `--variable=revealjs-url:${revealJSURLResolver(output)}`,
        ...Object.getOwnPropertyNames(variables).map(
          (name) => `--variable=${name}:${variables[name]}`
        ),
        src.path,
      ];

      const { stdout } = await exec(PLUGIN_NAME, "pandoc", args, {
        encoding: "buffer",
        cwd: input.cwd,
      });
      output.contents = stdout;

      this.push(output);
    }
  );
}