/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env webworker */
// eslint-disable-next-line mozilla/reject-import-system-module-from-non-system
import { Color } from "resource://gre/modules/Color.sys.mjs";
import { PromiseWorker } from "resource://gre/modules/workers/PromiseWorker.mjs";

/**
 * Worker for calculating the average relative luminance of an image
 * off the main thread.
 */
export class WallpaperThemeWorker {
  /**
   * @function calculateTheme
   * @param {{ blob: Blob }} options - Options object containing the image blob.
   * @param {Blob} options.blob - The image file blob to analyze.
   * @returns {Promise<"dark"|"light">} A promise that resolves to "dark" if the
   * average luminance is below the contrast threshold, otherwise "light".
   */
  async calculateTheme(blob) {
    let totalLuminance = 0;
    let count = 0;
    // Create an offscreen image bitmap
    const bitmap = await globalThis.createImageBitmap(blob);
    const scale = Math.min(1, 256 / Math.max(bitmap.width, bitmap.height));
    const width = Math.round(bitmap.width * scale);
    const height = Math.round(bitmap.height * scale);

    // Draw to an off-screen canvas
    const canvas = new OffscreenCanvas(width, height);
    const ctx = canvas.getContext("2d");
    ctx.drawImage(bitmap, 0, 0, width, height);

    // get pixel data
    const { data } = ctx.getImageData(0, 0, width, height);

    // The +=1 in these loops means that it will look at every pixel
    for (let row = 0; row < height; row += 1) {
      for (let column = 0; column < width; column += 1) {
        const index = (row * width + column) * 4;
        const alpha = data[index + 3];
        // Skip transparent pixels
        if (alpha > 0) {
          const red = data[index];
          const green = data[index + 1];
          const blue = data[index + 2];
          const luminance = new Color(red, green, blue).relativeLuminance;
          totalLuminance += luminance;
          count++;
        }
      }
    }
    const averageLuminance = totalLuminance / count;

    // Threshold taken from Color.sys.mjs module
    const CONTRAST_BRIGHTTEXT_THRESHOLD = Math.sqrt(1.05 * 0.05) - 0.05;
    return averageLuminance <= CONTRAST_BRIGHTTEXT_THRESHOLD ? "dark" : "light";
  }
}

// Promise worker boiler plate
const wallpaperWorker = new WallpaperThemeWorker();
const worker = new PromiseWorker.AbstractWorker();
worker.dispatch = function (method, args = []) {
  return wallpaperWorker[method](...args);
};
worker.postMessage = function (message, ...transfers) {
  self.postMessage(message, ...transfers);
};
worker.close = function () {
  self.close();
};
self.addEventListener("message", msg => worker.handleMessage(msg));
self.addEventListener("unhandledrejection", function (error) {
  throw error.reason;
});
