(function () {
  "use strict";

  var REQUEST_SOURCE = "word-picker-advanced-tester";
  var RESPONSE_SOURCE = "word-picker-advanced-runner";

  window.addEventListener("message", function (event) {
    var data = event.data;

    if (!data || data.source !== REQUEST_SOURCE) return;

    run(data)
      .then(function (response) {
        event.source.postMessage(response, "*");
      })
      .catch(function (error) {
        event.source.postMessage(
          {
            source: RESPONSE_SOURCE,
            id: data.id,
            ok: false,
            error: formatError(error),
          },
          "*",
        );
      });
  });

  async function run(data) {
    loadHtml(data.html || "");

    var logs = [];
    var restoreConsole = captureConsole(logs);

    try {
      var result = await executeScript(
        window,
        data.kind,
        data.script || "",
        data.params,
      );
    } finally {
      restoreConsole();
    }

    return {
      source: RESPONSE_SOURCE,
      id: data.id,
      ok: true,
      result: toCloneable(result),
      logs: logs,
    };
  }

  function executeScript(targetWindow, kind, script, params) {
    var names =
      kind === "route"
        ? ["route", "routeScript", "routeToSource"]
        : ["getParams", "parameterScript", "parameter"];

    var hasNamedEntry = names.some(function (name) {
      return (
        new RegExp("\\b(?:async\\s+)?function\\s+" + name + "\\s*\\(").test(
          script,
        ) ||
        new RegExp("\\b(?:const|let|var)\\s+" + name + "\\s*=").test(script)
      );
    });

    var runner = hasNamedEntry
      ? targetWindow.Function(
          "params",
          '"use strict";\n' +
            script +
            "\nconst __entry =\n" +
            names
              .map(function (name) {
                return (
                  '  typeof ' +
                  name +
                  ' === "function" ? ' +
                  name +
                  " :"
                );
              })
              .join("\n") +
            '\n  null;\nif (!__entry) throw new Error("Script entry function not found");\nreturn Promise.resolve(__entry(params));',
        )
      : targetWindow.Function(
          "params",
          '"use strict"; return Promise.resolve((async () => {\n' +
            script +
            "\n})());",
        );

    return runner(params);
  }

  function loadHtml(html) {
    var parsed = new DOMParser().parseFromString(html, "text/html");
    var nextHead = document.importNode(parsed.head, true);
    var nextBody = document.importNode(parsed.body, true);

    document.head.replaceChildren.apply(document.head, nextHead.childNodes);
    document.body.replaceChildren.apply(document.body, nextBody.childNodes);
  }

  function toCloneable(value) {
    try {
      structuredClone(value);
      return value;
    } catch (_error) {
      return String(value);
    }
  }

  function captureConsole(logs) {
    var methods = ["log", "info", "warn", "error", "debug"];
    var original = {};

    methods.forEach(function (level) {
      original[level] = console[level];
      console[level] = function () {
        var args = Array.prototype.slice.call(arguments);

        logs.push({
          level: level,
          args: args.map(formatConsoleValue),
        });

        return original[level].apply(console, args);
      };
    });

    return function restoreConsole() {
      methods.forEach(function (level) {
        console[level] = original[level];
      });
    };
  }

  function formatConsoleValue(value) {
    if (typeof value === "string") return value;
    if (typeof value === "undefined") return "undefined";
    if (typeof value === "function") {
      return value.name ? "[Function " + value.name + "]" : "[Function]";
    }

    try {
      return JSON.stringify(value, null, 2);
    } catch (_error) {
      return String(value);
    }
  }

  function formatError(error) {
    if (error && error.stack) return error.stack;
    if (error && error.message) return error.message;
    return String(error);
  }
})();
