import { applyModuleOverridesToModules } from "@open-mercato/shared/modules/overrides";
import { invalidateDictionaryCache, invalidateDictionaryCacheLocales } from "../i18n/dictionary-cache.js";
import { createLogger } from "../logger/index.js";
const logger = createLogger("shared").child({ component: "modules-registry" });
const GLOBAL_KEY = "__openMercatoModulesRegistry__";
const LISTENERS_GLOBAL_KEY = "__openMercatoModulesRegistryListeners__";
const SNAPSHOT_GLOBAL_KEY = "__openMercatoModulesRegistrySnapshot__";
const UNREADABLE_CONTRACT_ENTRY = /* @__PURE__ */ Symbol("open-mercato.modules.unreadable-contract-entry");
function registryGlobals() {
  return globalThis;
}
function getGlobalModules() {
  return registryGlobals()[GLOBAL_KEY] ?? null;
}
function setGlobalModules(modules) {
  registryGlobals()[GLOBAL_KEY] = modules;
}
function getListeners() {
  const globalScope = registryGlobals();
  const listeners = globalScope[LISTENERS_GLOBAL_KEY] ?? /* @__PURE__ */ new Set();
  globalScope[LISTENERS_GLOBAL_KEY] = listeners;
  return listeners;
}
function onModulesRegistered(listener) {
  const listeners = getListeners();
  listeners.add(listener);
  return () => {
    listeners.delete(listener);
  };
}
function snapshotModules(modules) {
  return modules.map((entry) => {
    const contract = Object.keys(entry).sort((a, b) => a < b ? -1 : a > b ? 1 : 0).map((key) => {
      const descriptor = Object.getOwnPropertyDescriptor(entry, key);
      if (!descriptor || !("value" in descriptor)) return [key, UNREADABLE_CONTRACT_ENTRY];
      const value = descriptor.value;
      return [key, Array.isArray(value) ? [...value] : value];
    });
    return [entry.id, contract];
  });
}
function contractValuesMatch(previous, next) {
  if (previous === UNREADABLE_CONTRACT_ENTRY || next === UNREADABLE_CONTRACT_ENTRY) return false;
  if (Array.isArray(previous) && Array.isArray(next)) {
    return previous.length === next.length && previous.every((item, index) => item === next[index]);
  }
  return previous === next;
}
function snapshotsMatch(previous, next) {
  if (previous === null || previous.length !== next.length) return false;
  return previous.every(([id, contract], index) => {
    const [nextId, nextContract] = next[index];
    if (id !== nextId || contract.length !== nextContract.length) return false;
    return contract.every(([key, value], contractIndex) => {
      const [nextKey, nextValue] = nextContract[contractIndex];
      return key === nextKey && contractValuesMatch(value, nextValue);
    });
  });
}
function isPromiseLike(value) {
  return typeof value?.then === "function";
}
function notifyModulesRegistered(modules) {
  for (const listener of [...getListeners()]) {
    try {
      const result = listener(modules);
      if (isPromiseLike(result)) {
        Promise.resolve(result).catch((err) => {
          logger.error("Module registration listener rejected", { err });
        });
      }
    } catch (err) {
      logger.error("Module registration listener failed", { err });
    }
  }
}
function hasRuntimeContracts(entry) {
  return Object.keys(entry).some((key) => key !== "id" && key !== "translations");
}
function isI18nOnlyRegistration(modules) {
  return modules.length > 0 && modules.every((entry) => !hasRuntimeContracts(entry));
}
function mergeI18nModules(existing, incoming) {
  const incomingById = new Map(incoming.map((entry) => [entry.id, entry]));
  const existingIds = new Set(existing.map((entry) => entry.id));
  const merged = existing.map((entry) => {
    const i18nModule = incomingById.get(entry.id);
    if (!i18nModule?.translations) return entry;
    return {
      ...entry,
      translations: {
        ...entry.translations ?? {},
        ...i18nModule.translations
      }
    };
  });
  for (const entry of incoming) {
    if (!existingIds.has(entry.id)) merged.push(entry);
  }
  return merged;
}
function getTranslationLocales(modules) {
  const locales = /* @__PURE__ */ new Set();
  for (const entry of modules) {
    for (const locale of Object.keys(entry.translations ?? {})) {
      locales.add(locale);
    }
  }
  return [...locales];
}
function preserveExistingTranslations(existing, incoming) {
  const existingById = new Map(existing.map((entry) => [entry.id, entry]));
  return incoming.map((entry) => {
    if (entry.translations) return entry;
    const translations = existingById.get(entry.id)?.translations;
    return translations ? { ...entry, translations } : entry;
  });
}
function registerModules(modules) {
  const existing = getGlobalModules();
  if (existing !== null && process.env.NODE_ENV === "development") {
    logger.debug("Modules re-registered (this may occur during HMR)");
  }
  const nextModules = applyModuleOverridesToModules(modules);
  const i18nOnlyRegistration = isI18nOnlyRegistration(nextModules);
  const shouldMergeI18nOnly = existing !== null && i18nOnlyRegistration;
  const registeredModules = shouldMergeI18nOnly ? mergeI18nModules(existing, nextModules) : preserveExistingTranslations(existing ?? [], nextModules);
  setGlobalModules(registeredModules);
  if (i18nOnlyRegistration) {
    invalidateDictionaryCacheLocales(getTranslationLocales(nextModules));
  } else {
    invalidateDictionaryCache();
  }
  const globalScope = registryGlobals();
  const previousSnapshot = globalScope[SNAPSHOT_GLOBAL_KEY] ?? null;
  const nextSnapshot = snapshotModules(registeredModules);
  globalScope[SNAPSHOT_GLOBAL_KEY] = nextSnapshot;
  if (!snapshotsMatch(previousSnapshot, nextSnapshot)) {
    notifyModulesRegistered(registeredModules);
  }
}
function getModules() {
  const modules = getGlobalModules();
  if (!modules) {
    throw new Error("[Bootstrap] Modules not registered. Call registerModules() at bootstrap.");
  }
  return modules;
}
function tryGetModules() {
  return getGlobalModules();
}
export {
  getModules,
  onModulesRegistered,
  registerModules,
  tryGetModules
};
//# sourceMappingURL=registry.js.map
