export const faviconSizes = [16, 32, 64, 180, 192, 512] as const;

export type GeneratedFavicon = {
  size: (typeof faviconSizes)[number];
  blob: Blob;
  previewUrl: string;
};

function canvasBlob(canvas: HTMLCanvasElement) {
  return new Promise<Blob>((resolve, reject) => canvas.toBlob((blob) => blob ? resolve(blob) : reject(new Error("favicon_canvas_failed")), "image/png"));
}

export async function generateFavicons(file: File): Promise<GeneratedFavicon[]> {
  const bitmap = await createImageBitmap(file);
  try {
    return await Promise.all(faviconSizes.map(async (size) => {
      const canvas = document.createElement("canvas");
      canvas.width = size;
      canvas.height = size;
      const context = canvas.getContext("2d");
      if (!context) throw new Error("favicon_context_failed");
      context.imageSmoothingEnabled = true;
      context.imageSmoothingQuality = "high";
      const scale = Math.min(size / bitmap.width, size / bitmap.height);
      const width = bitmap.width * scale;
      const height = bitmap.height * scale;
      context.drawImage(bitmap, (size - width) / 2, (size - height) / 2, width, height);
      const blob = await canvasBlob(canvas);
      return { size, blob, previewUrl: URL.createObjectURL(blob) };
    }));
  } finally {
    bitmap.close();
  }
}

export async function createIcoBlob(images: GeneratedFavicon[]) {
  const selected = images.filter((item) => item.size === 16 || item.size === 32 || item.size === 64);
  const payloads = await Promise.all(selected.map((item) => item.blob.arrayBuffer()));
  const headerSize = 6 + selected.length * 16;
  const totalSize = headerSize + payloads.reduce((sum, payload) => sum + payload.byteLength, 0);
  const buffer = new ArrayBuffer(totalSize);
  const view = new DataView(buffer);
  view.setUint16(0, 0, true);
  view.setUint16(2, 1, true);
  view.setUint16(4, selected.length, true);
  let offset = headerSize;
  selected.forEach((item, index) => {
    const entry = 6 + index * 16;
    const payload = payloads[index];
    view.setUint8(entry, item.size);
    view.setUint8(entry + 1, item.size);
    view.setUint8(entry + 2, 0);
    view.setUint8(entry + 3, 0);
    view.setUint16(entry + 4, 1, true);
    view.setUint16(entry + 6, 32, true);
    view.setUint32(entry + 8, payload.byteLength, true);
    view.setUint32(entry + 12, offset, true);
    new Uint8Array(buffer, offset, payload.byteLength).set(new Uint8Array(payload));
    offset += payload.byteLength;
  });
  return new Blob([buffer], { type: "image/x-icon" });
}

export type FaviconAssetUrls = { ico?: string; apple?: string; icon192?: string; icon512?: string };

export function applyFaviconAssets(assets?: FaviconAssetUrls) {
  if (!assets) return;
  const setLink = (id: string, rel: string, href?: string, sizes?: string) => {
    if (!href) return;
    let link = document.querySelector<HTMLLinkElement>(`link[data-nodedc-favicon="${id}"]`);
    if (!link) {
      link = document.createElement("link");
      link.dataset.nodedcFavicon = id;
      link.rel = rel;
      document.head.append(link);
    }
    link.href = href;
    if (sizes) link.sizes = sizes;
  };
  setLink("ico", "icon", assets.ico);
  setLink("apple", "apple-touch-icon", assets.apple, "180x180");
  setLink("192", "icon", assets.icon192, "192x192");
  setLink("512", "icon", assets.icon512, "512x512");
}
