type HashLike = {
  update(input: string | Uint8Array, encoding?: string): HashLike;
  digest(encoding?: 'hex'): string | Uint8Array;
};

function toBytes(input: string | Uint8Array, encoding?: string): Uint8Array {
  if (input instanceof Uint8Array) return input;
  if (encoding && encoding !== 'utf8' && encoding !== 'utf-8') {
    throw new Error(`[director/browser-shim] Unsupported hash encoding: ${encoding}`);
  }
  return new TextEncoder().encode(input);
}

function simpleHash(bytes: Uint8Array): string {
  let h1 = 0x811c9dc5;
  let h2 = 0x01000193;
  for (const byte of bytes) {
    h1 ^= byte;
    h1 = Math.imul(h1, 0x01000193);
    h2 ^= byte;
    h2 = Math.imul(h2, 0x27d4eb2d);
  }
  const a = (h1 >>> 0).toString(16).padStart(8, '0');
  const b = (h2 >>> 0).toString(16).padStart(8, '0');
  return `${a}${b}${a}${b}${a}${b}${a}${b}`;
}

export function createHash(_algorithm = 'sha256'): HashLike {
  const chunks: Uint8Array[] = [];
  return {
    update(input: string | Uint8Array, encoding?: string): HashLike {
      chunks.push(toBytes(input, encoding));
      return this;
    },
    digest(encoding?: 'hex'): string | Uint8Array {
      const size = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
      const merged = new Uint8Array(size);
      let offset = 0;
      for (const chunk of chunks) {
        merged.set(chunk, offset);
        offset += chunk.length;
      }
      const hex = simpleHash(merged);
      if (!encoding || encoding === 'hex') return hex;
      return new TextEncoder().encode(hex);
    },
  };
}

export function randomBytes(size: number): Uint8Array {
  const out = new Uint8Array(size);
  const nativeCrypto = globalThis.crypto;
  if (nativeCrypto?.getRandomValues) {
    nativeCrypto.getRandomValues(out);
    return out;
  }
  for (let i = 0; i < out.length; i += 1) {
    out[i] = Math.floor(Math.random() * 256);
  }
  return out;
}

export function randomUUID(): string {
  const nativeCrypto = globalThis.crypto;
  if (nativeCrypto?.randomUUID) return nativeCrypto.randomUUID();
  const bytes = randomBytes(16);
  bytes[6] = (bytes[6] & 0x0f) | 0x40;
  bytes[8] = (bytes[8] & 0x3f) | 0x80;
  const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0'));
  return [
    hex.slice(0, 4).join(''),
    hex.slice(4, 6).join(''),
    hex.slice(6, 8).join(''),
    hex.slice(8, 10).join(''),
    hex.slice(10, 16).join(''),
  ].join('-');
}

const shimCrypto = {
  createHash,
  randomBytes,
  randomUUID,
  getRandomValues<T extends ArrayBufferView | null>(array: T): T {
    if (array && 'byteLength' in array) {
      const view = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
      view.set(randomBytes(view.length));
    }
    return array;
  },
};

export default shimCrypto;
