core/serializer.js

import { Registry } from "./registry.js";

/**
 * Abstract base class for piece serializers.
 *
 * For each piece type there is a serializer that can convert the piece back and
 * forth to a key, and that also provides metadata about the type for the map
 * editor. Mirrors the Java Serializer<T> interface exactly.
 *
 * Subclasses must override all five methods.
 */
export class Serializer {
  /**
   * Reconstruct a piece from its resolved args array.
   * String args are plain values; Piece args are already-resolved nested
   * registry lookups (the Registry resolves "^"-escaped sub-keys before
   * calling create, exactly as it does today).
   * @param {Array<string|Piece>} args
   * @returns {Piece}
   */
  create(_args) {
    throw new Error("Serializer.create() not implemented");
  }

  /**
   * Return the canonical serialized key for this piece, e.g. "Door|Blue|off".
   * The key must be parseable back via create().
   * @param {Piece} piece
   * @returns {string}
   */
  store(_piece) {
    throw new Error("Serializer.store() not implemented");
  }

  /**
   * Return a representative instance of this type for display in the map editor.
   * @returns {Piece}
   */
  example() {
    throw new Error("Serializer.example() not implemented");
  }

  /**
   * Given the typeId, return a human-readable template showing the key format,
   * e.g. "Door|{color}|{state}". Used by the editor to guide authoring.
   * @param {string} typeId
   * @returns {string}
   */
  template(_typeId) {
    throw new Error("Serializer.template() not implemented");
  }

  /**
   * Return the editor category for this type, e.g. "Terrain", "Room Features".
   * @returns {string}
   */
  tag() {
    throw new Error("Serializer.tag() not implemented");
  }
}

// ── TypeOnlySerializer ────────────────────────────────────────────────────────

/**
 * A serializer base that works for any piece defined only by its type.
 * The key is just the typeId with no arguments (e.g. "Floor", "Wall", "Fire").
 * Subclasses implement create() (return a new instance) and tag().
 */
export class TypeOnlySerializer extends Serializer {
  /** @param {string} typeId */
  constructor(typeId) {
    super();
    this._typeId = typeId;
  }

  store(_piece) {
    return this._typeId;
  }

  example() {
    return this.create(null);
  }

  template(_typeId) {
    return this._typeId;
  }
}

// ── BaseSerializer ────────────────────────────────────────────────────────────

/**
 * Base support for serializers that escape/unescape pieces as part of their
 * serialization key. Provides esc() and unesc*() helpers for building and
 * parsing keys that embed other pieces. Subclasses must implement all five
 * Serializer methods.
 */
export class BaseSerializer extends Serializer {
  /**
   * Escape a piece reference for embedding inside another key.
   * Replaces "|" with "^" so the nested key does not break pipe-splitting.
   * @param {Piece} piece
   * @returns {string}
   */
  esc(piece) {
    return Registry.serialize(piece).replaceAll("|", "^");
  }

  /**
   * Unescape a terrain reference embedded in a key arg.
   * The Registry has already resolved "^"-escaped args into Piece instances
   * before calling create(), so this helper is provided only for cases where
   * manual resolution is needed outside of create().
   * @param {string} key  escaped key (carets instead of pipes)
   * @returns {Terrain}
   */
  unescTerrain(key) {
    return Registry.get(key.replaceAll("^", "|"));
  }

  /** @param {string} key @returns {Item} */
  unescItem(key) {
    return Registry.get(key.replaceAll("^", "|"));
  }

  /** @param {string} key @returns {Piece} */
  unesc(key) {
    return Registry.get(key.replaceAll("^", "|"));
  }
}