import CramCodec from './_base.ts'
import { CramUnimplementedError } from '../../errors.ts'

import type { Cursor, Cursors } from './_base.ts'
import type { BetaEncoding } from '../encoding.ts'
import type { CramFileBlock } from '../file.ts'
import type CramSlice from '../slice/index.ts'

export default class BetaCodec extends CramCodec<
  'int',
  BetaEncoding['parameters']
> {
  constructor(parameters: BetaEncoding['parameters'], dataType: 'int') {
    super(parameters, dataType)
    if (this.dataType !== 'int') {
      throw new CramUnimplementedError(
        `${this.dataType} decoding not yet implemented by BETA codec`,
      )
    }
  }

  decode(
    _slice: CramSlice,
    coreDataBlock: CramFileBlock,
    _blocksByContentId: Record<number, CramFileBlock>,
    cursors: Cursors,
  ) {
    return decodeBetaInline(
      coreDataBlock.content,
      cursors.coreBlock,
      this.parameters.length,
      this.parameters.offset,
    )
  }
}

/**
 * Optimized beta decoder with inlined bit reading.
 */
function decodeBetaInline(
  data: Uint8Array,
  cursor: Cursor,
  numBits: number,
  offset: number,
): number {
  let { bytePosition, bitPosition } = cursor

  // Fast path: reading exactly 8 bits when byte-aligned
  if (numBits === 8 && bitPosition === 7) {
    const val = data[bytePosition]!
    cursor.bytePosition = bytePosition + 1
    return val - offset
  }

  // General case
  let val = 0
  for (let i = 0; i < numBits; i++) {
    val <<= 1
    val |= (data[bytePosition]! >> bitPosition) & 1
    bitPosition -= 1
    if (bitPosition < 0) {
      bytePosition += 1
      bitPosition = 7
    }
  }

  cursor.bytePosition = bytePosition
  cursor.bitPosition = bitPosition
  return val - offset
}
