import { ungzip } from 'pako-esm2'

import {
  decompressAll,
  decompressChunkSlice,
} from './wasm/bgzf-wasm-inlined.js'

interface VirtualOffset {
  blockPosition: number
  dataPosition: number
}
interface Chunk {
  minv: VirtualOffset
  maxv: VirtualOffset
}

function hasGzipHeader(data: Uint8Array) {
  return data[0] === 0x1f && data[1] === 0x8b
}

function errorMessage(error: unknown) {
  return error instanceof Error ? error.message : `${error}`
}

async function decompressGzip(inputData: Uint8Array) {
  if (typeof DecompressionStream !== 'undefined') {
    const stream = new Blob([inputData as Uint8Array<ArrayBuffer>])
      .stream()
      .pipeThrough(new DecompressionStream('gzip'))
    return new Uint8Array(await new Response(stream).arrayBuffer())
  } else {
    return ungzip(inputData, undefined)
  }
}

function wrapGzipHeaderError(error: unknown) {
  if (errorMessage(error).includes('invalid gzip header')) {
    return new Error(
      'problem decompressing block: incorrect gzip header check',
      { cause: error },
    )
  }
  return error
}

export async function unzip(inputData: Uint8Array) {
  try {
    return await decompressAll(inputData)
  } catch (error) {
    const message = errorMessage(error)
    if (message.includes('invalid bgzf header')) {
      if (hasGzipHeader(inputData)) {
        return decompressGzip(inputData)
      }
      throw new Error(
        'problem decompressing block: not a valid bgzf or gzip block',
        { cause: error },
      )
    }
    throw wrapGzipHeaderError(error)
  }
}

export async function unzipChunkSlice(inputData: Uint8Array, chunk: Chunk) {
  const { minv, maxv } = chunk
  try {
    const result = await decompressChunkSlice(
      inputData,
      minv.blockPosition,
      minv.dataPosition,
      maxv.blockPosition,
      maxv.dataPosition,
    )
    return {
      buffer: result.buffer,
      cpositions: result.cpositions,
      dpositions: result.dpositions,
    }
  } catch (error) {
    throw wrapGzipHeaderError(error)
  }
}
