"use strict";

const pull = require('../server/node_modules/pull-stream');

const NOTE_NAMES = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];
const OCTAVES = [3, 4, 5];

const TYPE_TO_DEGREE = {
  post: 0,
  vote: 2,
  about: 4,
  contact: 5,
  forum: 7,
  tribe: 9,
  transfer: 11,
  shop: 1,
  shopProduct: 1,
  job: 3,
  project: 6,
  event: 8,
  task: 10,
  bookmark: 4,
  feed: 5,
  pad: 6,
  chat: 7,
  audio: 8,
  video: 9,
  image: 10,
  document: 11,
  torrent: 0,
  map: 2,
  pixelia: 3,
  gameScore: 5,
  votes: 7,
  calendar: 8,
  curriculum: 9,
  report: 10,
  parliament: 11,
  courts: 0,
  market: 1,
  aiExchange: 6,
  tombstone: 4
};

const NOTE_FREQS = (() => {
  const a4 = 440;
  const map = {};
  for (const oct of [2, 3, 4, 5, 6]) {
    for (let i = 0; i < NOTE_NAMES.length; i++) {
      const name = NOTE_NAMES[i] + oct;
      const semis = (oct - 4) * 12 + (i - 9);
      map[name] = a4 * Math.pow(2, semis / 12);
    }
  }
  return map;
})();

function blockToNote(msg) {
  const c = msg && msg.value && msg.value.content;
  if (!c || typeof c !== 'object') return null;
  const type = String(c.type || '').trim() || 'unknown';
  const degree = TYPE_TO_DEGREE[type] != null ? TYPE_TO_DEGREE[type] : (Math.abs(hashStr(type)) % 12);
  const size = Buffer.byteLength(JSON.stringify(msg.value), 'utf8');
  const octIdx = size < 256 ? 0 : (size < 1024 ? 1 : 2);
  const octave = OCTAVES[octIdx];
  const name = NOTE_NAMES[degree] + octave;
  const freq = NOTE_FREQS[name] || 440;
  const durMs = Math.min(600, 200 + Math.round(size / 64));
  return {
    id: msg.key,
    ts: msg.value.timestamp,
    type,
    size,
    name,
    freq,
    durMs
  };
}

function hashStr(s) {
  let h = 0;
  for (let i = 0; i < s.length; i++) h = ((h << 5) - h + s.charCodeAt(i)) | 0;
  return h;
}

module.exports = ({ cooler }) => {
  let ssb = null;
  const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; };

  async function getUserMelodyInternal(userId, limit = 200) {
    const client = await openSsb();
    const me = userId || client.id;
    const msgs = await new Promise((resolve, reject) => {
      pull(
        client.createUserStream({ id: me, reverse: true, limit }),
        pull.collect((err, rows) => err ? reject(err) : resolve(rows))
      );
    });
    const seq = msgs
      .filter(m => m && m.value && m.value.content && typeof m.value.content === 'object')
      .map(blockToNote)
      .filter(Boolean)
      .reverse();
    const stats = {};
    for (const n of seq) {
      stats[n.type] = (stats[n.type] || 0) + 1;
    }
    return { feedId: me, total: seq.length, sequence: seq, stats };
  }

  return {
    NOTE_NAMES,
    OCTAVES,
    TYPE_TO_DEGREE,
    getUserMelody: getUserMelodyInternal,
    embedTextInWav,
    extractTextFromWav
  };
};

const STEG_MAGIC = Buffer.from([0xBC, 0x53]);
const STEG_HEADER_BITS = 32;

function embedTextInWav(wavBuffer, text) {
  if (!Buffer.isBuffer(wavBuffer) || wavBuffer.length < 44) return wavBuffer;
  const payload = Buffer.from(String(text || ''), 'utf8');
  if (payload.length === 0) return wavBuffer;
  if (payload.length > 4096) return wavBuffer;
  const lenBuf = Buffer.alloc(2);
  lenBuf.writeUInt16BE(payload.length, 0);
  const fullData = Buffer.concat([STEG_MAGIC, lenBuf, payload]);
  const totalBits = fullData.length * 8;
  const dataLen = wavBuffer.readUInt32LE(40);
  const numSamples = Math.floor(dataLen / 2);
  if (totalBits > numSamples) return wavBuffer;
  const out = Buffer.from(wavBuffer);
  for (let i = 0; i < totalBits; i++) {
    const byteIdx = i >> 3;
    const bitIdx = i & 7;
    const bit = (fullData[byteIdx] >> (7 - bitIdx)) & 1;
    const sampleOffset = 44 + (i * 2);
    let sample = out.readInt16LE(sampleOffset);
    sample = (sample & ~1) | bit;
    out.writeInt16LE(sample, sampleOffset);
  }
  return out;
}

function readBitsAsBuffer(wavBuffer, startBit, numBits) {
  const bytes = Math.ceil(numBits / 8);
  const out = Buffer.alloc(bytes, 0);
  for (let i = 0; i < numBits; i++) {
    const sampleOffset = 44 + ((startBit + i) * 2);
    if (sampleOffset + 2 > wavBuffer.length) return null;
    const sample = wavBuffer.readInt16LE(sampleOffset);
    const bit = sample & 1;
    const byteIdx = i >> 3;
    const bitIdx = i & 7;
    out[byteIdx] |= bit << (7 - bitIdx);
  }
  return out;
}

function extractTextFromWav(wavBuffer) {
  if (!Buffer.isBuffer(wavBuffer) || wavBuffer.length < 44 + STEG_HEADER_BITS * 2) return null;
  if (wavBuffer.slice(0, 4).toString('ascii') !== 'RIFF') return null;
  if (wavBuffer.slice(8, 12).toString('ascii') !== 'WAVE') return null;
  const magic = readBitsAsBuffer(wavBuffer, 0, 16);
  if (!magic || magic[0] !== STEG_MAGIC[0] || magic[1] !== STEG_MAGIC[1]) return null;
  const lenBuf = readBitsAsBuffer(wavBuffer, 16, 16);
  if (!lenBuf) return null;
  const len = lenBuf.readUInt16BE(0);
  if (len === 0 || len > 4096) return null;
  const payload = readBitsAsBuffer(wavBuffer, 32, len * 8);
  if (!payload) return null;
  try {
    const text = payload.toString('utf8');
    if (!text) return null;
    return text;
  } catch (_) { return null; }
}
