'use strict';

importScripts('../bitmap/selection.js');
const BitmapSelection = self.PyroBitmapSelection;
if (!BitmapSelection) throw new Error('PYRO-Bitmapmodul konnte nicht geladen werden');

const bitmapCache = new Map();
const bitmapCacheByteBudget = 64 * 1024 * 1024;
let bitmapCacheBytes = 0;
let currentJob = null;

function cancelCurrentJob() {
  if (!currentJob || currentJob.cancelled) return;
  currentJob.cancelled = true;
  postMessage({ id: currentJob.id, cancelled: true });
}

function rememberBitmap(key, width, height, buffer) {
  if (!key || !(buffer instanceof ArrayBuffer)) return;
  const objectKey = String(key).split(':').slice(0, -1).join(':');
  [...bitmapCache].forEach(([cachedKey, entry]) => {
    const cachedObjectKey = String(cachedKey).split(':').slice(0, -1).join(':');
    if (cachedKey !== key && cachedObjectKey === objectKey) {
      bitmapCache.delete(cachedKey); bitmapCacheBytes -= entry.bytes;
    }
  });
  const existing = bitmapCache.get(key);
  if (existing) { bitmapCache.delete(key); bitmapCacheBytes -= existing.bytes; }
  const pixels = new Uint8ClampedArray(buffer); const entry = { width, height, pixels, bytes: pixels.byteLength };
  bitmapCache.set(key, entry); bitmapCacheBytes += entry.bytes;
  while (bitmapCache.size > 1 && bitmapCacheBytes > bitmapCacheByteBudget) {
    const oldestKey = bitmapCache.keys().next().value; const oldest = bitmapCache.get(oldestKey);
    bitmapCache.delete(oldestKey); bitmapCacheBytes -= oldest?.bytes || 0;
  }
}

function cachedBitmap(key) {
  const bitmap = bitmapCache.get(key);
  if (!bitmap) return null;
  bitmapCache.delete(key); bitmapCache.set(key, bitmap);
  return bitmap;
}

function fail(id, message) {
  postMessage({ id, error: String(message || 'Bitmapberechnung fehlgeschlagen') });
}

function finish(job, result, transfers = []) {
  if (job.cancelled) return;
  if (currentJob === job) currentJob = null;
  postMessage({ id: job.id, result }, transfers);
}

function startWandJob(message) {
  const bitmap = cachedBitmap(message.bitmapKey);
  if (!bitmap) { fail(message.id, 'Bitmapdaten fehlen im Worker'); return; }
  const width = bitmap.width; const height = bitmap.height;
  const seedX = Math.max(0, Math.min(width - 1, Math.round(Number(message.seed?.x) || 0)));
  const seedY = Math.max(0, Math.min(height - 1, Math.round(Number(message.seed?.y) || 0)));
  const tolerance = Math.max(0, Math.min(100, Math.round(Number(message.tolerance) || 0)));
  const pixels = bitmap.pixels; const pixelCount = width * height;
  const start = seedY * width + seedX; const startOffset = start * 4;
  const target = [pixels[startOffset], pixels[startOffset + 1], pixels[startOffset + 2], pixels[startOffset + 3]];
  const mask = new Uint8Array(pixelCount); const queue = new Int32Array(pixelCount);
  mask[start] = 1; queue[0] = start;
  const job = {
    id: message.id, kind: 'wand', cancelled: false, width, height, pixels, target, tolerance, mask, queue,
    head: 0, tail: 1, minX: seedX, maxX: seedX, minY: seedY, maxY: seedY
  };
  currentJob = job;

  const matches = (index) => {
    const offset = index * 4;
    return Math.max(
      Math.abs(job.pixels[offset] - job.target[0]),
      Math.abs(job.pixels[offset + 1] - job.target[1]),
      Math.abs(job.pixels[offset + 2] - job.target[2]),
      Math.abs(job.pixels[offset + 3] - job.target[3])
    ) <= job.tolerance;
  };
  const enqueue = (index) => {
    if (index < 0 || index >= pixelCount || job.mask[index] || !matches(index)) return;
    job.mask[index] = 1; job.queue[job.tail++] = index;
  };
  const step = () => {
    if (job.cancelled) return;
    const stop = Math.min(job.tail, job.head + 50000);
    while (job.head < stop) {
      const index = job.queue[job.head++]; const x = index % width; const y = Math.floor(index / width);
      job.minX = Math.min(job.minX, x); job.maxX = Math.max(job.maxX, x); job.minY = Math.min(job.minY, y); job.maxY = Math.max(job.maxY, y);
      if (x > 0) enqueue(index - 1);
      if (x + 1 < width) enqueue(index + 1);
      if (y > 0) enqueue(index - width);
      if (y + 1 < height) enqueue(index + width);
    }
    if (job.head < job.tail) { setTimeout(step, 0); return; }
    finish(job, {
      x: job.minX, y: job.minY, width: job.maxX - job.minX + 1, height: job.maxY - job.minY + 1,
      sourceWidth: width, sourceHeight: height, count: job.tail, mask: job.mask.buffer
    }, [job.mask.buffer]);
  };
  step();
}

function startLassoJob(message) {
  const dimensions = message.dimensions || {};
  const sourceWidth = Math.max(1, Math.round(Number(dimensions.width) || 1));
  const sourceHeight = Math.max(1, Math.round(Number(dimensions.height) || 1));
  const points = (Array.isArray(message.points) ? message.points : []).map((point) => ({ x: Math.round(Number(point.x) || 0), y: Math.round(Number(point.y) || 0) }));
  if (points.length < 3) { finish({ id: message.id, cancelled: false }, { count: 0 }); return; }
  const minX = Math.max(0, Math.min(sourceWidth - 1, Math.floor(Math.min(...points.map((point) => point.x)))));
  const maxX = Math.max(0, Math.min(sourceWidth - 1, Math.ceil(Math.max(...points.map((point) => point.x)))));
  const minY = Math.max(0, Math.min(sourceHeight - 1, Math.floor(Math.min(...points.map((point) => point.y)))));
  const maxY = Math.max(0, Math.min(sourceHeight - 1, Math.ceil(Math.max(...points.map((point) => point.y)))));
  const width = Math.max(1, maxX - minX + 1); const height = Math.max(1, maxY - minY + 1);
  const mask = new Uint8Array(width * height);
  const job = { id: message.id, kind: 'lasso', cancelled: false, points, minX, minY, width, height, mask, index: 0, count: 0 };
  currentJob = job;
  const step = () => {
    if (job.cancelled) return;
    const stop = Math.min(mask.length, job.index + 100000);
    while (job.index < stop) {
      const index = job.index++; const x = index % width; const y = Math.floor(index / width);
      if (!BitmapSelection.pointInPolygon(minX + x + .5, minY + y + .5, points)) continue;
      mask[index] = 1; job.count += 1;
    }
    if (job.index < mask.length) { setTimeout(step, 0); return; }
    finish(job, { x: minX, y: minY, width, height, count: job.count, mask: mask.buffer }, [mask.buffer]);
  };
  step();
}

self.onmessage = ({ data: message = {} }) => {
  if (message.type === 'cancel') { cancelCurrentJob(); return; }
  if (message.bitmapBuffer) rememberBitmap(message.bitmapKey, message.width, message.height, message.bitmapBuffer);
  cancelCurrentJob();
  if (message.type === 'wand') startWandJob(message);
  else if (message.type === 'lasso') startLassoJob(message);
  else fail(message.id, `Unbekannte Berechnung: ${message.type}`);
};
