import { NOT_EDITABLE } from "./flags.js";
import { Effect } from "./effect.js";
/**
* Registry — singleton cache for all piece instances.
*
* Pieces are immutable singletons identified by their serialization key
* (pipe-delimited: "TypeName|arg1|arg2"). Nested piece references within
* an arg use ^ as a sub-delimiter (e.g. "Altar^none" deserializes as
* the piece from key "Altar|none").
*
* Usage:
* Registry.register("Floor", Floor.SERIALIZER);
* const floor = Registry.get("Floor");
* Registry.serialize(floor); // → "Floor"
*/
const _serializers = new Map(); // typeId → Serializer
const _cache = new Map(); // serialized key → Piece
const _reverse = new Map(); // Piece → serialized key
export const Registry = {
/**
* Register a Serializer for a type ID.
* Also accepts a bare factory function for backward compatibility during
* migration — these are wrapped in a minimal shim.
* @param {string} typeId
* @param {Serializer|function} serializerOrFactory
*/
register(typeId, serializerOrFactory) {
if (typeof serializerOrFactory === "function") {
// Legacy bare factory — wrap automatically
_serializers.set(typeId, _wrapFactory(typeId, serializerOrFactory));
} else {
_serializers.set(typeId, serializerOrFactory);
}
},
/**
* Get (or create and cache) a piece by its serialized key.
* @param {string} key e.g. "Floor" or "TriggerOnceOnDrop|Altar^none|Blue|Chalice"
* @returns {Piece}
*/
get(key) {
if (_cache.has(key)) return _cache.get(key);
const parts = key.split("|");
const typeId = parts[0];
const rawArgs = parts.slice(1);
const serializer = _serializers.get(typeId);
if (!serializer) throw new Error(`Unknown piece type: "${typeId}"`);
// Resolve nested piece references: "Altar^none" → Registry.get("Altar|none")
const args = rawArgs.map((arg) =>
arg.includes("^") ? this.get(arg.replace(/\^/g, "|")) : arg,
);
const piece = serializer.create(args);
_cache.set(key, piece);
_reverse.set(piece, key);
return piece;
},
/**
* Return the serialized key for a piece created via Registry.get().
* Throws if the piece was not created through the registry.
* @param {Piece} piece
* @returns {string}
*/
serialize(piece) {
const key = _reverse.get(piece);
if (key === undefined)
throw new Error("Piece was not created via Registry");
return key;
},
/**
* Parse a serialized key and return the piece. Alias for get().
* @param {string} str
* @returns {Piece}
*/
deserialize(str) {
return this.get(str);
},
/**
* Return a Map of tag → Map<typeId, template> for all registered types.
* Excludes types whose example piece has the NOT_EDITABLE flag or is an Effect.
* Mirrors Java's Registry.getSerializersByTags().
* @returns {Map<string, Map<string, string>>}
*/
getSerializersByTag() {
const result = new Map();
for (const [typeId, ser] of _serializers) {
let ex;
try {
ex = ser.example();
} catch {
continue; // legacy shim with no example
}
if (ex?.is?.(NOT_EDITABLE)) continue;
if (ex instanceof Effect) continue;
let tag;
try {
tag = ser.tag();
} catch {
continue;
}
if (!result.has(tag)) result.set(tag, new Map());
result.get(tag).set(typeId, ser.template(typeId));
}
return result;
},
/**
* Return an example piece for the given typeId.
* Used by the editor to render a preview of each type.
* @param {string} typeId
* @returns {Piece}
*/
getExample(typeId) {
const ser = _serializers.get(typeId);
if (!ser) throw new Error(`Unknown piece type: "${typeId}"`);
return ser.example();
},
/** Clear all registrations and cache. Intended for use in tests only. */
reset() {
_serializers.clear();
_cache.clear();
_reverse.clear();
},
};
// ── Helpers ───────────────────────────────────────────────────────────────────
/**
* Wrap a legacy bare factory function in a minimal Serializer-shaped object.
* Provides create() only; tag/example/template/store throw (Uncategorised).
* @param {string} typeId
* @param {function} factory
*/
function _wrapFactory(typeId, factory) {
return {
create: factory,
store() {
throw new Error(`${typeId}: no Serializer (legacy factory)`);
},
example() {
throw new Error(`${typeId}: no Serializer (legacy factory)`);
},
template() {
return typeId;
},
tag() {
return "Uncategorised";
},
};
}