import { NOT_EDITABLE } from "./flags.ts";
import { Effect } from "./effect.ts";
import { Serializer } from "./serializer.ts";
import { Piece } from "./piece.ts";
import { Item } from "./item.ts";
import { Agent } from "./agent.ts";
import { Terrain } from "./terrain.ts";
/**
* 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.terrain("Floor");
* Registry.serialize(floor); // → "Floor"
*/
const _serializers = new Map<string, Serializer>(); // typeId → Serializer
const _cache = new Map<string, Piece>(); // serialized key → Piece
const _reverse = new Map<Piece, string>(); // Piece → serialized key
export const Registry = {
/** Register a Serializer for a type ID. */
register(typeId: string, serializer: Serializer) {
_serializers.set(typeId, serializer);
},
item(key: string | Item): Item {
return typeof key === "string" ? this.get(key) as Item : key;
},
agent(key: string | Agent): Agent {
return typeof key === "string" ? this.get(key) as Agent : key;
},
terrain(key: string | Terrain): Terrain {
return typeof key === "string" ? this.get(key) as Terrain : key;
},
effect(key: string | Effect): Effect {
return typeof key === "string" ? this.get(key) as Effect : key;
},
/**
* Get (or create and cache) a piece by its serialized key.
*/
get(key: string): Piece | undefined {
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: Array<string | Piece> = rawArgs.map((arg) =>
arg.includes("^") ? this.get(arg.replace(/\^/g, "|")) ?? arg : arg,
);
const piece = serializer.create(args);
_cache.set(key, piece);
_reverse.set(piece, key);
return piece;
},
/**
* Return the serialized key for a piece.
* Prefers the cached reverse-lookup for pieces created via Registry.get().
* Falls back to piece.constructor.SERIALIZER.store() for pieces that were
* constructed directly (e.g. Statue wrappers applied at runtime).
*/
serialize(piece: Piece): string {
const key = _reverse.get(piece);
if (key !== undefined) {
return key;
}
const serializer = (piece.constructor as { SERIALIZER?: Serializer }).SERIALIZER;
if (!serializer) {
throw new Error("Piece was not created via Registry");
}
return serializer.store(piece);
},
/**
* Parse a serialized key and return the piece. Alias for get().
*/
deserialize(str: string): Piece | undefined {
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().
*/
getSerializersByTag(): Map<string, Map<string, string>> {
const result = new Map();
for (const [typeId, ser] of _serializers) {
if (typeof ser.example !== "function" || typeof ser.tag !== "function") {
continue; // structurally missing, not "threw for some other reason"
}
const ex = ser.example();
if (ex.is(NOT_EDITABLE)) continue;
if (ex instanceof Effect) continue;
const tag = ser.tag();
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.
*/
getExample(typeId: string): Piece {
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();
},
};