import { NotFoundError } from 'interface-store'
import { BaseBlockstore } from './base.ts'
import type { AbortOptions } from 'abort-error'
import type { Blockstore, Pair } from 'interface-blockstore'
import type { CID } from 'multiformats/cid'

// https://github.com/multiformats/multicodec/blob/d06fc6194710e8909bac64273c43f16b56ca4c34/table.csv#L2
const IDENTITY_CODEC = 0x00

class IdentityHashDigestTooLongError extends Error {
  static name = 'IdentityHashDigestTooLongError'
  name = 'IdentityHashDigestTooLongError'
}

export interface IdentityBlockstoreInit {
  maxDigestLength?: number
}

export class IdentityBlockstore extends BaseBlockstore {
  private readonly child?: Blockstore
  private readonly maxDigestLength?: number

  constructor (child?: Blockstore, init?: IdentityBlockstoreInit) {
    super()

    this.child = child
    this.maxDigestLength = init?.maxDigestLength
  }

  put (key: CID, block: Uint8Array | Iterable<Uint8Array> | AsyncIterable<Uint8Array>, options?: AbortOptions): CID | Promise<CID> {
    if (key.multihash.code === IDENTITY_CODEC) {
      if (this.maxDigestLength != null && key.multihash.digest.byteLength > this.maxDigestLength) {
        throw new IdentityHashDigestTooLongError(`Identity digest too long - ${key.multihash.digest.byteLength} > this.maxDigestLength`)
      }

      options?.signal?.throwIfAborted()
      return key
    }

    if (this.child == null) {
      options?.signal?.throwIfAborted()
      return key
    }

    return this.child.put(key, block, options)
  }

  async * get (key: CID, options?: AbortOptions): AsyncGenerator<Uint8Array> {
    if (key.multihash.code === IDENTITY_CODEC) {
      if (this.maxDigestLength != null && key.multihash.digest.byteLength > this.maxDigestLength) {
        throw new IdentityHashDigestTooLongError(`Identity digest too long - ${key.multihash.digest.byteLength} > this.maxDigestLength`)
      }

      options?.signal?.throwIfAborted()
      yield key.multihash.digest
      return
    }

    if (this.child == null) {
      options?.signal?.throwIfAborted()
      throw new NotFoundError()
    }

    yield * this.child.get(key, options)
  }

  has (key: CID, options?: AbortOptions): boolean | Promise<boolean> {
    if (key.multihash.code === IDENTITY_CODEC) {
      if (this.maxDigestLength != null && key.multihash.digest.byteLength > this.maxDigestLength) {
        throw new IdentityHashDigestTooLongError(`Identity digest too long - ${key.multihash.digest.byteLength} > this.maxDigestLength`)
      }

      options?.signal?.throwIfAborted()
      return true
    }

    if (this.child == null) {
      options?.signal?.throwIfAborted()
      return false
    }

    return this.child.has(key, options)
  }

  delete (key: CID, options?: AbortOptions): void | Promise<void> {
    if (key.code === IDENTITY_CODEC) {
      if (this.maxDigestLength != null && key.multihash.digest.byteLength > this.maxDigestLength) {
        throw new IdentityHashDigestTooLongError(`Identity digest too long - ${key.multihash.digest.byteLength} > this.maxDigestLength`)
      }

      options?.signal?.throwIfAborted()
      return
    }

    if (this.child != null) {
      return this.child.delete(key, options)
    }
  }

  async * getAll (options?: AbortOptions): AsyncGenerator<Pair> {
    if (this.child != null) {
      yield * this.child.getAll(options)
    }

    options?.signal?.throwIfAborted()
  }
}
