export interface FaviconOverlayPosition {
  x: number;
  y: number;
  centered?: boolean;
}

const createLoadedImage = (imageSrc: string): Promise<HTMLImageElement> =>
  new Promise((resolve) => {
    const image = document.createElement("img");
    image.addEventListener("load", () => resolve(image), { once: true });
    image.src = imageSrc;
  });

const makeReactive = <T>(object: T): T => {
  const reactiveObject = {};
  Object.keys(object).forEach((key) => {
    Object.defineProperty(reactiveObject, key, {
      get() {
        return object[key];
      },
      set(value) {
        if (value == object[key]) {
          return;
        }
        requestAnimationFrame(FaviconOverlayManager.render);
        return (object[key] = value);
      },
    });
  });
  return reactiveObject as T;
};

class FaviconOverlay {
  public readonly image: HTMLImageElement;
  private _position: FaviconOverlayPosition = makeReactive<FaviconOverlayPosition>(
    {
      x: 0,
      y: 0,
      centered: false,
    }
  );

  get position(): FaviconOverlayPosition {
    return this._position;
  }

  set position(value: FaviconOverlayPosition) {
    Object.assign(this._position, value);
  }

  constructor(image: HTMLImageElement) {
    this.image = image;
  }
}

export class FaviconOverlayManager {
  private static canvas: HTMLCanvasElement = null;
  private static faviconElement: HTMLLinkElement = null;
  private static faviconImage: HTMLImageElement = null;
  private static overlays: FaviconOverlay[] = [];

  private constructor(public image: HTMLImageElement) {
    throw new Error("THIS BLOWS UP! DONT DO IT!");
  }

  public static setFaviconOverlay(imageUrl: string) {
    return createLoadedImage(imageUrl).then((overlayImage) => {
      FaviconOverlayManager.overlays = [new FaviconOverlay(overlayImage)];
      requestAnimationFrame(FaviconOverlayManager.render);
    });
  }

  public static resetFaviconOverlay() {
    FaviconOverlayManager.overlays = [];
    requestAnimationFrame(FaviconOverlayManager.render);
  }

  public static get isInitialized() {
    return Boolean(FaviconOverlayManager.canvas);
  }

  private static get faviconUrl() {
    const { faviconElement } = FaviconOverlayManager;
    const url = faviconElement.dataset.originalHref;
    if (url) {
      return url;
    }
    return (faviconElement.dataset.originalHref = faviconElement.href);
  }

  public static getOverlay(idx: number) {
    return FaviconOverlayManager.overlays[idx];
  }

  public static initialize({
    faviconSelector = 'link[rel="icon"]',
  } = {}): Promise<void> {
    if (FaviconOverlayManager.isInitialized) {
      return Promise.resolve();
    }

    FaviconOverlayManager.canvas = document.createElement("canvas");
    FaviconOverlayManager.faviconElement = document.querySelector(
      faviconSelector
    );

    return createLoadedImage(FaviconOverlayManager.faviconUrl).then((image) => {
      FaviconOverlayManager.canvas.width = image.width;
      FaviconOverlayManager.canvas.height = image.height;
      FaviconOverlayManager.faviconImage = image;
    });
  }

  public static render() {
    const {
      overlays,
      faviconImage,
      faviconElement,
      canvas,
    } = FaviconOverlayManager;

    if (!overlays.length) {
      faviconElement.href = faviconElement.dataset.originalHref;
      return;
    }

    const context = canvas.getContext("2d");
    context.clearRect(0, 0, faviconImage.width, faviconImage.height);
    context.drawImage(
      faviconImage,
      0,
      0,
      faviconImage.width,
      faviconImage.height,
      0,
      0,
      faviconImage.width,
      faviconImage.height
    );

    overlays.forEach((overlay) => {
      let { x, y } = overlay.position;

      if (overlay.position.centered) {
        x -= overlay.image.width / 2;
        y -= overlay.image.height / 2;
      }

      context.drawImage(
        overlay.image,
        0,
        0,
        overlay.image.width,
        overlay.image.height,
        x,
        y,
        overlay.image.width,
        overlay.image.height
      );
    });

    faviconElement.href = canvas.toDataURL();
  }
}
