/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

const {
  STUBS_UPDATE_ENV,
  getCleanedPacket,
  getSerializedPacket,
  getStubFile,
  writeStubsToFile,
} = require(`${CHROME_URL_ROOT}stub-generator-helpers`);

const TEST_URI = "data:text/html;charset=utf-8,<!DOCTYPE html>stub generation";
const STUB_FILE = "evaluationResult.js";

add_task(async function () {
  const isStubsUpdate = Services.env.get(STUBS_UPDATE_ENV) == "true";
  info(`${isStubsUpdate ? "Update" : "Check"} ${STUB_FILE}`);

  const generatedStubs = await generateEvaluationResultStubs();

  if (isStubsUpdate) {
    await writeStubsToFile(STUB_FILE, generatedStubs);
    ok(true, `${STUB_FILE} was updated`);
    return;
  }

  const existingStubs = getStubFile(STUB_FILE);
  const FAILURE_MSG =
    "The evaluationResult stubs file needs to be updated by running `" +
    `mach test ${getCurrentTestFilePath()} --headless --setenv WEBCONSOLE_STUBS_UPDATE=true` +
    "`";

  if (generatedStubs.size !== existingStubs.rawPackets.size) {
    ok(false, FAILURE_MSG);
    return;
  }

  let failed = false;
  for (const [key, packet] of generatedStubs) {
    const packetStr = getSerializedPacket(packet, {
      sortKeys: true,
      replaceActorIds: true,
    });
    const existingPacketStr = getSerializedPacket(
      existingStubs.rawPackets.get(key),
      { sortKeys: true, replaceActorIds: true }
    );
    is(packetStr, existingPacketStr, `"${key}" packet has expected value`);
    failed = failed || packetStr !== existingPacketStr;
  }

  if (failed) {
    ok(false, FAILURE_MSG);
  } else {
    ok(true, "Stubs are up to date");
  }

  await closeTabAndToolbox();
});

async function generateEvaluationResultStubs() {
  const stubs = new Map();
  const toolbox = await openNewTabAndToolbox(TEST_URI, "webconsole");
  for (const [key, code] of getCommands()) {
    const packet = await toolbox.commands.scriptCommand.execute(code);
    stubs.set(key, getCleanedPacket(key, packet));
  }

  return stubs;
}

function getCommands() {
  const evaluationResultCommands = [
    "new Date(0)",
    "asdf()",
    "1 + @",
    "inspect({a: 1})",
    "undefined",
  ];

  const evaluationResult = new Map(
    evaluationResultCommands.map(cmd => [cmd, cmd])
  );
  evaluationResult.set(
    "longString message Error",
    `throw new Error("Long error ".repeat(10000))`
  );

  evaluationResult.set(`eval throw ""`, `throw ""`);
  evaluationResult.set(`eval throw "tomato"`, `throw "tomato"`);
  evaluationResult.set(`eval throw false`, `throw false`);
  evaluationResult.set(`eval throw 0`, `throw 0`);
  evaluationResult.set(`eval throw null`, `throw null`);
  evaluationResult.set(`eval throw undefined`, `throw undefined`);
  evaluationResult.set(`eval throw Symbol`, `throw Symbol("potato")`);
  evaluationResult.set(`eval throw Object`, `throw {vegetable: "cucumber"}`);
  evaluationResult.set(`eval throw Error Object`, `throw new Error("pumpkin")`);
  evaluationResult.set(
    `eval throw Error Object with custom name`,
    `
    var err = new Error("pineapple");
    err.name = "JuicyError";
    err.flavor = "delicious";
    throw err;
  `
  );
  evaluationResult.set(
    `eval throw Error Object with error cause`,
    `
    var originalError = new SyntaxError("original error")
    var err = new Error("something went wrong", {
      cause: originalError
    });
    throw err;
  `
  );
  evaluationResult.set(
    `eval throw Error Object with cause chain`,
    `
    var errA = new Error("err-a")
    var errB = new Error("err-b", { cause: errA })
    var errC = new Error("err-c", { cause: errB })
    var errD = new Error("err-d", { cause: errC })
    throw errD;
  `
  );
  evaluationResult.set(
    `eval throw Error Object with cyclical cause chain`,
    `
    var errX = new Error("err-x", { cause: errY})
    var errY = new Error("err-y", { cause: errX })
    throw errY;
  `
  );
  evaluationResult.set(
    `eval throw Error Object with falsy cause`,
    `throw new Error("false cause", { cause: false });`
  );
  evaluationResult.set(
    `eval throw Error Object with null cause`,
    `throw new Error("null cause", { cause: null });`
  );
  evaluationResult.set(
    `eval throw Error Object with undefined cause`,
    `throw new Error("undefined cause", { cause: undefined });`
  );
  evaluationResult.set(
    `eval throw Error Object with number cause`,
    `throw new Error("number cause", { cause: 0 });`
  );
  evaluationResult.set(
    `eval throw Error Object with string cause`,
    `throw new Error("string cause", { cause: "cause message" });`
  );
  evaluationResult.set(
    `eval throw Error Object with object cause`,
    `throw new Error("object cause", { cause: { code: 234, message: "ERR_234"} });`
  );

  evaluationResult.set(`eval pending promise`, `new Promise(() => {})`);
  evaluationResult.set(`eval Promise.resolve`, `Promise.resolve(123)`);
  evaluationResult.set(`eval Promise.reject`, `Promise.reject("ouch")`);
  evaluationResult.set(
    `eval resolved promise`,
    `Promise.resolve().then(() => 246)`
  );
  evaluationResult.set(
    `eval rejected promise`,
    `Promise.resolve().then(() => a.b.c)`
  );
  evaluationResult.set(
    `eval rejected promise with Error`,
    `Promise.resolve().then(() => {
      try {
        a.b.c
      } catch(e) {
        throw new Error("something went wrong", { cause: e })
      }
    })`
  );

  return evaluationResult;
}
