/**
 * services/hash.js
 * pHash for image + video frames via sharp.
 *
 * Strategy: 32x32 grayscale resize → mean of 8x8 average → bit per pixel
 *           above/below mean → 64-bit hash.
 *
 * sharp's built-in: not available, so we implement a lightweight perceptual hash.
 */

'use strict';

const crypto = require('crypto');
const sharp  = require('sharp');

const HASH_BITS = 64;

/**
 * Generate a 64-bit pHash from a buffer (or base64 string).
 * Returns lowercase hex (16 chars).
 */
async function phash(input) {
  const buf = await toBuffer(input);
  const { data, info } = await sharp(buf)
    .resize(32, 32, { fit: 'fill' })
    .greyscale()
    .raw()
    .toBuffer({ resolveWithObject: true });

  // Downscale to 8x8 by averaging 4x4 blocks
  const small = new Float32Array(64);
  for (let y = 0; y < 8; y++) {
    for (let x = 0; x < 8; x++) {
      let sum = 0;
      for (let dy = 0; dy < 4; dy++) {
        for (let dx = 0; dx < 4; dx++) {
          const px = (y * 4 + dy) * info.width + (x * 4 + dx);
          sum += data[px];
        }
      }
      small[y * 8 + x] = sum / 16;
    }
  }
  // mean
  let mean = 0;
  for (let i = 0; i < 64; i++) mean += small[i];
  mean /= 64;
  // bits
  let bits = '';
  for (let i = 0; i < 64; i++) bits += small[i] > mean ? '1' : '0';
  // convert to hex
  let hex = '';
  for (let i = 0; i < 64; i += 4) {
    hex += parseInt(bits.slice(i, i + 4), 2).toString(16);
  }
  return hex;
}

async function toBuffer(input) {
  if (Buffer.isBuffer(input)) return input;
  if (typeof input === 'string') {
    if (input.startsWith('data:')) {
      const b64 = input.split(',', 2)[1] || '';
      return Buffer.from(b64, 'base64');
    }
    // assume base64
    return Buffer.from(input, 'base64');
  }
  throw new Error('hash: unsupported input type');
}

/**
 * Hash an array of base64-encoded frames.
 * Returns array of 64-bit hex hashes.
 */
async function phashFrames(framesB64) {
  const out = [];
  for (const f of framesB64) {
    try { out.push(await phash(f)); } catch { /* skip bad frames */ }
  }
  return out;
}

/**
 * Convenience for tests — Hamming distance between two hex hashes.
 */
function hamming(a, b) {
  if (!a || !b || a.length !== b.length) return Infinity;
  let dist = 0;
  for (let i = 0; i < a.length; i += 2) {
    const x = parseInt(a.slice(i, i + 2), 16) ^ parseInt(b.slice(i, i + 2), 16);
    while (x) { dist += x & 1; x >>= 1; }
  }
  return dist;
}

module.exports = { phash, phashFrames, hamming };
