models/family.js

import { pluralize } from "../string_utils.js";
import Model from "./model.js";
import Relationship from "./relationship.js";

const REL_LABELS = new Map(
  Object.entries({
    deceased: "(now deceased), and",
    absent: "(now absent), and",
    married: "married to",
    divorced: "divorced from",
    separated: "separated from",
    couple: "lives with",
  }),
);

function couple(ch1) {
  if (!ch1.partner) {
    return `<li>${ch1.name.toString()} (${ch1.age})</li>`;
  }
  const ch2 = ch1.partner;
  const relationship = ch1.relationship;
  let relLabel = REL_LABELS.get(relationship);
  let arr = ["<li>"];
  if (ch1.name.family === ch2.name.family) {
    arr.push(ch1.name.given);
    arr.push(relLabel);
    arr.push(ch2.name.given);
    arr.push(ch2.name.toLastNameString());
  } else {
    arr.push(ch1.name.toString());
    arr.push(relLabel);
    arr.push(ch2.name.toString());
  }
  if (!ch1.status && !ch2.status) {
    arr.push(`(Ages ${ch1.age} and ${ch2.age}).`);
  } else {
    if (ch1.status && ch1.status === ch2.status) {
      arr.push(`(Died at ages ${ch1.age} and ${ch2.age}).`);
    } else {
      if (ch1.status) {
        arr.push(`(${ch1.name.given} died at age ${ch1.age};`);
        arr.push(`${ch2.name.given} is now age ${ch2.age}).`);
      }
      if (ch2.status) {
        arr.push(`(${ch2.name.given} died at age ${ch2.age};`);
        arr.push(`${ch1.name.given} is  now age ${ch1.age}).`);
      }
    }
  }
  if (ch1.children?.length > 0) {
    arr.push("They have " + pluralize(`{|}{1 |}child{ren}: `, ch1.children.length));
    arr.push("<ul>");
    for (const child of ch1.children) {
      if (child.partner) {
        arr.push(couple(child));
      } else if (child.status) {
        arr.push(`<li>${child.name} (${child.status} ${child.age})</li>`);
      } else {
        arr.push(`<li>${child.name} (${child.age})</li>`);
      }
    }
    arr.push("</ul>");
  }
  arr.push("</li>");
  return arr.join(" ");
}

/**
 * A family. This means (in the context of this game), a set of parents with some kids,
 * some of whom may themselves be in family objects with kids, etc. One of the building
 * blocks of encounters and homesteads, at the least.
 *
 * @extends Model
 * @property {Character} parent - The parent character who heads this family. Always female because
 *    we trace kinship matrilinearly during generation.
 * @property {number} generation - The generation number of this family (1 for the founding generation,
 *    2 for parents with children, and so forth).
 * @property {string} familyName - The surname shared by members of this family, derived from the male
 *    partner’s last name of the original parent. Actual people in the family will have different names
 *    for all the normal reasons.
 * @property {Character[]} members - All characters in the family.
 */
class Family extends Model {
  constructor(params) {
    super(params);
    this.familyName = params.parent.male
      ? params.parent.name.family
      : params.parent.partner.name.family;
    this.parent = params.parent;
    this.generation = params.generation;
  }
  get members() {
    function addPerson(p) {
      if (p.status !== "deceased") {
        all.push(p);
      }
      if (p.partner && p.partner.status !== "deceased") {
        all.push(p.partner);
      }
      for (const child of p.children ?? []) {
        addPerson(child);
      }
    }
    const all = [];
    addPerson(this.parent);
    return all;
  }
  getOldestRelationship() {
    const older = this.parent.age >= this.parent.partner.age ? this.parent : this.parent.partner;
    const younger = this.parent.age < this.parent.partner.age ? this.parent : this.parent.partner;
    return new Relationship({ older, younger, relName: this.parent.relationship });
  }
  toString() {
    return `<h4>Family Tree</h4>${this.toFamilyTree()}<h4>Roster</h4>${this.toRoster()}`;
  }
  toFamilyTree() {
    return `<ul>${couple(this.parent)}</ul>`;
  }
  toRoster() {
    return this.members.map((ch) => ch.toString()).join("");
  }
}

export default Family;