importScripts("/workerware/WWError.js");
const dbg = console.log.bind(console, "[WorkerWare]");
const time = console.time.bind(console, "[WorkerWare]");
const timeEnd = console.timeEnd.bind(console, "[WorkerWare]");

/*
  OPTS:
    debug - Enables debug logging.
    randomNames - Generate random names for middlewares.
    timing - Logs timing for each middleware.
*/

const defaultOpt = {
  debug: false,
  randomNames: false,
  timing: false,
};

const validEvents = [
  "abortpayment",
  "activate",
  "backgroundfetchabort",
  "backgroundfetchclick",
  "backgroundfetchfail",
  "backgroundfetchsuccess",
  "canmakepayment",
  "contentdelete",
  "cookiechange",
  "fetch",
  "install",
  "message",
  "messageerror",
  "notificationclick",
  "notificationclose",
  "paymentrequest",
  "periodicsync",
  "push",
  "pushsubscriptionchange",
  "sync",
];

class WorkerWare {
  constructor(opt) {
    this._opt = Object.assign({}, defaultOpt, opt);
    this._middlewares = [];
  }
  info() {
    return {
      version: "0.1.0",
      middlewares: this._middlewares,
      options: this._opt,
    };
  }
  use(middleware) {
    let validateMW = this.validateMiddleware(middleware);
    if (validateMW.error) throw new WWError(validateMW.error);
    // This means the middleware is an anonymous function, or the user is silly and named their function "function"
    if (middleware.function.name == "function") middleware.name = crypto.randomUUID();
    if (!middleware.name) middleware.name = middleware.function.name;
    if (this._opt.randomNames) middleware.name = crypto.randomUUID();
    if (this._opt.debug) dbg("Adding middleware:", middleware.name);
    this._middlewares.push(middleware);
  }
  // Run all middlewares for the event type passed in.
  run(event) {
    const middlewares = this._middlewares;
    const returnList = [];
    let fn = async () => {
      for (let i = 0; i < middlewares.length; i++) {
        if (middlewares[i].events.includes(event.type)) {
          if (this._opt.timing) console.time(middlewares[i].name);
          // Add the configuration to the event object.
          event.workerware = {
            config: middlewares[i].configuration || {},
          };
          if (!middlewares[i].explicitCall) {
            let res = await middlewares[i].function(event);
            if (this._opt.timing) console.timeEnd(middlewares[i].name);
            returnList.push(res);
          }
        }
      }
      return returnList;
    };
    return fn;
  }
  deleteByName(middlewareID) {
    if (this._opt.debug) dbg("Deleting middleware:", middlewareID);
    this._middlewares = this._middlewares.filter((mw) => mw.name !== middlewareID);
  }
  deleteByEvent(middlewareEvent) {
    if (this._opt.debug) dbg("Deleting middleware by event:", middlewareEvent);
    this._middlewares = this._middlewares.filter((mw) => !mw.events.includes(middlewareEvent));
  }
  get() {
    return this._middlewares;
  }
  /*  
    Run a single middleware by ID.
    This assumes that the user knows what they're doing, and is running the middleware on an event that it's supposed to run on.
  */
  runMW(name, event) {
    const middlewares = this._middlewares;
    if (this._opt.debug) dbg("Running middleware:", name);
    // if (middlewares.includes(name)) {
    //   return middlewares[name](event);
    // } else {
    //   throw new WWError("Middleware not found!");
    // }
    let didCall = false;
    for (let i = 0; i < middlewares.length; i++) {
      if (middlewares[i].name == name) {
        didCall = true;
        event.workerware = {
          config: middlewares[i].configuration || {},
        }
        if (this._opt.timing) console.time(middlewares[i].name);
        let call = middlewares[i].function(event);
        if (this._opt.timing) console.timeEnd(middlewares[i].name);
        return call;
      }
    }
    if (!didCall) {
      throw new WWError("Middleware not found!");
    }
  }
  // type middlewareManifest = {
  //     function: Function,
  //     name?: string,
  //     events: string[], // Should be a union of validEvents.
  //     configuration?: Object // Optional configuration for the middleware.
  // }
  validateMiddleware(middleware) {
    if (!middleware.function)
      return {
        error: "middleware.function is required",
      };
    if (typeof middleware.function !== "function")
      return {
        error: "middleware.function must be typeof function",
      };
    if (typeof middleware.configuration !== "object" && middleware.configuration !== undefined) {
      return {
        error: "middleware.configuration must be typeof object",
      };
    }
    if (!middleware.events)
      return {
        error: "middleware.events is required",
      };
    if (!Array.isArray(middleware.events))
      return {
        error: "middleware.events must be an array",
      };
    if (middleware.events.some((ev) => !validEvents.includes(ev)))
      return {
        error: "Invalid event type! Must be one of the following: " + validEvents.join(", "),
      };
    if (middleware.explicitCall && typeof middleware.explicitCall !== "boolean") {
      return {
        error: "middleware.explicitCall must be typeof boolean",
      };
    }
    return {
      error: undefined,
    };
  }
}
