// memory.js — in-process storage impl. Test seed.
// Map<docPath, data>. EventEmitter for subscribe.
// Mirrors firebase semantics: setDoc=replace, updateDoc=shallow merge, addDoc=auto-id.

'use strict';

import { EventEmitter } from 'events';

function createMemoryStorage() {
  const docs = new Map();              // path -> data obj
  const bus = new EventEmitter();
  bus.setMaxListeners(1000);

  // Firebase-prefixed paths (artifacts/{APP_ID}/public/data/...) normalized to
  // bare canonical. Matches ws.js norm() so all impls share path identity.
  function norm(p) {
    if (!p) return p;
    return p.replace(/^[\s\S]*\/public\/data\//, '');
  }

  // ---- path helpers ----
  // collection path = path with even number of segments OR known collection.
  // doc path = odd segments (coll/doc, coll/doc/subcoll/subdoc).
  // getCollection(path) returns all docs whose path === path/id for any single id segment.
  function isCollectionPath(p) {
    return p.split('/').length % 2 === 1;
  }

  function emitDoc(path, data) { bus.emit('doc:' + path, data); }
  function emitCollection(collPath) {
    const children = collectionDocs(collPath);
    bus.emit('coll:' + collPath, children);
  }

  function collectionDocs(collPath) {
    const out = [];
    const segLen = collPath.split('/').length + 1;
    for (const [p, data] of docs) {
      const segs = p.split('/');
      if (segs.length !== segLen) continue;
      const parent = segs.slice(0, -1).join('/');
      if (parent === collPath) out.push(data);
    }
    return out;
  }

  function genId() {
    return (typeof crypto !== 'undefined' && crypto.randomUUID)
      ? crypto.randomUUID()
      : `id_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
  }

  const storage = {
    async getDoc(rawPath) {
      const path = norm(rawPath);
      return docs.has(path) ? deepClone(docs.get(path)) : null;
    },

    async setDoc(rawPath, data) {
      const path = norm(rawPath);
      docs.set(path, deepClone(data));
      emitDoc(path, deepClone(data));
      const segs = path.split('/');
      if (segs.length >= 2) emitCollection(segs.slice(0, -1).join('/'));
    },

    async updateDoc(rawPath, patch) {
      const path = norm(rawPath);
      const existing = docs.has(path) ? docs.get(path) : {};
      const merged = { ...existing, ...patch };
      docs.set(path, merged);
      emitDoc(path, deepClone(merged));
      const segs = path.split('/');
      if (segs.length >= 2) emitCollection(segs.slice(0, -1).join('/'));
    },

    async deleteDoc(rawPath) {
      const path = norm(rawPath);
      docs.delete(path);
      emitDoc(path, null);
      const segs = path.split('/');
      if (segs.length >= 2) emitCollection(segs.slice(0, -1).join('/'));
    },

    async addDoc(rawCollectionPath, data) {
      const collectionPath = norm(rawCollectionPath);
      const id = genId();
      const path = `${collectionPath}/${id}`;
      docs.set(path, deepClone(data));
      emitDoc(path, deepClone(data));
      emitCollection(collectionPath);
      return { id, path };
    },

    async getCollection(rawCollPath) {
      const collPath = norm(rawCollPath);
      return collectionDocs(collPath).map(deepClone);
    },

    async batchWrite(ops) {
      for (const op of ops) {
        const mop = { ...op, path: norm(op.path) };
        if (mop.type === 'set') await storage.setDoc(mop.path, mop.data);
        else if (mop.type === 'delete') await storage.deleteDoc(mop.path);
        else if (mop.type === 'update') await storage.updateDoc(mop.path, mop.data);
      }
    },

    subscribeDoc(rawPath, cb) {
      const path = norm(rawPath);
      const cur = docs.has(path) ? deepClone(docs.get(path)) : null;
      Promise.resolve().then(() => cb(cur));
      const handler = (data) => cb(data);
      bus.on('doc:' + path, handler);
      return () => bus.off('doc:' + path, handler);
    },

    subscribeCollection(rawCollPath, cb) {
      const collPath = norm(rawCollPath);
      Promise.resolve().then(() => cb(collectionDocs(collPath).map(deepClone)));
      const handler = (docs) => cb(docs);
      bus.on('coll:' + collPath, handler);
      return () => bus.off('coll:' + collPath, handler);
    },

    dispose() { bus.removeAllListeners(); docs.clear(); },

    // test/debug
    _docs: docs,
  };

  return storage;
}

function deepClone(v) {
  if (v === null || v === undefined) return v;
  return JSON.parse(JSON.stringify(v));
}

export { createMemoryStorage };
