// src/lib/deck.gl/DougalBinaryLoader.js
import { LoaderObject } from '@loaders.gl/core';
import { DougalBinaryBundle } from '@dougal/binary';

async function cachedFetch(url, init, opts = {}) {
    let res; // The response
    let cache; // Potentially, a Cache API cache name
    let isCached;

    if (opts?.cache === true) {
      opts.cache = { name: "dougal" };
    } else if (typeof opts?.cache === "string") {
      opts.cache = { name: opts.cache };
    } else if (opts?.cache) {
      if (!(opts.cache instanceof Object)) {
        opts.cache = { name: "dougal" }
      } else if (!(opts.cache.name)) {
        opts.cache.name = "dougal";
      }
    }

    if (opts?.cache && window.cache) {
      cache = await caches.open(opts.cache.name);
      res = await cache.match(url);
      isCached = !!res;
    }

    if (!res) {
      res = await fetch(url, init);
    }

    if (cache && !isCached) {
      cache.put(url, res.clone());
    }

    return res;
}

const DougalBinaryLoader = {
  name: 'DougalBinaryBundle Loader',
  extensions: ['dbb'],
  mimeTypes: ['application/vnd.aaltronav.dougal+octet-stream'],
  parse: async (input, options) => {
    let arrayBuffer;
    if (typeof input === 'string') {
      // Input is URL, fetch with caching
      const response = await cachedFetch(input, options?.fetch, options);
      if (!response.ok) {
        throw new Error(`Failed to fetch: ${response.statusText}`);
      }
      arrayBuffer = await response.arrayBuffer();
    } else if (input instanceof ArrayBuffer) {
      arrayBuffer = input;
    } else {
      throw new Error('Invalid input: Expected URL string or ArrayBuffer');
    }

    const bundle = DougalBinaryBundle.clone(arrayBuffer);

    // Calculate total points
    const totalCount = bundle.chunks().reduce((acc, chunk) => acc + chunk.jCount, 0);

    // Prepare positions (Float32Array: [lon1, lat1, lon2, lat2, ...])
    const positions = new Float32Array(totalCount * 2);

    // Extract udv (assume constant across chunks)
    const udv = bundle.chunks()[0].udv;

    // Prepare values as an array of TypedArrays
    const ΔelemCount = bundle.chunks()[0].ΔelemCount;
    const elemCount = bundle.chunks()[0].elemCount;
    const values = new Array(ΔelemCount + elemCount + 2);

    // Initialize values arrays with correct types
    if (udv == 0) {
      for (let k = 0; k < values.length; k++) {
        values[k] = new (k === 0 ? Uint16Array : k === 1 ? Uint32Array : Uint8Array)(totalCount);
      }
    } else if (udv == 1) {
      for (let k = 0; k < values.length; k++) {
        values[k] = new (k === 0 ? Uint16Array : k === 1 ? Uint32Array : k === 2 ? Uint8Array : Uint16Array)(totalCount);
      }
    } else if (udv == 2) {
      for (let k = 0; k < values.length; k++) {
        values[k] = new (k === 0 ? Uint16Array : k === 1 ? Uint32Array : k === 2 ? BigUint64Array : Float32Array)(totalCount);
      }
    } else {
      throw new Error(`Invalid udv: Expected 0, 1, or 2; found ${udv}`);
    }

    let offset = 0;
    for (const chunk of bundle.chunks()) {
      const λarray = chunk.elem(0);
      const φarray = chunk.elem(1);
      for (let i = 0; i < λarray.length; i++) {
        positions[offset * 2 + i * 2] = λarray[i];
        positions[offset * 2 + i * 2 + 1] = φarray[i];
      }

      values[0].set(new Uint16Array(chunk.jCount).fill(chunk.i), offset);
      values[1].set(Uint32Array.from({ length: chunk.jCount }, (_, i) => chunk.j0 + i * chunk.Δj), offset);

      for (let j = 0; j < ΔelemCount; j++) {
        values[2 + j].set(chunk.Δelem(j), offset);
      }
      for (let j = 2; j < elemCount; j++) {
        values[2 + ΔelemCount + j - 2].set(chunk.elem(j), offset);
      }

      offset += chunk.jCount;
    }

    console.log(`Parsed ${totalCount} points, ${values.length} value arrays`);

    const attributes = {
      getPosition: {
        value: positions,
        type: 'float32',
        size: 2
      },
      udv
    };

    values.forEach((valArray, k) => {
      let value = valArray;
      if (valArray instanceof BigUint64Array) {
        value = Float64Array.from(valArray, v => Number(v));
      }
      attributes[`value${k}`] = {
        value,
        type: value instanceof Float64Array ? 'float64' :
              value instanceof Uint16Array ? 'uint16' :
              value instanceof Uint32Array ? 'uint32' : 'float32',
        size: 1
      };
    });

    return {
      length: totalCount,
      attributes
    };
  },
  options: {} // Optional: Add custom options if needed
};

export default DougalBinaryLoader;
