'use strict';

const getTypeOf = (thing) => typeof thing;
const TINYBASE = 'tinybase';
const EMPTY_STRING = '';
const COMMA = ',';
const STRING = getTypeOf(EMPTY_STRING);
const BOOLEAN = getTypeOf(true);
const NUMBER = getTypeOf(0);
const TYPE = 'type';
const DEFAULT = 'default';
const LISTENER = 'Listener';
const GET = 'get';
const ADD = 'add';
const HAS = 'Has';
const IDS = 'Ids';
const TABLE = 'Table';
const TABLES = TABLE + 's';
const TABLE_IDS = TABLE + IDS;
const ROW = 'Row';
const ROW_IDS = ROW + IDS;
const SORTED_ROW_IDS = 'Sorted' + ROW + IDS;
const CELL = 'Cell';
const CELL_IDS = CELL + IDS;
const VALUE = 'Value';
const VALUES = VALUE + 's';
const VALUE_IDS = VALUE + IDS;

const promise = Promise;
const isFiniteNumber = isFinite;
const isUndefined = (thing) => thing == void 0;
const ifNotUndefined = (value, then, otherwise) =>
  isUndefined(value) ? otherwise?.() : then(value);
const isTypeStringOrBoolean = (type) => type == STRING || type == BOOLEAN;
const isString = (thing) => getTypeOf(thing) == STRING;
const isArray = (thing) => Array.isArray(thing);
const slice = (arrayOrString, start, end) => arrayOrString.slice(start, end);
const size = (arrayOrString) => arrayOrString.length;
const promiseAll = async (promises) => promise.all(promises);

const getCellOrValueType = (cellOrValue) => {
  const type = getTypeOf(cellOrValue);
  return isTypeStringOrBoolean(type) ||
    (type == NUMBER && isFiniteNumber(cellOrValue))
    ? type
    : void 0;
};

const arrayEvery = (array, cb) => array.every(cb);
const arraySort = (array, sorter) => array.sort(sorter);
const arrayForEach = (array, cb) => array.forEach(cb);
const arrayJoin = (array, sep = EMPTY_STRING) => array.join(sep);
const arrayMap = (array, cb) => array.map(cb);
const arrayFilter = (array, cb) => array.filter(cb);
const arrayPush = (array, ...values) => array.push(...values);
const arrayPop = (array) => array.pop();
const arrayUnshift = (array, ...values) => array.unshift(...values);
const arrayShift = (array) => array.shift();

const object = Object;
const getPrototypeOf = (obj) => object.getPrototypeOf(obj);
const objEntries = object.entries;
const isObject = (obj) =>
  !isUndefined(obj) &&
  ifNotUndefined(
    getPrototypeOf(obj),
    (objPrototype) =>
      objPrototype == object.prototype ||
      isUndefined(getPrototypeOf(objPrototype)),

    /* istanbul ignore next */
    () => true,
  );
const objIds = object.keys;
const objFreeze = object.freeze;
const objToArray = (obj, cb) =>
  arrayMap(objEntries(obj), ([id, value]) => cb(value, id));
const objSize = (obj) => size(objIds(obj));
const objIsEmpty = (obj) => isObject(obj) && objSize(obj) == 0;

const collHas = (coll, keyOrValue) => coll?.has(keyOrValue) ?? false;
const collValues = (coll) => [...(coll?.values() ?? [])];
const collForEach = (coll, cb) => coll?.forEach(cb);
const collDel = (coll, keyOrValue) => coll?.delete(keyOrValue);

const mapNew = (entries) => new Map(entries);
const mapGet = (map, key) => map?.get(key);
const mapForEach = (map, cb) =>
  collForEach(map, (value, key) => cb(key, value));
const mapMap = (coll, cb) =>
  arrayMap([...(coll?.entries() ?? [])], ([key, value]) => cb(value, key));
const mapSet = (map, key, value) =>
  isUndefined(value) ? (collDel(map, key), map) : map?.set(key, value);
const mapEnsure = (map, key, getDefaultValue, hadExistingValue) => {
  if (!collHas(map, key)) {
    mapSet(map, key, getDefaultValue());
  }
  return mapGet(map, key);
};

const upper = (str) => str.toUpperCase();
const lower = (str) => str.toLowerCase();
const A = 'a ';
const A_FUNCTION_FOR = 'A function for';
const AND_REGISTERS =
  ', and registers a listener so that any changes to that result will cause a re-render';
const CALLBACK = 'Callback';
const CHANGES = 'Changes';
const COUNT = 'Count';
const DEL = 'Del';
const DEPS = 'Deps';
const DEPS_SUFFIX = DEPS + '?: React.DependencyList';
const DO_ROLLBACK_PARAM = 'doRollback?: DoRollback';
const DO_ACTIONS_AND_ROLLBACK_PARAMS =
  'actions: () => Return, ' + DO_ROLLBACK_PARAM;
const EXPORT = 'export';
const ID = 'Id';
const INVALID = 'Invalid';
const JSON$1 = 'Json';
const LISTENER_ = lower(LISTENER);
const OPTIONAL_COLON = '?: ';
const OR_UNDEFINED = ' | undefined';
const NON_NULLABLE = 'NonNullable';
const PARTIAL = 'Partial';
const PROPS = 'Props';
const PROVIDER = 'Provider';
const REGISTERS_A_LISTENER = `Registers a ${LISTENER_} that will be called`;
const REPRESENTS = 'Represents';
const ROW_ID_PARAM = 'rowId: ' + ID;
const SCHEMA = 'Schema';
const SET = 'Set';
const SORTED_ARGS = ', descending?: boolean, offset?: number, limit?: number';
const SQUARE_BRACKETS = '[]';
const THE_STORE = 'the Store';
const TRANSACTION = 'Transaction';
const TRANSACTION_ = lower(TRANSACTION);
const TRANSACTION_DOC =
  'Execute a ' + TRANSACTION_ + ' to make multiple mutations';
const START_TRANSACTION_DOC = 'Explicitly starts a ' + TRANSACTION_;
const FINISH_TRANSACTION_DOC = 'Explicitly finishes a ' + TRANSACTION_;
const THE_END_OF_THE_TRANSACTION = 'the end of the ' + TRANSACTION_;
const VOID = 'void';
const RETURNS_VOID = ' => ' + VOID;
const WHEN_SET = 'WhenSet';
const WHEN_SETTING_IT = ' when setting it';
const A_STRING_SERIALIZATION_OF = A + 'string serialization of';
const SPACE = ' ';
const GETS_A_CALLBACK_THAT_CAN = 'Gets a callback that can ';
const THE = 'the ';
const THE_SCHEMA_FOR = ' the schema for';
const getHasDoc = (has = 0) => (has ? 'the existence of ' : '');
const getTheContentOfDoc = (content = 0, theStore = 0, has = 0) =>
  getHasDoc(has) +
  `the ${CONTENT[content]}content of` +
  (theStore ? SPACE + THE_STORE : EMPTY_STRING);
const getTheContentOfTheStoreDoc = (content = 0, verb, set = 0, has = 0) =>
  VERBS[verb] +
  SPACE +
  getHasDoc(has) +
  getTheContentOfDoc(content, 1) +
  (set ? ' when setting it' : EMPTY_STRING);
const getRowTypeDoc = (tableId, set = 0) =>
  REPRESENTS +
  ` a Row when ${set ? 's' : 'g'}etting ${getTheContentOfDoc()} the '${tableId}' ` +
  TABLE;
const getIdsDoc = (idsNoun, parentNoun, sorted = 0) =>
  `Gets ${sorted ? 'sorted, paginated' : 'the'} Ids of the ${idsNoun}s in ` +
  parentNoun;
const getForEachDoc = (childNoun, parentNoun) =>
  `Calls a function for each ${childNoun} in ` + parentNoun;
const getPropsDoc = (childNoun) =>
  'The props passed to a component that renders ' + childNoun;
const getCallbackDoc = (takes) => 'A function that takes ' + takes;
const getListenerTypeDoc = (childNoun, parentNoun = 0, has = 0) =>
  A_FUNCTION_FOR +
  ' listening to changes to ' +
  getHasDoc(has) +
  NOUNS[childNoun] +
  ' in ' +
  NOUNS[parentNoun];
const getListenerDoc = (childNoun, parentNoun, pluralChild = 0, has = 0) =>
  REGISTERS_A_LISTENER +
  ' whenever ' +
  getHasDoc(has) +
  NOUNS[childNoun] +
  ' in ' +
  NOUNS[parentNoun] +
  ' change' +
  (pluralChild ? EMPTY_STRING : 's');
const getTableDoc = (tableId) => `the '${tableId}' ` + TABLE;
const getRowDoc = (tableId) => 'the specified Row in ' + getTableDoc(tableId);
const getTableContentDoc = (tableId, verb = 0, has = 0) =>
  VERBS[verb] +
  SPACE +
  getTheContentOfDoc(0, 0, has) +
  SPACE +
  getTableDoc(tableId);
const getRowContentDoc = (tableId, verb = 0, has = 0) =>
  VERBS[verb] + ` ${getTheContentOfDoc(0, 0, has)} ` + getRowDoc(tableId);
const getCellContentDoc = (tableId, cellId, verb = 0, has = 0) =>
  VERBS[verb] +
  SPACE +
  getHasDoc(has) +
  `the '${cellId}' Cell for ` +
  getRowDoc(tableId);
const getValueContentDoc = (valueId, verb = 0, has = 0) =>
  VERBS[verb] + SPACE + getHasDoc(has) + `the '${valueId}' Value`;
const VERBS = [
  'Gets',
  'Checks existence of',
  'Sets',
  'Deletes',
  'Sets part of',
  REPRESENTS,
  'Gets ' + A_STRING_SERIALIZATION_OF,
  'Sets ' + A_STRING_SERIALIZATION_OF,
  REGISTERS_A_LISTENER + ' whenever',
  GETS_A_CALLBACK_THAT_CAN + 'set',
  GETS_A_CALLBACK_THAT_CAN + 'add',
  GETS_A_CALLBACK_THAT_CAN + 'set part of',
  GETS_A_CALLBACK_THAT_CAN + 'delete',
  'Renders',
  'Gets ' + A_STRING_SERIALIZATION_OF + THE_SCHEMA_FOR,
  'Sets' + THE_SCHEMA_FOR,
  'Deletes' + THE_SCHEMA_FOR,
];
const METHOD_PREFIX_VERBS = [
  GET,
  'has',
  'set',
  'del',
  'set',
  // partial
  'forEach',
  ADD,
  EMPTY_STRING,
];
const NOUNS = [
  THE_STORE,
  TABLES,
  THE + TABLE + SPACE + IDS,
  A + TABLE,
  THE + ROW + SPACE + IDS,
  A + ROW,
  THE + CELL + SPACE + IDS,
  A + CELL,
  'invalid Cell changes',
  VALUES,
  THE + VALUE + SPACE + IDS,
  A + VALUE,
  'invalid Value changes',
  THE + 'sorted ' + ROW + SPACE + IDS,
  THE + CELL + SPACE + IDS + ' anywhere',
  THE + 'number of Rows',
  A + CELL + ' anywhere',
];
const CONTENT = [EMPTY_STRING, 'tabular ', 'keyed value '];

const setNew = (entryOrEntries) =>
  new Set(
    isArray(entryOrEntries) || isUndefined(entryOrEntries)
      ? entryOrEntries
      : [entryOrEntries],
  );
const setAdd = (set, value) => set?.add(value);

const NON_ALPHA = /[^A-Za-z]+/;
const NON_ALPHANUMERIC = /[^A-Za-z0-9]+/;
const JSDOC = /^( *)\/\*\* *(.*?) *\*\/$/gm;
const stringHasComma = (str) => str.includes(COMMA);
const mapUnique = (map, id, value, index = 1) => {
  const uniqueId = `${id}${index == 1 ? '' : index}`;
  if (collHas(map, uniqueId)) {
    return mapUnique(map, id, value, index + 1);
  } else {
    mapSet(map, uniqueId, value);
    return uniqueId;
  }
};
const formatJsDoc = (file) =>
  file.replace(JSDOC, (_, indent, text) => {
    const lineLength = 77 - size(indent);
    return `${indent}/**
${text.replace(
  new RegExp(`([^\\n]{1,${lineLength}})(\\s|$)`, 'g'),
  `${indent} * $1
`,
)}${indent} */`;
  });
const flat = (array) => array.flat(1e3);
const camel = (str, firstCap = 0) =>
  arrayJoin(
    arrayMap(
      str.split(NON_ALPHANUMERIC),
      (word, w) =>
        (w > 0 || firstCap ? upper : lower)(slice(word, 0, 1)) + slice(word, 1),
    ),
  );
const snake = (str) =>
  upper(
    arrayJoin(
      (str && !NON_ALPHA.test(str[0]) ? str : ' ' + str).split(
        NON_ALPHANUMERIC,
      ),
      '_',
    ),
  );
const comment = (doc) => `/** ${doc}. */`;
const getParameterList = (...params) =>
  arrayJoin(
    arrayFilter(params, (param) => param),
    ', ',
  );
const getFieldTypeList = (...props) => '{' + arrayJoin(props, '; ') + '}';
const getPropTypeList = (...props) =>
  getFieldTypeList(...arrayMap(props, (prop) => 'readonly ' + prop));
const getCodeFunctions = () => {
  const allImports = [mapNew(), mapNew(), mapNew(), mapNew()];
  const types = mapNew();
  const constants = mapNew();
  const build = (...lines) => arrayJoin(flat(lines), '\n');
  const addImport = (location, source, ...items) =>
    arrayForEach(items, (item) =>
      arrayForEach([0, 1], (eachLocation) =>
        (location ?? eachLocation) == eachLocation
          ? setAdd(mapEnsure(allImports[eachLocation], source, setNew), item)
          : 0,
      ),
    );
  const addType = (name, body, doc, generic = EMPTY_STRING, exported = 1) =>
    mapUnique(types, name, [body, doc, generic, exported]);
  const addInternalFunction = (name, parameters, body) =>
    mapUnique(
      constants,
      name,
      isArray(body)
        ? [`(${parameters}) => {`, body, '}']
        : [`(${parameters}) => ${body}`],
    );
  const addConstant = (name, body) =>
    mapGet(constants, name) === body ? name : mapUnique(constants, name, body);
  const getSortableImport = (importMaybeAs) => {
    const as = importMaybeAs.indexOf(' as ');
    return as != -1 ? slice(importMaybeAs, as + 4) : importMaybeAs;
  };
  const getImports = (location = 0) =>
    arrayMap(
      [
        ...arraySort(
          mapMap(
            allImports[location],
            (items, source) =>
              `import {${arrayJoin(
                arraySort(collValues(items), (import1, import2) =>
                  getSortableImport(import1) > getSortableImport(import2)
                    ? 1
                    : -1,
                ),
                ', ',
              )}} from '${source}';`,
          ),
          (import1, import2) =>
            stringHasComma(import1) != stringHasComma(import2)
              ? stringHasComma(import1)
                ? -1
                : 1
              : import1 > import2
                ? 1
                : -1,
        ),
        EMPTY_STRING,
      ],
      (line) => line.replace('{React}', 'React'),
      // sigh
    );
  const getTypes = () =>
    mapMap(types, ([body, doc, generic, exported], name) => [
      comment(doc),
      `${exported ? EXPORT + ' ' : EMPTY_STRING}type ${name}${generic} = ${body};`,
      EMPTY_STRING,
    ]);
  const getConstants = () =>
    mapMap(constants, (body, name) => {
      body = isArray(body) ? body : [body];
      arrayPush(body, `${arrayPop(body)};`);
      return [`const ${name} = ${arrayShift(body)}`, body, EMPTY_STRING];
    });
  return [
    build,
    addImport,
    addType,
    addInternalFunction,
    addConstant,
    getImports,
    getTypes,
    getConstants,
  ];
};

const getCreateFunction = (getFunction, initFunction) => {
  const thingsByStore = /* @__PURE__ */ new WeakMap();
  return (store) => {
    if (!thingsByStore.has(store)) {
      thingsByStore.set(store, getFunction(store));
    }
    const thing = thingsByStore.get(store);
    return thing;
  };
};

const getSchemaFunctions = (tablesSchema, valuesSchema, addConstant) => {
  const mapTablesSchema = (callback) =>
    objToArray(tablesSchema, (_, tableId) => {
      return callback(
        tableId,
        camel(tableId, 1),
        addConstant(snake(tableId), `'${tableId}'`),
      );
    });
  const mapCellSchema = (tableId, callback) =>
    objToArray(tablesSchema[tableId], (cellSchema, cellId) =>
      callback(
        cellId,
        cellSchema[TYPE],
        cellSchema[DEFAULT],
        addConstant(snake(cellId), `'${cellId}'`),
        camel(cellId, 1),
      ),
    );
  const mapValuesSchema = (callback) =>
    objToArray(valuesSchema, (valueSchema, valueId) =>
      callback(
        valueId,
        valueSchema[TYPE],
        valueSchema[DEFAULT],
        addConstant(snake(valueId), `'${valueId}'`),
        camel(valueId, 1),
      ),
    );
  return [mapTablesSchema, mapCellSchema, mapValuesSchema];
};

const getTypeFunctions = (
  addType,
  mapTablesSchema,
  mapCellSchema,
  mapValuesSchema,
) => {
  const getTablesTypes = (storeInstance, storeType) => {
    const storeParam = storeInstance + ': ' + storeType;
    const tablesType = addType(
      TABLES,
      getFieldTypeList(
        ...mapTablesSchema(
          (tableId) =>
            `'${tableId}'?: {[rowId: Id]: ` +
            getFieldTypeList(
              ...mapCellSchema(
                tableId,
                (cellId, type, defaultValue) =>
                  `'${cellId}'${isUndefined(defaultValue) ? '?' : EMPTY_STRING}: ${type}`,
              ),
            ) +
            '}',
        ),
      ),
      getTheContentOfTheStoreDoc(1, 5),
    );
    const tablesWhenSetType = addType(
      TABLES + WHEN_SET,
      getFieldTypeList(
        ...mapTablesSchema(
          (tableId) =>
            `'${tableId}'?: {[rowId: Id]: ` +
            getFieldTypeList(
              ...mapCellSchema(
                tableId,
                (cellId, type) => `'${cellId}'?: ${type}`,
              ),
            ) +
            '}',
        ),
      ),
      getTheContentOfTheStoreDoc(1, 5, 1),
    );
    const tableIdType = addType(
      TABLE + ID,
      'keyof ' + tablesType,
      'A ' + TABLE + ' Id in ' + THE_STORE,
    );
    const tIdGeneric = `<TId extends ${tableIdType}>`;
    const tableType = addType(
      TABLE,
      NON_NULLABLE + `<${tablesType}[TId]>`,
      'A ' + TABLE + ' in ' + THE_STORE,
      tIdGeneric,
    );
    const tableWhenSetType = addType(
      TABLE + WHEN_SET,
      NON_NULLABLE + `<${tablesWhenSetType}[TId]>`,
      'A ' + TABLE + ' in ' + THE_STORE + WHEN_SETTING_IT,
      tIdGeneric,
    );
    const rowType = addType(
      ROW,
      tableType + '<TId>[Id]',
      'A ' + ROW + ' in a ' + TABLE,
      tIdGeneric,
    );
    const rowWhenSetType = addType(
      ROW + WHEN_SET,
      tableWhenSetType + '<TId>[Id]',
      'A ' + ROW + ' in a ' + TABLE + WHEN_SETTING_IT,
      tIdGeneric,
    );
    const cellIdType = addType(
      CELL + ID,
      `Extract<keyof ${rowType}<TId>, Id>`,
      'A ' + CELL + ' Id in a ' + ROW,
      tIdGeneric,
    );
    const cellType = addType(
      CELL,
      NON_NULLABLE + `<${tablesType}[TId]>[Id][CId]`,
      'A ' + CELL + ' in a ' + ROW,
      `<TId extends ${tableIdType}, CId extends ${cellIdType}<TId>>`,
    );
    const cellIdCellArrayType = addType(
      'CellIdCellArray',
      `CId extends ${cellIdType}<TId> ? [cellId: CId, cell: ${cellType}<TId, CId>] : never`,
      CELL + ' Ids and types in a ' + ROW,
      `<TId extends ${tableIdType}, CId = ${cellIdType}<TId>>`,
      0,
    );
    const cellCallbackType = addType(
      CELL + CALLBACK,
      `(...[cellId, cell]: ${cellIdCellArrayType}<TId>)` + RETURNS_VOID,
      getCallbackDoc(A + CELL + ' Id, and ' + CELL),
      tIdGeneric,
    );
    const rowCallbackType = addType(
      ROW + CALLBACK,
      '(rowId: Id, forEachCell: (cellCallback: CellCallback<TId>) ' +
        RETURNS_VOID +
        ') ' +
        RETURNS_VOID,
      getCallbackDoc(A + ROW + ' Id, and a ' + CELL + ' iterator'),
      tIdGeneric,
    );
    const tableCellCallbackType = addType(
      TABLE + CELL + CALLBACK,
      `(cellId: ${cellIdType}<TId>, count: number) ` + RETURNS_VOID,
      getCallbackDoc(A + CELL + ' Id, and count of how many times it appears'),
      tIdGeneric,
    );
    const tableIdForEachRowArrayType = addType(
      'TableIdForEachRowArray',
      `TId extends ${tableIdType} ? [tableId: TId, forEachRow: (rowCallback: ${rowCallbackType}<TId>)${RETURNS_VOID}] : never`,
      TABLE + ' Ids and callback types',
      `<TId = ${tableIdType}>`,
      0,
    );
    const tableCallbackType = addType(
      TABLE + CALLBACK,
      `(...[tableId, forEachRow]: ${tableIdForEachRowArrayType})` +
        RETURNS_VOID,
      getCallbackDoc(A + TABLE + ' Id, and a ' + ROW + ' iterator'),
      EMPTY_STRING,
    );
    const tableIdRowIdCellIdArrayType = addType(
      'TableIdRowIdCellIdArray',
      `TId extends ${tableIdType} ? [tableId: TId, rowId: Id, cellId: ${cellIdType}<TId>] : never`,
      'Ids for GetCellChange',
      `<TId = ${tableIdType}>`,
      0,
    );
    const getCellChangeType = addType(
      'GetCellChange',
      `(...[tableId, rowId, cellId]: ${tableIdRowIdCellIdArrayType}) => CellChange`,
      A_FUNCTION_FOR +
        ` returning information about any Cell's changes during a ` +
        TRANSACTION_,
    );
    const hasTablesListenerType = addType(
      HAS + TABLES + LISTENER,
      `(${storeParam}, hasTables: boolean)` + RETURNS_VOID,
      getListenerTypeDoc(1, 0, 1),
    );
    const tablesListenerType = addType(
      TABLES + LISTENER,
      `(${storeParam}, getCellChange: ${getCellChangeType}${OR_UNDEFINED})` +
        RETURNS_VOID,
      getListenerTypeDoc(1),
    );
    const tableIdsListenerType = addType(
      TABLE_IDS + LISTENER,
      `(${storeParam})` + RETURNS_VOID,
      getListenerTypeDoc(2),
    );
    const hasTableListenerType = addType(
      HAS + TABLE + LISTENER,
      `(${storeParam}, tableId: ${tableIdType}, hasTable: boolean)` +
        RETURNS_VOID,
      getListenerTypeDoc(3, 0, 1),
    );
    const tableListenerType = addType(
      TABLE + LISTENER,
      `(${storeParam}, tableId: ${tableIdType}, getCellChange: ${getCellChangeType}${OR_UNDEFINED})` +
        RETURNS_VOID,
      getListenerTypeDoc(3),
    );
    const tableCellIdsListenerType = addType(
      TABLE + CELL_IDS + LISTENER,
      `(${storeParam}, tableId: ${tableIdType})` + RETURNS_VOID,
      getListenerTypeDoc(14, 3),
    );
    const hasTableCellListenerArgsArrayInnerType = addType(
      'HasTableCellListenerArgsArrayInner',
      `CId extends ${cellIdType}<TId> ? [${storeParam}, tableId: TId, cellId: CId, hasTableCell: boolean] : never`,
      'Cell args for HasTableCellListener',
      `<TId extends ${tableIdType}, CId = ${cellIdType}<TId>>`,
      0,
    );
    const hasTableCellListenerArgsArrayOuterType = addType(
      'HasTableCellListenerArgsArrayOuter',
      `TId extends ${tableIdType} ? ` +
        hasTableCellListenerArgsArrayInnerType +
        '<TId> : never',
      'Table args for HasTableCellListener',
      `<TId = ${tableIdType}>`,
      0,
    );
    const hasTableCellListenerType = addType(
      HAS + TABLE + CELL + LISTENER,
      `(...[${storeInstance}, tableId, cellId, hasTableCell]: ` +
        hasTableCellListenerArgsArrayOuterType +
        ')' +
        RETURNS_VOID,
      getListenerTypeDoc(16, 3, 1),
    );
    const rowCountListenerType = addType(
      ROW + 'Count' + LISTENER,
      `(${storeParam}, tableId: ${tableIdType})` + RETURNS_VOID,
      getListenerTypeDoc(15, 3),
    );
    const rowIdsListenerType = addType(
      ROW_IDS + LISTENER,
      `(${storeParam}, tableId: ${tableIdType})` + RETURNS_VOID,
      getListenerTypeDoc(4, 3),
    );
    const sortedRowIdsListenerType = addType(
      SORTED_ROW_IDS + LISTENER,
      '(' +
        getParameterList(
          storeParam,
          'tableId: ' + tableIdType,
          'cellId: Id' + OR_UNDEFINED,
          'descending: boolean',
          'offset: number',
          'limit: number' + OR_UNDEFINED,
          'sortedRowIds: Ids',
        ) +
        ')' +
        RETURNS_VOID,
      getListenerTypeDoc(13, 3),
    );
    const hasRowListenerType = addType(
      HAS + ROW + LISTENER,
      '(' +
        getParameterList(
          `${storeParam}`,
          'tableId: ' + tableIdType,
          ROW_ID_PARAM,
          `hasRow: boolean`,
        ) +
        ')' +
        RETURNS_VOID,
      getListenerTypeDoc(5, 3, 1),
    );
    const rowListenerType = addType(
      ROW + LISTENER,
      '(' +
        getParameterList(
          `${storeParam}`,
          'tableId: ' + tableIdType,
          ROW_ID_PARAM,
          `getCellChange: ${getCellChangeType}${OR_UNDEFINED}`,
        ) +
        ')' +
        RETURNS_VOID,
      getListenerTypeDoc(5, 3),
    );
    const cellIdsListenerType = addType(
      CELL_IDS + LISTENER,
      '(' +
        getParameterList(
          `${storeParam}`,
          'tableId: ' + tableIdType,
          ROW_ID_PARAM,
        ) +
        ')' +
        RETURNS_VOID,
      getListenerTypeDoc(6, 5),
    );
    const hasCellListenerArgsArrayInnerType = addType(
      'HasCellListenerArgsArrayInner',
      `CId extends ${cellIdType}<TId> ? [${storeParam}, tableId: TId, ${ROW_ID_PARAM}, cellId: CId, hasCell: boolean] : never`,
      'Cell args for HasCellListener',
      `<TId extends ${tableIdType}, CId = ${cellIdType}<TId>>`,
      0,
    );
    const hasCellListenerArgsArrayOuterType = addType(
      'HasCellListenerArgsArrayOuter',
      `TId extends ${tableIdType} ? ` +
        hasCellListenerArgsArrayInnerType +
        '<TId> : never',
      'Table args for HasCellListener',
      `<TId = ${tableIdType}>`,
      0,
    );
    const hasCellListenerType = addType(
      HAS + CELL + LISTENER,
      `(...[${storeInstance}, tableId, rowId, cellId, hasCell]: ` +
        hasCellListenerArgsArrayOuterType +
        ')' +
        RETURNS_VOID,
      getListenerTypeDoc(7, 5, 1),
    );
    const cellListenerArgsArrayInnerType = addType(
      'CellListenerArgsArrayInner',
      `CId extends ${cellIdType}<TId> ? [${storeParam}, tableId: TId, ${ROW_ID_PARAM}, cellId: CId, newCell: ${cellType}<TId, CId> ${OR_UNDEFINED}, oldCell: ${cellType}<TId, CId> ${OR_UNDEFINED}, getCellChange: ${getCellChangeType} ${OR_UNDEFINED}] : never`,
      'Cell args for CellListener',
      `<TId extends ${tableIdType}, CId = ${cellIdType}<TId>>`,
      0,
    );
    const cellListenerArgsArrayOuterType = addType(
      'CellListenerArgsArrayOuter',
      `TId extends ${tableIdType} ? ` +
        cellListenerArgsArrayInnerType +
        '<TId> : never',
      'Table args for CellListener',
      `<TId = ${tableIdType}>`,
      0,
    );
    const cellListenerType = addType(
      CELL + LISTENER,
      `(...[${storeInstance}, tableId, rowId, cellId, newCell, oldCell, getCellChange]: ${cellListenerArgsArrayOuterType})` +
        RETURNS_VOID,
      getListenerTypeDoc(7, 5),
    );
    const invalidCellListenerType = addType(
      INVALID + CELL + LISTENER,
      `(${storeParam}, tableId: Id, ${ROW_ID_PARAM}, cellId: Id, invalidCells: any[])` +
        RETURNS_VOID,
      getListenerTypeDoc(8),
    );
    return [
      tablesType,
      tablesWhenSetType,
      tableIdType,
      tableType,
      tableWhenSetType,
      rowType,
      rowWhenSetType,
      cellIdType,
      cellType,
      cellCallbackType,
      rowCallbackType,
      tableCellCallbackType,
      tableCallbackType,
      hasTablesListenerType,
      tablesListenerType,
      tableIdsListenerType,
      hasTableListenerType,
      tableListenerType,
      tableCellIdsListenerType,
      hasTableCellListenerType,
      rowCountListenerType,
      rowIdsListenerType,
      sortedRowIdsListenerType,
      hasRowListenerType,
      rowListenerType,
      cellIdsListenerType,
      hasCellListenerType,
      cellListenerType,
      invalidCellListenerType,
    ];
  };
  const getValuesTypes = (storeInstance, storeType) => {
    const storeParam = storeInstance + ': ' + storeType;
    const valuesType = addType(
      VALUES,
      getFieldTypeList(
        ...mapValuesSchema(
          (valueId, type, defaultValue) =>
            `'${valueId}'${isUndefined(defaultValue) ? '?' : EMPTY_STRING}: ${type}`,
        ),
      ),
      getTheContentOfTheStoreDoc(2, 5),
    );
    const valuesWhenSetType = addType(
      VALUES + WHEN_SET,
      getFieldTypeList(
        ...mapValuesSchema((valueId, type) => `'${valueId}'?: ${type}`),
      ),
      getTheContentOfTheStoreDoc(2, 5, 1),
    );
    const valueIdType = addType(
      VALUE + ID,
      'keyof ' + valuesType,
      'A ' + VALUE + ' Id in ' + THE_STORE,
    );
    const valueType = addType(
      VALUE,
      NON_NULLABLE + `<${valuesType}[VId]>`,
      'A ' + VALUE + ' Id in ' + THE_STORE,
      `<VId extends ${valueIdType}>`,
    );
    const valueIdValueArrayType = addType(
      'ValueIdValueArray',
      `VId extends ${valueIdType} ? [valueId: VId, value: ${valueType}<VId>] : never`,
      VALUE + ' Ids and types in ' + THE_STORE,
      `<VId = ${valueIdType}>`,
      0,
    );
    const valueCallbackType = addType(
      VALUE + CALLBACK,
      `(...[valueId, value]: ${valueIdValueArrayType})` + RETURNS_VOID,
      getCallbackDoc(A + VALUE + ' Id, and ' + VALUE),
    );
    const getValueChangeType = addType(
      'GetValueChange',
      `(valueId: ${valueIdType}) => ValueChange`,
      A_FUNCTION_FOR +
        ` returning information about any Value's changes during a ` +
        TRANSACTION_,
    );
    const hasValuesListenerType = addType(
      HAS + VALUES + LISTENER,
      `(${storeParam}, hasValues: boolean)` + RETURNS_VOID,
      getListenerTypeDoc(9, 0, 1),
    );
    const valuesListenerType = addType(
      VALUES + LISTENER,
      `(${storeParam}, getValueChange: ${getValueChangeType}${OR_UNDEFINED})` +
        RETURNS_VOID,
      getListenerTypeDoc(9),
    );
    const valueIdsListenerType = addType(
      VALUE_IDS + LISTENER,
      `(${storeParam})` + RETURNS_VOID,
      getListenerTypeDoc(10),
    );
    const hasValueListenerType = addType(
      HAS + VALUE + LISTENER,
      `(${storeParam}, valueId: ValueId, hasValue: boolean)` + RETURNS_VOID,
      getListenerTypeDoc(11, 0, 1),
    );
    const valueListenerArgsArrayType = addType(
      'ValueListenerArgsArray',
      `VId extends ${valueIdType} ? [${storeParam}, valueId: VId, newValue: ${valueType}<VId> ${OR_UNDEFINED}, oldValue: ${valueType}<VId> ${OR_UNDEFINED}, getValueChange: ${getValueChangeType} ${OR_UNDEFINED}] : never`,
      'Value args for ValueListener',
      `<VId = ${valueIdType}>`,
      0,
    );
    const valueListenerType = addType(
      VALUE + LISTENER,
      `(...[${storeInstance}, valueId, newValue, oldValue, getValueChange]: ` +
        valueListenerArgsArrayType +
        ')' +
        RETURNS_VOID,
      getListenerTypeDoc(11),
    );
    const invalidValueListenerType = addType(
      INVALID + VALUE + LISTENER,
      `(${storeParam}, valueId: Id, invalidValues: any[])` + RETURNS_VOID,
      getListenerTypeDoc(12),
    );
    return [
      valuesType,
      valuesWhenSetType,
      valueIdType,
      valueType,
      valueCallbackType,
      hasValuesListenerType,
      valuesListenerType,
      valueIdsListenerType,
      hasValueListenerType,
      valueListenerType,
      invalidValueListenerType,
    ];
  };
  const getTransactionListenerType = (storeInstance, storeType) =>
    addType(
      TRANSACTION + LISTENER,
      `(${storeInstance}: ${storeType})` + RETURNS_VOID,
      A_FUNCTION_FOR + ' listening to the completion of a ' + TRANSACTION_,
    );
  return [getTablesTypes, getValuesTypes, getTransactionListenerType];
};

const storeMethod = (method, parameters = EMPTY_STRING, cast = EMPTY_STRING) =>
  `store.${method}(${parameters})` + (cast ? ' as ' + cast : EMPTY_STRING);
const fluentStoreMethod = (method, parameters = EMPTY_STRING) =>
  `fluent(() => ${storeMethod(method, parameters)})`;
const storeListener = (
  method,
  beforeParameters = EMPTY_STRING,
  afterParameters = EMPTY_STRING,
) =>
  `store.${method}(${beforeParameters ? beforeParameters + ', ' : EMPTY_STRING}proxy(listener)${afterParameters ? ', ' + afterParameters : EMPTY_STRING})`;
const getStoreCoreApi = (tablesSchema, valuesSchema, module) => {
  const [
    build,
    addImport,
    addType,
    addInternalFunction,
    addConstant,
    getImports,
    getTypes,
    getConstants,
  ] = getCodeFunctions();
  const [mapTablesSchema, mapCellSchema, mapValuesSchema] = getSchemaFunctions(
    tablesSchema,
    valuesSchema,
    addConstant,
  );
  const [getTablesTypes, getValuesTypes, getTransactionListenerType] =
    getTypeFunctions(addType, mapTablesSchema, mapCellSchema, mapValuesSchema);
  const methods = mapNew();
  const getMethods = (location = 0) =>
    mapMap(methods, ([parameters, returnType, body, doc, generic], name) => {
      const lines = location
        ? [name + `: ${generic}(${parameters}): ${returnType} => ${body},`]
        : [name + generic + `(${parameters}): ${returnType};`];
      if (!location) {
        arrayUnshift(lines, comment(doc));
      }
      arrayPush(lines, EMPTY_STRING);
      return lines;
    });
  const addMethod = (
    name,
    parameters,
    returnType,
    body,
    doc,
    generic = EMPTY_STRING,
  ) => mapUnique(methods, name, [parameters, returnType, body, doc, generic]);
  const addProxyMethod = (
    prefixVerb,
    prefix,
    underlyingName,
    returnType,
    doc,
    params = EMPTY_STRING,
    paramsInCall = EMPTY_STRING,
    generic = EMPTY_STRING,
  ) =>
    addMethod(
      METHOD_PREFIX_VERBS[prefixVerb] +
        prefix +
        (prefixVerb == 4 ? PARTIAL : EMPTY_STRING) +
        underlyingName,
      params,
      returnType,
      (returnType == storeType ? fluentStoreMethod : storeMethod)(
        METHOD_PREFIX_VERBS[prefixVerb] +
          (prefixVerb == 4 ? PARTIAL : EMPTY_STRING) +
          underlyingName,
        paramsInCall,
        prefixVerb ? void 0 : returnType,
      ),
      doc,
      generic,
    );
  const addProxyListener = (
    underlyingName,
    listenerType,
    doc,
    params = EMPTY_STRING,
    paramsInCall = EMPTY_STRING,
    mutator = 1,
    generic = EMPTY_STRING,
  ) =>
    addMethod(
      ADD + underlyingName + LISTENER,
      (params ? params + ', ' : EMPTY_STRING) +
        LISTENER_ +
        ': ' +
        listenerType +
        (mutator ? ', mutator?: boolean' : EMPTY_STRING),
      ID,
      storeListener(
        ADD + underlyingName + LISTENER,
        paramsInCall,
        mutator ? 'mutator' : EMPTY_STRING,
      ),
      doc,
      generic,
    );
  const moduleDefinition = `./${camel(module)}.d`;
  const storeType = camel(module, 1);
  const storeInstance = camel(storeType);
  const createSteps = [];
  const mapCellOrValueTypes = mapNew();
  let sharedTableTypes = [];
  let sharedValueTypes = [];
  addImport(
    1,
    moduleDefinition,
    storeType,
    `create${storeType} as create${storeType}Decl`,
  );
  if (!objIsEmpty(tablesSchema)) {
    addImport(0, TINYBASE, 'CellChange');
    addImport(null, TINYBASE, IDS);
    const [
      tablesType,
      tablesWhenSetType,
      tableIdType,
      tableType,
      tableWhenSetType,
      rowType,
      rowWhenSetType,
      cellIdType,
      _cellType,
      cellCallbackType,
      rowCallbackType,
      tableCellCallbackType,
      tableCallbackType,
      hasTablesListenerType,
      tablesListenerType,
      tableIdsListenerType,
      hasTableListenerType,
      tableListenerType,
      tableCellIdsListenerType,
      hasTableCellListenerType,
      rowCountListenerType,
      rowIdsListenerType,
      sortedRowIdsListenerType,
      hasRowListenerType,
      rowListenerType,
      cellIdsListenerType,
      hasCellListenerType,
      cellListenerType,
      invalidCellListenerType,
    ] = getTablesTypes(storeInstance, storeType);
    const tablesTypes = mapNew();
    mapTablesSchema((tableId, tableName) => {
      const tableIdGeneric = `<'${tableId}'>`;
      const tableTypes = [
        // Table
        addType(
          tableName + TABLE,
          tableType + tableIdGeneric,
          REPRESENTS + ` the '${tableId}' ` + TABLE,
        ),
        // TableWhenSet
        addType(
          tableName + TABLE + WHEN_SET,
          tableWhenSetType + tableIdGeneric,
          REPRESENTS + ` the '${tableId}' ` + TABLE + WHEN_SETTING_IT,
        ),
        // Row
        addType(
          tableName + ROW,
          rowType + tableIdGeneric,
          getRowTypeDoc(tableId),
        ),
        // RowWhenSet
        addType(
          tableName + ROW + WHEN_SET,
          rowWhenSetType + tableIdGeneric,
          getRowTypeDoc(tableId, 1),
        ),
        // CellId
        addType(
          tableName + CELL + ID,
          cellIdType + tableIdGeneric,
          `A Cell Id for the '${tableId}' ` + TABLE,
        ),
        // CellCallback
        addType(
          tableName + CELL + CALLBACK,
          cellCallbackType + tableIdGeneric,
          getCallbackDoc(
            `a Cell Id and value from a Row in the '${tableId}' ` + TABLE,
          ),
        ),
        // RowCallback
        addType(
          tableName + ROW + CALLBACK,
          rowCallbackType + tableIdGeneric,
          getCallbackDoc(
            `a Row Id from the '${tableId}' Table, and a Cell iterator`,
          ),
        ),
        // TableCellCallbackType
        addType(
          tableName + TABLE + CELL + CALLBACK,
          tableCellCallbackType + tableIdGeneric,
          getCallbackDoc(
            `a Cell Id from anywhere in the '${tableId}' Table, and a count of how many times it appears`,
          ),
        ),
      ];
      mapSet(tablesTypes, tableId, tableTypes);
      addImport(1, moduleDefinition, ...tableTypes);
    });
    addImport(
      1,
      moduleDefinition,
      tablesType,
      tablesWhenSetType,
      tableIdType,
      cellIdType,
      tableCallbackType,
      hasTablesListenerType,
      tablesListenerType,
      tableIdsListenerType,
      hasTableListenerType,
      tableListenerType,
      tableCellIdsListenerType,
      hasTableCellListenerType,
      rowCountListenerType,
      rowIdsListenerType,
      sortedRowIdsListenerType,
      hasRowListenerType,
      rowListenerType,
      cellIdsListenerType,
      hasCellListenerType,
      cellListenerType,
      invalidCellListenerType,
    );
    sharedTableTypes = [
      tablesType,
      tablesWhenSetType,
      tableIdType,
      cellIdType,
      hasTablesListenerType,
      tablesListenerType,
      tableIdsListenerType,
      hasTableListenerType,
      tableListenerType,
      tableCellIdsListenerType,
      hasTableCellListenerType,
      rowCountListenerType,
      rowIdsListenerType,
      sortedRowIdsListenerType,
      hasRowListenerType,
      rowListenerType,
      cellIdsListenerType,
      hasCellListenerType,
      cellListenerType,
      tablesTypes,
    ];
    arrayForEach(
      [
        [tablesType],
        [BOOLEAN],
        [storeType, 'tables: ' + tablesWhenSetType, 'tables'],
        [storeType],
      ],
      ([returnType, params, paramsInCall], verb) =>
        addProxyMethod(
          verb,
          EMPTY_STRING,
          TABLES,
          returnType,
          getTheContentOfTheStoreDoc(1, verb),
          params,
          paramsInCall,
        ),
    );
    addProxyMethod(
      0,
      EMPTY_STRING,
      TABLE_IDS,
      tableIdType + SQUARE_BRACKETS,
      getIdsDoc(TABLE, THE_STORE),
    );
    addProxyMethod(
      5,
      EMPTY_STRING,
      TABLE,
      VOID,
      getForEachDoc(TABLE, THE_STORE),
      'tableCallback: ' + tableCallbackType,
      'tableCallback as any',
    );
    mapTablesSchema((tableId, tableName, TABLE_ID) => {
      const [
        tableType2,
        tableWhenSetType2,
        rowType2,
        rowWhenSetType2,
        cellIdType2,
        cellCallbackType2,
        rowCallbackType2,
        tableCellCallbackType2,
      ] = mapGet(tablesTypes, tableId);
      arrayForEach(
        [
          [tableType2],
          [BOOLEAN],
          [storeType, 'table: ' + tableWhenSetType2, ', table'],
          [storeType],
        ],
        ([returnType, params, paramsInCall = EMPTY_STRING], verb) =>
          addProxyMethod(
            verb,
            tableName,
            TABLE,
            returnType,
            getTableContentDoc(tableId, verb),
            params,
            TABLE_ID + paramsInCall,
          ),
      );
      addProxyMethod(
        0,
        tableName,
        TABLE + CELL_IDS,
        IDS,
        getIdsDoc(CELL, 'the whole of ' + getTableDoc(tableId)),
        EMPTY_STRING,
        TABLE_ID,
      );
      addProxyMethod(
        5,
        tableName,
        TABLE + CELL,
        VOID,
        getForEachDoc(TABLE + CELL, 'the whole of ' + getTableDoc(tableId)),
        'tableCellCallback: ' + tableCellCallbackType2,
        TABLE_ID + ', tableCellCallback as any',
      );
      addProxyMethod(
        0,
        tableName,
        ROW + COUNT,
        'number',
        'Gets the number of Rows in the ' + getTableDoc(tableId),
        EMPTY_STRING,
        TABLE_ID,
      );
      addProxyMethod(
        0,
        tableName,
        ROW_IDS,
        IDS,
        getIdsDoc(ROW, getTableDoc(tableId)),
        EMPTY_STRING,
        TABLE_ID,
      );
      addProxyMethod(
        0,
        tableName,
        SORTED_ROW_IDS,
        IDS,
        getIdsDoc(ROW, getTableDoc(tableId), 1),
        'cellId?: ' + cellIdType2 + SORTED_ARGS,
        TABLE_ID + ', cellId, descending, offset, limit',
      );
      addProxyMethod(
        5,
        tableName,
        ROW,
        VOID,
        getForEachDoc(ROW, getTableDoc(tableId)),
        'rowCallback: ' + rowCallbackType2,
        TABLE_ID + ', rowCallback as any',
      );
      arrayForEach(
        [
          [rowType2],
          [BOOLEAN],
          [storeType, ', row: ' + rowWhenSetType2, ', row'],
          [storeType],
          [storeType, ', partialRow: ' + rowWhenSetType2, ', partialRow'],
        ],
        (
          [returnType, params = EMPTY_STRING, paramsInCall = EMPTY_STRING],
          verb,
        ) =>
          addProxyMethod(
            verb,
            tableName,
            ROW,
            returnType,
            getRowContentDoc(tableId, verb),
            ROW_ID_PARAM + params,
            TABLE_ID + ', rowId' + paramsInCall,
          ),
      );
      addProxyMethod(
        6,
        tableName,
        ROW,
        ID + OR_UNDEFINED,
        'Add a new Row to ' + getTableDoc(tableId),
        'row: ' + rowWhenSetType2 + ', reuseIds?: boolean',
        TABLE_ID + ', row, reuseIds',
      );
      addProxyMethod(
        0,
        tableName,
        CELL_IDS,
        cellIdType2 + SQUARE_BRACKETS,
        getIdsDoc(CELL, getRowDoc(tableId)),
        ROW_ID_PARAM,
        TABLE_ID + ', rowId',
      );
      addProxyMethod(
        5,
        tableName,
        CELL,
        VOID,
        getForEachDoc(CELL, getRowDoc(tableId)),
        ROW_ID_PARAM + ', cellCallback: ' + cellCallbackType2,
        TABLE_ID + ', rowId, cellCallback as any',
      );
      mapCellSchema(
        tableId,
        (cellId, type, defaultValue, CELL_ID, cellName) => {
          const mapCellType = 'Map' + camel(type, 1);
          mapSet(mapCellOrValueTypes, type, mapCellType);
          const returnCellType =
            type + (isUndefined(defaultValue) ? OR_UNDEFINED : EMPTY_STRING);
          arrayForEach(
            [
              [returnCellType],
              [BOOLEAN],
              [storeType, `, cell: ${type} | ` + mapCellType, ', cell as any'],
              [storeType],
            ],
            (
              [returnType, params = EMPTY_STRING, paramsInCall = EMPTY_STRING],
              verb,
            ) =>
              addProxyMethod(
                verb,
                tableName + cellName,
                CELL,
                returnType,
                getCellContentDoc(tableId, cellId, verb),
                ROW_ID_PARAM + params,
                TABLE_ID + ', rowId, ' + CELL_ID + paramsInCall,
              ),
          );
          addProxyMethod(
            1,
            tableName + cellName,
            TABLE + CELL,
            BOOLEAN,
            VERBS[1] +
              ` the '${cellId}' Cell anywhere in ` +
              getTableDoc(tableId),
            EMPTY_STRING,
            TABLE_ID + ', ' + CELL_ID,
          );
        },
      );
    });
    addProxyMethod(
      0,
      EMPTY_STRING,
      TABLES + JSON$1,
      JSON$1,
      getTheContentOfTheStoreDoc(1, 6),
    );
    addProxyMethod(
      2,
      EMPTY_STRING,
      TABLES + JSON$1,
      storeType,
      getTheContentOfTheStoreDoc(1, 7),
      'tablesJson: ' + JSON$1,
      'tables' + JSON$1,
    );
    addProxyListener(
      HAS + TABLES,
      hasTablesListenerType,
      getTheContentOfTheStoreDoc(1, 8, 0, 1) + ' changes',
    );
    addProxyListener(
      TABLES,
      tablesListenerType,
      getTheContentOfTheStoreDoc(1, 8) + ' changes',
    );
    addProxyListener(TABLE_IDS, tableIdsListenerType, getListenerDoc(2, 0, 1));
    addProxyListener(
      HAS + TABLE,
      hasTableListenerType,
      getListenerDoc(3, 0, 0, 1),
      `tableId: ${tableIdType} | null`,
      'tableId',
    );
    addProxyListener(
      TABLE,
      tableListenerType,
      getListenerDoc(3, 0),
      `tableId: ${tableIdType} | null`,
      'tableId',
    );
    addProxyListener(
      TABLE + CELL_IDS,
      tableCellIdsListenerType,
      getListenerDoc(14, 3, 1),
      `tableId: ${tableIdType} | null`,
      'tableId',
    );
    addProxyListener(
      HAS + TABLE + CELL,
      hasTableCellListenerType,
      getListenerDoc(16, 3, 0, 1),
      `tableId: ${tableIdType} | null, cellId: ${arrayJoin(
        mapTablesSchema(
          (tableId) => mapGet(tablesTypes, tableId)?.[4] ?? EMPTY_STRING,
        ),
        ' | ',
      )} | null`,
      'tableId,  cellId',
    );
    addProxyListener(
      ROW + COUNT,
      rowCountListenerType,
      getListenerDoc(15, 3),
      `tableId: ${tableIdType} | null`,
      'tableId',
    );
    addProxyListener(
      ROW_IDS,
      rowIdsListenerType,
      getListenerDoc(4, 3, 1),
      `tableId: ${tableIdType} | null`,
      'tableId',
    );
    addProxyListener(
      SORTED_ROW_IDS,
      sortedRowIdsListenerType,
      getListenerDoc(13, 3, 1),
      getParameterList(
        'tableId: TId',
        `cellId: ${cellIdType}<TId>` + OR_UNDEFINED,
        'descending: boolean',
        'offset: number',
        'limit: number' + OR_UNDEFINED,
      ),
      getParameterList('tableId', 'cellId', 'descending', 'offset', 'limit'),
      1,
      '<TId extends TableId>',
    );
    addProxyListener(
      HAS + ROW,
      hasRowListenerType,
      getListenerDoc(5, 3, 0, 1),
      `tableId: ${tableIdType} | null, rowId: IdOrNull`,
      'tableId, rowId',
    );
    addProxyListener(
      ROW,
      rowListenerType,
      getListenerDoc(5, 3),
      `tableId: ${tableIdType} | null, rowId: IdOrNull`,
      'tableId, rowId',
    );
    addProxyListener(
      CELL_IDS,
      cellIdsListenerType,
      getListenerDoc(6, 5, 1),
      `tableId: ${tableIdType} | null, rowId: IdOrNull`,
      'tableId, rowId',
    );
    addProxyListener(
      HAS + CELL,
      hasCellListenerType,
      getListenerDoc(7, 5, 0, 1),
      `tableId: ${tableIdType} | null, rowId: IdOrNull, cellId: ${arrayJoin(
        mapTablesSchema(
          (tableId) => mapGet(tablesTypes, tableId)?.[4] ?? EMPTY_STRING,
        ),
        ' | ',
      )} | null`,
      'tableId, rowId, cellId',
    );
    addProxyListener(
      CELL,
      cellListenerType,
      getListenerDoc(7, 5),
      `tableId: ${tableIdType} | null, rowId: IdOrNull, cellId: ${arrayJoin(
        mapTablesSchema(
          (tableId) => mapGet(tablesTypes, tableId)?.[4] ?? EMPTY_STRING,
        ),
        ' | ',
      )} | null`,
      'tableId, rowId, cellId',
    );
    addProxyListener(
      INVALID + CELL,
      invalidCellListenerType,
      REGISTERS_A_LISTENER + ' whenever an invalid Cell change was attempted',
      `tableId: IdOrNull, rowId: IdOrNull, cellId: IdOrNull`,
      'tableId, rowId, cellId',
    );
    addImport(1, moduleDefinition, ...collValues(mapCellOrValueTypes));
    arrayPush(
      createSteps,
      '.set' + TABLES + SCHEMA + '({',
      flat(
        mapTablesSchema((tableId, _, TABLE_ID) => [
          `[${TABLE_ID}]: {`,
          ...mapCellSchema(
            tableId,
            (_2, type, defaultValue, CELL_ID) =>
              `[${CELL_ID}]: {[${addConstant(
                snake(TYPE),
                `'${TYPE}'`,
              )}]: ${addConstant(snake(type), `'${type}'`)}${isUndefined(defaultValue) ? EMPTY_STRING : `, [${addConstant(snake(DEFAULT), `'${DEFAULT}'`)}]: ` + (isString(defaultValue) ? addConstant(snake(defaultValue), `'${defaultValue}'`) : defaultValue)}},`,
          ),
          `},`,
        ]),
      ),
      '})',
    );
  } else {
    addImport(null, TINYBASE, TABLES);
  }
  if (!objIsEmpty(valuesSchema)) {
    const [
      valuesType,
      valuesWhenSetType,
      valueIdType,
      _valueType,
      valueCallbackType,
      hasValuesListenerType,
      valuesListenerType,
      valueIdsListenerType,
      hasValueListenerType,
      valueListenerType,
      invalidValueListenerType,
    ] = getValuesTypes(storeInstance, storeType);
    addImport(
      1,
      moduleDefinition,
      valuesType,
      valuesWhenSetType,
      valueIdType,
      valueCallbackType,
      hasValuesListenerType,
      valuesListenerType,
      valueIdsListenerType,
      hasValueListenerType,
      valueListenerType,
      invalidValueListenerType,
    );
    sharedValueTypes = [
      valuesType,
      valuesWhenSetType,
      valueIdType,
      hasValuesListenerType,
      valuesListenerType,
      valueIdsListenerType,
      hasValueListenerType,
      valueListenerType,
    ];
    arrayForEach(
      [
        [valuesType],
        [BOOLEAN],
        [storeType, 'values: ' + valuesWhenSetType, 'values'],
        [storeType],
        [storeType, 'partialValues: ' + valuesWhenSetType, 'partialValues'],
      ],
      ([returnType, params, paramsInCall], verb) =>
        addProxyMethod(
          verb,
          EMPTY_STRING,
          VALUES,
          returnType,
          getTheContentOfTheStoreDoc(2, verb),
          params,
          paramsInCall,
        ),
    );
    addProxyMethod(
      0,
      EMPTY_STRING,
      VALUE_IDS,
      valueIdType + SQUARE_BRACKETS,
      getIdsDoc(VALUE, THE_STORE),
    );
    addProxyMethod(
      5,
      EMPTY_STRING,
      VALUE,
      `void`,
      getForEachDoc(VALUE, THE_STORE),
      `valueCallback: ${valueCallbackType}`,
      'valueCallback as any',
    );
    mapValuesSchema((valueId, type, _, VALUE_ID, valueName) => {
      const mapValueType = 'Map' + camel(type, 1);
      mapSet(mapCellOrValueTypes, type, mapValueType);
      arrayForEach(
        [
          [type],
          [BOOLEAN],
          [storeType, `value: ${type} | ` + mapValueType, ', value as any'],
          [storeType],
        ],
        ([returnType, params, paramsInCall = EMPTY_STRING], verb) =>
          addProxyMethod(
            verb,
            valueName,
            VALUE,
            returnType,
            getValueContentDoc(valueId, verb),
            params,
            VALUE_ID + paramsInCall,
          ),
      );
    });
    addProxyMethod(
      0,
      EMPTY_STRING,
      VALUES + JSON$1,
      JSON$1,
      getTheContentOfTheStoreDoc(2, 6),
    );
    addProxyMethod(
      2,
      EMPTY_STRING,
      VALUES + JSON$1,
      storeType,
      getTheContentOfTheStoreDoc(2, 7),
      'valuesJson: ' + JSON$1,
      'values' + JSON$1,
    );
    addProxyListener(
      HAS + VALUES,
      hasValuesListenerType,
      getTheContentOfTheStoreDoc(2, 8, 0, 1) + ' changes',
    );
    addProxyListener(
      VALUES,
      valuesListenerType,
      getTheContentOfTheStoreDoc(2, 8) + ' changes',
    );
    addProxyListener(VALUE_IDS, valueIdsListenerType, getListenerDoc(10, 0, 1));
    addProxyListener(
      HAS + VALUE,
      hasValueListenerType,
      getListenerDoc(11, 0, 0, 1),
      `valueId: ${valueIdType} | null`,
      'valueId',
    );
    addProxyListener(
      VALUE,
      valueListenerType,
      getListenerDoc(11, 0),
      `valueId: ${valueIdType} | null`,
      'valueId',
    );
    addProxyListener(
      INVALID + VALUE,
      invalidValueListenerType,
      REGISTERS_A_LISTENER + ' whenever an invalid Value change was attempted',
      `valueId: IdOrNull`,
      'valueId',
    );
    addImport(1, moduleDefinition, ...collValues(mapCellOrValueTypes));
    addImport(0, TINYBASE, 'ValueChange');
    arrayPush(
      createSteps,
      '.set' + VALUES + SCHEMA + '({',
      mapValuesSchema((_, type, defaultValue, VALUE_ID) => [
        `[${VALUE_ID}]: {[${addConstant(
          snake(TYPE),
          `'${TYPE}'`,
        )}]: ${addConstant(snake(type), `'${type}'`)}${isUndefined(defaultValue) ? EMPTY_STRING : `, [${addConstant(snake(DEFAULT), `'${DEFAULT}'`)}]: ` + (isString(defaultValue) ? addConstant(snake(defaultValue), `'${defaultValue}'`) : defaultValue)}},`,
      ]),
      '})',
    );
  } else {
    addImport(null, TINYBASE, VALUES);
  }
  addProxyMethod(
    0,
    EMPTY_STRING,
    'Content',
    `[${TABLES}, ${VALUES}]`,
    getTheContentOfTheStoreDoc(0, 0),
  );
  addProxyMethod(
    2,
    EMPTY_STRING,
    'Content',
    storeType,
    getTheContentOfTheStoreDoc(0, 2),
    `[tables, values]: [${TABLES}, ${VALUES}]`,
    '[tables, values]',
  );
  addProxyMethod(
    7,
    EMPTY_STRING,
    'applyChanges',
    storeType,
    `Applies a set of ${CHANGES} to the Store`,
    'changes: ' + CHANGES,
    'changes',
  );
  mapForEach(mapCellOrValueTypes, (type, mapCellType) =>
    addType(
      mapCellType,
      `(cell: ${type}${OR_UNDEFINED}) => ` + type,
      `Takes a ${type} Cell value and returns another`,
    ),
  );
  addImport(
    null,
    TINYBASE,
    'DoRollback',
    ID,
    'IdOrNull',
    JSON$1,
    'Store',
    CHANGES,
  );
  addProxyMethod(
    0,
    EMPTY_STRING,
    JSON$1,
    JSON$1,
    getTheContentOfTheStoreDoc(0, 6),
  );
  addProxyMethod(
    2,
    EMPTY_STRING,
    JSON$1,
    storeType,
    getTheContentOfTheStoreDoc(0, 7),
    'tablesAndValuesJson: ' + JSON$1,
    'tablesAndValuesJson',
  );
  addProxyMethod(
    7,
    EMPTY_STRING,
    TRANSACTION_,
    'Return',
    TRANSACTION_DOC,
    DO_ACTIONS_AND_ROLLBACK_PARAMS,
    'actions, doRollback',
    '<Return>',
  );
  addProxyMethod(
    7,
    EMPTY_STRING,
    'start' + TRANSACTION,
    storeType,
    START_TRANSACTION_DOC,
  );
  addProxyMethod(
    7,
    EMPTY_STRING,
    'finish' + TRANSACTION,
    storeType,
    FINISH_TRANSACTION_DOC,
    DO_ROLLBACK_PARAM,
    'doRollback',
  );
  const transactionListenerType = getTransactionListenerType(
    storeInstance,
    storeType,
  );
  addProxyListener(
    'Start' + TRANSACTION,
    transactionListenerType,
    REGISTERS_A_LISTENER + ' just before the start of the ' + TRANSACTION_,
    EMPTY_STRING,
    EMPTY_STRING,
    0,
  );
  addProxyListener(
    'WillFinish' + TRANSACTION,
    transactionListenerType,
    REGISTERS_A_LISTENER + ' just before ' + THE_END_OF_THE_TRANSACTION,
    EMPTY_STRING,
    EMPTY_STRING,
    0,
  );
  addProxyListener(
    'DidFinish' + TRANSACTION,
    transactionListenerType,
    REGISTERS_A_LISTENER + ' just after ' + THE_END_OF_THE_TRANSACTION,
    EMPTY_STRING,
    EMPTY_STRING,
    0,
  );
  addProxyMethod(
    7,
    EMPTY_STRING,
    'call' + LISTENER,
    storeType,
    'Manually provoke a listener to be called',
    'listenerId: Id',
    'listenerId',
  );
  addProxyMethod(
    3,
    EMPTY_STRING,
    LISTENER,
    storeType,
    'Remove a listener that was previously added to ' + THE_STORE,
    'listenerId: Id',
    'listenerId',
  );
  addMethod(
    'getStore',
    EMPTY_STRING,
    'Store',
    'store',
    VERBS[0] + ' the underlying Store object',
  );
  addImport(1, TINYBASE, 'createStore');
  addImport(
    1,
    moduleDefinition,
    storeType,
    `create${storeType} as create${storeType}Decl`,
    transactionListenerType,
  );
  addConstant('store', ['createStore()', ...createSteps]);
  addInternalFunction('fluent', 'actions: () => Store', [
    'actions();',
    `return ${storeInstance};`,
  ]);
  addInternalFunction(
    'proxy',
    `listener: any`,
    `(_: Store, ...params: any[]) => listener(${storeInstance}, ...params)`,
  );
  addConstant(storeInstance, ['{', ...getMethods(1), '}']);
  return [
    build(
      ...getImports(0),
      ...getTypes(),
      EXPORT + ' interface ' + storeType + ' {',
      ...getMethods(0),
      '}',
      EMPTY_STRING,
      comment(`Creates a ${storeType} object`),
      EXPORT + ' function create' + storeType + '(): ' + storeType + ';',
    ),
    build(
      ...getImports(1),
      EXPORT +
        ' const create' +
        storeType +
        ': typeof create' +
        storeType +
        'Decl = () => {',
      ...getConstants(),
      `return Object.freeze(${storeInstance});`,
      '};',
    ),
    sharedTableTypes,
    sharedValueTypes,
  ];
};

const getGet = (noun) => GET + noun;
const getGetAndGetDeps = (noun) =>
  getParameterList(getGet(noun), getGet(noun) + DEPS);
const DEBUG_IDS_PROP_TYPE = 'debugIds?: boolean';
const DEBUG_IDS_PROP = 'debugIds={debugIds}';
const THEN_DEPS = 'then' + DEPS_SUFFIX;
const PARAMETER = 'Parameter';
const GETTER_ARGS = ': (parameter: ' + PARAMETER + ', store: Store) => ';
const USE_CONTEXT = 'const contextValue = useContext(Context);';
const BASED_ON_A_PARAMETER = ', based on a parameter';
const COLON_SPACE = ': ';
const GENERIC_PARAMETER = '<' + PARAMETER + ',>';
const PARAMETERIZED_CALLBACK =
  PARAMETER + 'ized' + CALLBACK + '<' + PARAMETER + '>';
const ROW_ID = 'rowId';
const ROW_ID_PROP = 'rowId={rowId}';
const SEPARATOR_AND_DEBUG_IDS = ', separator, debugIds';
const SEPARATOR_PROP_TYPE = 'separator?: ReactElement | string';
const THEN_PREFIX = 'then?: (store: Store';
const THEN_AND_THEN_DEPS = getParameterList(
  THEN_PREFIX + ')' + RETURNS_VOID,
  THEN_DEPS,
);
const THEN_AND_THEN_DEPS_IN_CALL = 'then, then' + DEPS;
const TYPED_ROW_ID = ROW_ID + COLON_SPACE + ID;
const VIEW = 'View';
const getListenerHookParams = (listenerType, ...extraParams) =>
  getParameterList(
    ...extraParams,
    LISTENER_ + ': ' + listenerType,
    LISTENER_ + DEPS_SUFFIX,
    'mutator?: boolean',
  );
const getListenerHookParamsInCall = (...extraParams) =>
  getParameterList(...extraParams, LISTENER_, LISTENER_ + DEPS, 'mutator');
const getStoreUiReactApi = (
  tablesSchema,
  valuesSchema,
  module,
  sharedTableTypes,
  sharedValueTypes,
) => {
  const [
    build,
    addImport,
    addType,
    addInternalFunction,
    addConstant,
    getImports,
    getTypes,
    getConstants,
  ] = getCodeFunctions();
  const [mapTablesSchema, mapCellSchema, mapValuesSchema] = getSchemaFunctions(
    tablesSchema,
    valuesSchema,
    addConstant,
  );
  const moduleDefinition = `./${camel(module)}.d`;
  const uiReactModuleDefinition = `./${camel(module)}-ui-react.d`;
  const tinyBaseUiReact = 'tinybase/ui-react';
  const storeType = camel(module, 1);
  const storeInstance = camel(storeType);
  const StoreOrStoreId = storeType + 'Or' + storeType + ID;
  const storeOrStoreId = storeInstance + 'Or' + storeType + ID;
  const storeProp = storeInstance + `={${storeInstance}}`;
  const functions = mapNew();
  const addFunction = (
    name,
    parameters,
    returnType,
    body,
    doc,
    generic = EMPTY_STRING,
  ) => {
    addImport(1, uiReactModuleDefinition, name + ' as ' + name + 'Decl');
    return mapUnique(functions, name, [
      parameters,
      returnType,
      body,
      doc,
      generic,
    ]);
  };
  const addHook = (
    name,
    parameters,
    returnType,
    body,
    doc,
    generic = EMPTY_STRING,
  ) => addFunction(`use${name}`, parameters, returnType, body, doc, generic);
  const addProxyHook = (
    name,
    underlyingName,
    returnType,
    doc,
    preParameters = EMPTY_STRING,
    preParametersInCall = EMPTY_STRING,
    generic = EMPTY_STRING,
    postParameters = EMPTY_STRING,
    postParametersInCall = EMPTY_STRING,
  ) => {
    addImport(
      1,
      tinyBaseUiReact,
      `use${underlyingName} as use${underlyingName}Core`,
    );
    return addHook(
      name,
      getParameterList(preParameters, storeOrStoreIdParameter, postParameters),
      returnType,
      useHook +
        `(${storeOrStoreId}, use${underlyingName}Core, [` +
        (preParametersInCall ? preParametersInCall : EMPTY_STRING) +
        (postParametersInCall ? '], [' + postParametersInCall : EMPTY_STRING) +
        ']);',
      doc,
      generic,
    );
  };
  const addComponent = (name, parameters, body, doc) =>
    addFunction(name, parameters, 1, body, doc);
  const getFunctions = (location = 0) =>
    mapMap(functions, ([parameters, returnType, body, doc, generic], name) => {
      const lines = location
        ? [
            EXPORT +
              ` const ${name}: typeof ${name}Decl = ${generic}(${parameters}): ${returnType == 1 ? 'any' : returnType} =>`,
            body,
          ]
        : [
            EXPORT +
              ` function ${name}${generic}(${parameters}): ${returnType == 1 ? 'ComponentReturnType' : returnType};`,
          ];
      if (!location) {
        arrayUnshift(lines, comment(doc));
      }
      arrayPush(lines, EMPTY_STRING);
      return lines;
    });
  addImport(
    null,
    TINYBASE,
    ID,
    'Store',
    CALLBACK,
    PARAMETER + 'ized' + CALLBACK,
  );
  addImport(0, tinyBaseUiReact, 'ComponentReturnType');
  addImport(1, tinyBaseUiReact, 'useCellIds');
  addImport(null, tinyBaseUiReact, 'ExtraProps');
  addImport(0, moduleDefinition, storeType);
  const storeOrStoreIdType = addType(
    StoreOrStoreId,
    storeType + ' | ' + ID,
    `Used when you need to refer to a ${storeType} in a React hook or component`,
  );
  const providerPropsType = addType(
    PROVIDER + PROPS,
    getPropTypeList(
      storeInstance + OPTIONAL_COLON + storeType,
      storeInstance + `ById?: {[${storeInstance}Id: Id]: ${storeType}}`,
    ),
    `Used with the ${PROVIDER} component, so that a ` +
      storeType +
      ' can be passed into the context of an application',
  );
  addImport(0, 'react', 'ReactElement', 'ComponentType');
  addImport(1, 'react', 'React');
  addImport(1, uiReactModuleDefinition, storeOrStoreIdType, providerPropsType);
  const storeOrStoreIdParameter =
    storeOrStoreId + OPTIONAL_COLON + storeOrStoreIdType;
  addConstant('{createContext, useContext, useMemo}', 'React');
  addConstant(
    'Context',
    `createContext<[${storeType}?, {[${storeInstance}Id: Id]: ${storeType}}?]>([])`,
  );
  addHook(
    `Create${storeType}`,
    `create: () => ${storeType}, create` + DEPS_SUFFIX,
    storeType,
    '\n// eslint-disable-next-line react-hooks/exhaustive-deps\nuseMemo(create, createDeps)',
    `Create a ${storeType} within a React application with convenient memoization`,
  );
  const getStoreHook = addHook(
    storeType,
    `id?: Id`,
    storeType + OR_UNDEFINED,
    [
      '{',
      USE_CONTEXT,
      'return id == null ? contextValue[0] : contextValue[1]?.[id];',
      '}',
    ],
    `Get a reference to a ${storeType} from within a ${PROVIDER} component context`,
  );
  const useHook = addInternalFunction(
    `useHook`,
    storeOrStoreId +
      `: ${storeOrStoreIdType} | undefined, hook: (...params: any[]) => any, preParams: any[], postParams: any[] = []`,
    [
      `const ${storeInstance} = ${getStoreHook}(${storeOrStoreId} as Id);`,
      `return hook(...preParams, ((${storeOrStoreId} == null || typeof ${storeOrStoreId} == 'string')`,
      `? ${storeInstance} : ${storeOrStoreId})?.getStore(), ...postParams)`,
    ],
  );
  const getProps = addInternalFunction(
    'getProps',
    'getProps: ((id: any) => ExtraProps) | undefined, id: Id',
    '(getProps == null) ? ({} as ExtraProps) : getProps(id)',
  );
  const wrap = addInternalFunction(
    'wrap',
    getParameterList(
      'children: any',
      'separator?: any',
      'encloseWithId?: boolean',
      'id?: Id',
    ),
    [
      'const separated = separator==null || !Array.isArray(children)',
      ' ? children',
      ' : children.map((child, c) => (c > 0 ? [separator, child] : child));',
      `return encloseWithId ? [id, ':{', separated, '}'] : separated;`,
    ],
  );
  const useCustomOrDefaultCellIds = addInternalFunction(
    'useCustomOrDefaultCellIds',
    getParameterList(
      'customCellIds: Ids | undefined',
      'tableId: Id',
      'rowId: Id',
      `${storeOrStoreId}?: ${storeOrStoreIdType} | undefined`,
    ),
    [
      `const defaultCellIds = ${useHook}(${storeOrStoreId}, useCellIds, [tableId, rowId]);`,
      'return customCellIds ?? defaultCellIds;',
    ],
  );
  const NullComponent = addConstant('NullComponent', `() => null`);
  if (!objIsEmpty(tablesSchema)) {
    const [
      tablesType,
      tablesWhenSetType,
      tableIdType,
      cellIdType,
      hasTablesListenerType,
      tablesListenerType,
      tableIdsListenerType,
      hasTableListenerType,
      tableListenerType,
      tableCellIdsListenerType,
      hasTableCellListenerType,
      rowCountListenerType,
      rowIdsListenerType,
      sortedRowIdsListenerType,
      hasRowListenerType,
      rowListenerType,
      cellIdsListenerType,
      hasCellListenerType,
      cellListenerType,
      tablesTypes,
    ] = sharedTableTypes;
    addImport(
      null,
      moduleDefinition,
      tablesType,
      tablesWhenSetType,
      tableIdType,
      hasTablesListenerType,
      tablesListenerType,
      tableIdsListenerType,
      hasTableListenerType,
      tableListenerType,
      tableCellIdsListenerType,
      hasTableCellListenerType,
      rowCountListenerType,
      rowIdsListenerType,
      sortedRowIdsListenerType,
      hasRowListenerType,
      rowListenerType,
      cellIdsListenerType,
      cellListenerType,
      hasCellListenerType,
    );
    addImport(0, moduleDefinition, cellIdType);
    addImport(1, moduleDefinition, storeType);
    addImport(null, TINYBASE, IDS, 'IdOrNull');
    const tableView = addInternalFunction(
      'tableView',
      `{${storeInstance}, rowComponent, getRowComponentProps, customCellIds` +
        SEPARATOR_AND_DEBUG_IDS +
        '}: any, rowIds: Ids, tableId: Id, defaultRowComponent: React.ComponentType<any>',
      [
        'const Row = rowComponent ?? defaultRowComponent;',
        `return ${wrap}(rowIds.map((rowId) => (`,
        '<Row',
        '{...' + getProps + '(getRowComponentProps, rowId)}',
        'key={rowId}',
        'tableId={tableId}',
        ROW_ID_PROP,
        'customCellIds={customCellIds}',
        storeProp,
        DEBUG_IDS_PROP,
        '/>',
        '))',
        SEPARATOR_AND_DEBUG_IDS,
        ', tableId,',
        ');',
      ],
    );
    const getDefaultTableComponent = addInternalFunction(
      'getDefaultTableComponent',
      'tableId: Id',
      arrayJoin(
        mapTablesSchema(
          (_, tableName, TABLE_ID) =>
            `tableId == ${TABLE_ID} ? ${tableName}TableView : `,
        ),
      ) + NullComponent,
    );
    const getDefaultCellComponent = addInternalFunction(
      'getDefaultCellComponent',
      'tableId: Id, cellId: Id',
      arrayJoin(
        mapTablesSchema(
          (tableId, tableName, TABLE_ID) =>
            `tableId == ${TABLE_ID} ? ${
              arrayJoin(
                mapCellSchema(
                  tableId,
                  (_, _2, _3, CELL_ID, cellName) =>
                    `cellId == ${CELL_ID} ? ` +
                    tableName +
                    cellName +
                    'CellView : ',
                ),
              ) + NullComponent
            } : `,
        ),
      ) + NullComponent,
    );
    addProxyHook(
      HAS + TABLES,
      HAS + TABLES,
      BOOLEAN,
      getTheContentOfTheStoreDoc(1, 0, 0, 1) + AND_REGISTERS,
    );
    addProxyHook(
      TABLES,
      TABLES,
      tablesType,
      getTheContentOfTheStoreDoc(1, 0) + AND_REGISTERS,
    );
    const useTableIds = addProxyHook(
      TABLE_IDS,
      TABLE_IDS,
      tableIdType + SQUARE_BRACKETS,
      getIdsDoc(TABLE, THE_STORE) + AND_REGISTERS,
    );
    addProxyHook(
      SET + TABLES + CALLBACK,
      SET + TABLES + CALLBACK,
      PARAMETERIZED_CALLBACK,
      getTheContentOfTheStoreDoc(1, 9) + BASED_ON_A_PARAMETER,
      getParameterList(
        getGet(TABLES) + GETTER_ARGS + tablesWhenSetType,
        getGet(TABLES) + DEPS_SUFFIX,
      ),
      getGetAndGetDeps(TABLES),
      GENERIC_PARAMETER,
      getParameterList(
        THEN_PREFIX,
        `tables: ${tablesWhenSetType})` + RETURNS_VOID,
        THEN_DEPS,
      ),
      THEN_AND_THEN_DEPS_IN_CALL,
    );
    addProxyHook(
      DEL + TABLES + CALLBACK,
      DEL + TABLES + CALLBACK,
      CALLBACK,
      getTheContentOfTheStoreDoc(1, 12),
      EMPTY_STRING,
      EMPTY_STRING,
      EMPTY_STRING,
      THEN_AND_THEN_DEPS,
      THEN_AND_THEN_DEPS_IN_CALL,
    );
    const cellPropsType = addType(
      CELL + PROPS,
      getPropTypeList(
        'tableId?: TId',
        'rowId: Id',
        'cellId?: CId',
        storeInstance + OPTIONAL_COLON + storeType,
        DEBUG_IDS_PROP_TYPE,
      ),
      getPropsDoc(A + CELL),
      `<TId extends ${tableIdType}, CId extends ${cellIdType}<TId>>`,
    );
    const rowPropsType = addType(
      ROW + PROPS,
      getPropTypeList(
        `tableId?: TId`,
        'rowId: Id',
        storeInstance + OPTIONAL_COLON + storeType,
        'cellComponents?: {readonly [CId in ' +
          cellIdType +
          `<TId>]?: ComponentType<${cellPropsType}<TId, CId>>;}`,
        `getCellComponentProps?: (cellId: ${cellIdType}<TId>) => ExtraProps`,
        `customCellIds?: ${cellIdType}<TId>[]`,
        SEPARATOR_PROP_TYPE,
        DEBUG_IDS_PROP_TYPE,
      ),
      getPropsDoc(A + ROW),
      `<TId extends ${tableIdType}>`,
    );
    const tablePropsType = addType(
      TABLE + PROPS,
      getPropTypeList(
        `tableId?: TId`,
        storeInstance + OPTIONAL_COLON + storeType,
        `rowComponent?: ComponentType<${rowPropsType}<TId>>`,
        `getRowComponentProps?: (rowId: Id) => ExtraProps`,
        `customCellIds?: CellId<TId>[]`,
        SEPARATOR_PROP_TYPE,
        DEBUG_IDS_PROP_TYPE,
      ),
      getPropsDoc(A + TABLE),
      `<TId extends ${tableIdType}>`,
    );
    const sortedTablePropsType = addType(
      'Sorted' + TABLE + PROPS,
      getPropTypeList(
        `tableId?: TId`,
        'cellId?: ' + cellIdType + '<TId>',
        'descending?: boolean',
        'offset?: number',
        'limit?: number',
        storeInstance + OPTIONAL_COLON + storeType,
        `rowComponent?: ComponentType<${rowPropsType}<TId>>`,
        `getRowComponentProps?: (rowId: Id) => ExtraProps`,
        `customCellIds?: CellId<TId>[]`,
        SEPARATOR_PROP_TYPE,
        DEBUG_IDS_PROP_TYPE,
      ),
      getPropsDoc(A + 'sorted ' + TABLE),
      `<TId extends ${tableIdType}>`,
    );
    const tablesPropsType = addType(
      TABLES + PROPS,
      getPropTypeList(
        storeInstance + OPTIONAL_COLON + storeType,
        'tableComponents?: {readonly [TId in ' +
          tableIdType +
          `]?: ComponentType<${tablePropsType}<TId>>;}`,
        `getTableComponentProps?: (tableId: ${tableIdType}) => ExtraProps`,
        SEPARATOR_PROP_TYPE,
        DEBUG_IDS_PROP_TYPE,
      ),
      getPropsDoc(getTheContentOfDoc(1, 1)),
    );
    addImport(
      1,
      uiReactModuleDefinition,
      tablesPropsType,
      tablePropsType,
      sortedTablePropsType,
      rowPropsType,
      cellPropsType,
    );
    addComponent(
      TABLES + VIEW,
      '{' +
        storeInstance +
        ', tableComponents, getTableComponentProps' +
        SEPARATOR_AND_DEBUG_IDS +
        '}: ' +
        tablesPropsType,
      [
        wrap + `(${useTableIds}(${storeInstance}).map((tableId) => {`,
        'const Table = (tableComponents?.[tableId] ?? ' +
          getDefaultTableComponent +
          `(tableId)) as React.ComponentType<TableProps<typeof tableId>>;`,
        'return <Table',
        `{...${getProps}(getTableComponentProps, tableId)}`,
        'tableId={tableId}',
        'key={tableId}',
        storeProp,
        DEBUG_IDS_PROP,
        '/>;',
        '}), separator)',
      ],
      getTheContentOfTheStoreDoc(1, 13) + AND_REGISTERS,
    );
    mapTablesSchema((tableId, tableName, TABLE_ID) => {
      const [
        tableType,
        tableWhenSetType,
        rowType,
        rowWhenSetType,
        cellIdType2,
      ] = mapGet(tablesTypes, tableId);
      addImport(
        null,
        moduleDefinition,
        tableType,
        tableWhenSetType,
        rowType,
        rowWhenSetType,
        cellIdType2,
      );
      addProxyHook(
        HAS + tableName + TABLE,
        HAS + TABLE,
        BOOLEAN,
        getTableContentDoc(tableId, 0, 1) + AND_REGISTERS,
        EMPTY_STRING,
        TABLE_ID,
      );
      addProxyHook(
        tableName + TABLE,
        TABLE,
        tableType,
        getTableContentDoc(tableId) + AND_REGISTERS,
        EMPTY_STRING,
        TABLE_ID,
      );
      addProxyHook(
        tableName + TABLE + CELL_IDS,
        TABLE + CELL_IDS,
        IDS,
        getIdsDoc(CELL, 'the whole of ' + getTableDoc(tableId)) + AND_REGISTERS,
        EMPTY_STRING,
        TABLE_ID,
      );
      addProxyHook(
        tableName + ROW + COUNT,
        ROW + COUNT,
        NUMBER,
        'Gets the number of Rows in ' + getTableDoc(tableId) + AND_REGISTERS,
        EMPTY_STRING,
        TABLE_ID,
      );
      const useRowIds = addProxyHook(
        tableName + ROW_IDS,
        ROW_IDS,
        IDS,
        getIdsDoc(ROW, getTableDoc(tableId)) + AND_REGISTERS,
        EMPTY_STRING,
        TABLE_ID,
      );
      const useSortedRowIds = addProxyHook(
        tableName + SORTED_ROW_IDS,
        SORTED_ROW_IDS,
        IDS,
        getIdsDoc(ROW, getTableDoc(tableId), 1) + AND_REGISTERS,
        'cellId?: ' +
          cellIdType2 +
          ', descending?: boolean, offset?: number, limit?: number',
        TABLE_ID + ', cellId, descending, offset, limit',
      );
      addProxyHook(
        HAS + tableName + ROW,
        HAS + ROW,
        BOOLEAN,
        getRowContentDoc(tableId, 0, 1) + AND_REGISTERS,
        TYPED_ROW_ID,
        getParameterList(TABLE_ID, ROW_ID),
      );
      addProxyHook(
        tableName + ROW,
        ROW,
        rowType,
        getRowContentDoc(tableId) + AND_REGISTERS,
        TYPED_ROW_ID,
        getParameterList(TABLE_ID, ROW_ID),
      );
      addProxyHook(
        tableName + CELL_IDS,
        CELL_IDS,
        cellIdType2 + SQUARE_BRACKETS,
        getIdsDoc(CELL, getRowDoc(tableId)) + AND_REGISTERS,
        TYPED_ROW_ID,
        getParameterList(TABLE_ID, ROW_ID),
      );
      addProxyHook(
        SET + tableName + TABLE + CALLBACK,
        SET + TABLE + CALLBACK,
        PARAMETERIZED_CALLBACK,
        getTableContentDoc(tableId, 9) + BASED_ON_A_PARAMETER,
        getParameterList(
          getGet(TABLE) + GETTER_ARGS + tableWhenSetType,
          getGet(TABLE) + DEPS_SUFFIX,
        ),
        getParameterList(TABLE_ID, getGetAndGetDeps(TABLE)),
        GENERIC_PARAMETER,
        getParameterList(
          THEN_PREFIX,
          `table: ${tableWhenSetType})` + RETURNS_VOID,
          THEN_DEPS,
        ),
        THEN_AND_THEN_DEPS_IN_CALL,
      );
      addProxyHook(
        DEL + tableName + TABLE + CALLBACK,
        DEL + TABLE + CALLBACK,
        CALLBACK,
        getTableContentDoc(tableId, 12),
        EMPTY_STRING,
        TABLE_ID,
        EMPTY_STRING,
        THEN_AND_THEN_DEPS,
        THEN_AND_THEN_DEPS_IN_CALL,
      );
      addProxyHook(
        SET + tableName + ROW + CALLBACK,
        SET + ROW + CALLBACK,
        PARAMETERIZED_CALLBACK,
        getRowContentDoc(tableId, 9) + BASED_ON_A_PARAMETER,
        getParameterList(
          TYPED_ROW_ID,
          getGet(ROW) + GETTER_ARGS + rowWhenSetType,
          getGet(ROW) + DEPS_SUFFIX,
        ),
        getParameterList(TABLE_ID, ROW_ID, getGetAndGetDeps(ROW)),
        GENERIC_PARAMETER,
        getParameterList(
          THEN_PREFIX,
          `row: ${rowWhenSetType})` + RETURNS_VOID,
          THEN_DEPS,
        ),
        THEN_AND_THEN_DEPS_IN_CALL,
      );
      addProxyHook(
        'Add' + tableName + ROW + CALLBACK,
        'Add' + ROW + CALLBACK,
        PARAMETERIZED_CALLBACK,
        getRowContentDoc(tableId, 10) + BASED_ON_A_PARAMETER,
        getParameterList(
          getGet(ROW) + GETTER_ARGS + rowWhenSetType,
          getGet(ROW) + DEPS_SUFFIX,
        ),
        getParameterList(TABLE_ID, getGetAndGetDeps(ROW)),
        GENERIC_PARAMETER,
        'then?: (' +
          getParameterList(
            TYPED_ROW_ID + OR_UNDEFINED,
            'store: Store',
            'row: ' + rowWhenSetType + ')' + RETURNS_VOID,
            'then' + DEPS_SUFFIX,
          ) +
          ', reuseRowIds?: boolean',
        THEN_AND_THEN_DEPS_IN_CALL + ', reuseRowIds',
      );
      addProxyHook(
        SET + tableName + PARTIAL + ROW + CALLBACK,
        SET + PARTIAL + ROW + CALLBACK,
        PARAMETERIZED_CALLBACK,
        getRowContentDoc(tableId, 11) + BASED_ON_A_PARAMETER,
        getParameterList(
          TYPED_ROW_ID,
          getGet(PARTIAL + ROW) + GETTER_ARGS + rowWhenSetType,
          getGet(PARTIAL + ROW) + DEPS_SUFFIX,
        ),
        getParameterList(TABLE_ID, ROW_ID, getGetAndGetDeps(PARTIAL + ROW)),
        GENERIC_PARAMETER,
        getParameterList(
          THEN_PREFIX,
          `partialRow: ${rowWhenSetType})` + RETURNS_VOID,
          THEN_DEPS,
        ),
        THEN_AND_THEN_DEPS_IN_CALL,
      );
      addProxyHook(
        DEL + tableName + ROW + CALLBACK,
        DEL + ROW + CALLBACK,
        CALLBACK,
        getRowContentDoc(tableId, 12),
        TYPED_ROW_ID,
        getParameterList(TABLE_ID, ROW_ID),
        EMPTY_STRING,
        THEN_AND_THEN_DEPS,
        THEN_AND_THEN_DEPS_IN_CALL,
      );
      const rowView = addComponent(
        tableName + ROW + VIEW,
        '{rowId, ' +
          storeInstance +
          ', cellComponents, getCellComponentProps, customCellIds' +
          SEPARATOR_AND_DEBUG_IDS +
          `}: ${rowPropsType}<'${tableId}'>`,
        [
          wrap +
            `(${useCustomOrDefaultCellIds}(customCellIds, ` +
            TABLE_ID +
            `, rowId, ${storeInstance}).map((cellId: ${cellIdType2}) => {`,
          'const Cell = (cellComponents?.[cellId] ?? ' +
            getDefaultCellComponent +
            `(${TABLE_ID}, cellId)) as React.ComponentType<CellProps<typeof ` +
            TABLE_ID +
            ', typeof cellId>>;',
          'return <Cell',
          `{...${getProps}(getCellComponentProps, cellId)} `,
          'key={cellId}',
          `tableId={${TABLE_ID}}`,
          ROW_ID_PROP,
          'cellId={cellId}',
          storeProp,
          DEBUG_IDS_PROP,
          '/>;',
          '})' + SEPARATOR_AND_DEBUG_IDS + ', rowId)',
        ],
        getRowContentDoc(tableId, 13) + AND_REGISTERS,
      );
      addComponent(
        tableName + 'Sorted' + TABLE + VIEW,
        '{cellId, descending, offset, limit, ...props}: ' +
          sortedTablePropsType +
          `<'${tableId}'>`,
        tableView +
          '(props, ' +
          useSortedRowIds +
          `(cellId, descending, offset, limit, props.${storeInstance}), ${TABLE_ID}, ${rowView});`,
        getTableContentDoc(tableId, 13) + ', sorted' + AND_REGISTERS,
      );
      addComponent(
        tableName + TABLE + VIEW,
        `props: ${tablePropsType}<'${tableId}'>`,
        tableView +
          '(props, ' +
          useRowIds +
          `(props.${storeInstance}), ${TABLE_ID}, ${rowView});`,
        getTableContentDoc(tableId, 13) + AND_REGISTERS,
      );
      mapCellSchema(
        tableId,
        (cellId, type, defaultValue, CELL_ID, cellName) => {
          const mapCellType = 'Map' + camel(type, 1);
          addImport(0, moduleDefinition, mapCellType);
          addImport(1, moduleDefinition, mapCellType);
          addProxyHook(
            HAS + tableName + cellName + TABLE + CELL,
            HAS + TABLE + CELL,
            BOOLEAN,
            `Gets ${getHasDoc(1)}the '${cellId}' Cell anywhere in ` +
              getTableDoc(tableId) +
              AND_REGISTERS,
            EMPTY_STRING,
            getParameterList(TABLE_ID, CELL_ID),
          );
          addProxyHook(
            HAS + tableName + cellName + CELL,
            HAS + CELL,
            BOOLEAN,
            getCellContentDoc(tableId, cellId, 0, 1) + AND_REGISTERS,
            TYPED_ROW_ID,
            getParameterList(TABLE_ID, ROW_ID, CELL_ID),
          );
          const useCell = addProxyHook(
            tableName + cellName + CELL,
            CELL,
            type + (isUndefined(defaultValue) ? OR_UNDEFINED : EMPTY_STRING),
            getCellContentDoc(tableId, cellId) + AND_REGISTERS,
            TYPED_ROW_ID,
            getParameterList(TABLE_ID, ROW_ID, CELL_ID),
          );
          addProxyHook(
            SET + tableName + cellName + CELL + CALLBACK,
            SET + CELL + CALLBACK,
            PARAMETERIZED_CALLBACK,
            getCellContentDoc(tableId, cellId, 9) + BASED_ON_A_PARAMETER,
            getParameterList(
              TYPED_ROW_ID,
              getGet(CELL) + GETTER_ARGS + type + ' | ' + mapCellType,
              getGet(CELL) + DEPS_SUFFIX,
            ),
            getParameterList(TABLE_ID, ROW_ID, CELL_ID, getGetAndGetDeps(CELL)),
            GENERIC_PARAMETER,
            getParameterList(
              THEN_PREFIX,
              `cell: ${type} | ${mapCellType})` + RETURNS_VOID,
              THEN_DEPS,
            ),
            THEN_AND_THEN_DEPS_IN_CALL,
          );
          addProxyHook(
            DEL + tableName + cellName + CELL + CALLBACK,
            DEL + CELL + CALLBACK,
            CALLBACK,
            getCellContentDoc(tableId, cellId, 12),
            getParameterList(TYPED_ROW_ID, 'forceDel?: boolean'),
            getParameterList(TABLE_ID, ROW_ID, CELL_ID, 'forceDel'),
            EMPTY_STRING,
            THEN_AND_THEN_DEPS,
            THEN_AND_THEN_DEPS_IN_CALL,
          );
          addComponent(
            tableName + cellName + CELL + VIEW,
            `{rowId, ${storeInstance}, debugIds}: ` +
              cellPropsType +
              `<'${tableId}', '${cellId}'>`,
            [
              wrap +
                `('' + ${useCell}(rowId, ` +
                storeInstance +
                `) ?? '', undefined, debugIds, ${CELL_ID})`,
            ],
            getCellContentDoc(tableId, cellId, 13) + AND_REGISTERS,
          );
        },
      );
    });
    const cellIdsType = arrayJoin(
      mapTablesSchema(
        (tableId) => mapGet(tablesTypes, tableId)?.[4] ?? EMPTY_STRING,
      ),
      ' | ',
    );
    addProxyHook(
      HAS + TABLES + LISTENER,
      HAS + TABLES + LISTENER,
      VOID,
      getTheContentOfTheStoreDoc(1, 8, 0, 1) + ' changes',
      getListenerHookParams(hasTablesListenerType),
      getListenerHookParamsInCall(),
    );
    addProxyHook(
      TABLES + LISTENER,
      TABLES + LISTENER,
      VOID,
      getTheContentOfTheStoreDoc(1, 8) + ' changes',
      getListenerHookParams(tablesListenerType),
      getListenerHookParamsInCall(),
    );
    addProxyHook(
      TABLE_IDS + LISTENER,
      TABLE_IDS + LISTENER,
      VOID,
      getListenerDoc(2, 0, 1),
      getListenerHookParams(tableIdsListenerType),
      getListenerHookParamsInCall(),
    );
    addProxyHook(
      HAS + TABLE + LISTENER,
      HAS + TABLE + LISTENER,
      VOID,
      getListenerDoc(3, 0, 0, 1),
      getListenerHookParams(
        hasTableListenerType,
        `tableId: ${tableIdType} | null`,
      ),
      getListenerHookParamsInCall('tableId'),
    );
    addProxyHook(
      TABLE + LISTENER,
      TABLE + LISTENER,
      VOID,
      getListenerDoc(3, 0),
      getListenerHookParams(
        tableListenerType,
        `tableId: ${tableIdType} | null`,
      ),
      getListenerHookParamsInCall('tableId'),
    );
    addProxyHook(
      TABLE + CELL_IDS + LISTENER,
      TABLE + CELL_IDS + LISTENER,
      VOID,
      getListenerDoc(14, 3, 1),
      getListenerHookParams(
        tableCellIdsListenerType,
        `tableId: ${tableIdType} | null`,
      ),
      getListenerHookParamsInCall('tableId'),
    );
    addProxyHook(
      HAS + TABLE + CELL + LISTENER,
      HAS + TABLE + CELL + LISTENER,
      VOID,
      getListenerDoc(16, 3, 0, 1),
      getListenerHookParams(
        hasTableCellListenerType,
        `tableId: ${tableIdType} | null`,
        `cellId: ${cellIdsType} | null`,
      ),
      getListenerHookParamsInCall('tableId', 'cellId'),
    );
    addProxyHook(
      ROW + COUNT + LISTENER,
      ROW + COUNT + LISTENER,
      VOID,
      getListenerDoc(15, 3),
      getListenerHookParams(
        rowCountListenerType,
        `tableId: ${tableIdType} | null`,
      ),
      getListenerHookParamsInCall('tableId'),
    );
    addProxyHook(
      ROW_IDS + LISTENER,
      ROW_IDS + LISTENER,
      VOID,
      getListenerDoc(4, 3, 1),
      getListenerHookParams(
        rowIdsListenerType,
        `tableId: ${tableIdType} | null`,
      ),
      getListenerHookParamsInCall('tableId'),
    );
    addProxyHook(
      SORTED_ROW_IDS + LISTENER,
      SORTED_ROW_IDS + LISTENER,
      VOID,
      getListenerDoc(13, 3, 1),
      getListenerHookParams(
        sortedRowIdsListenerType,
        `tableId: ${tableIdType} | null`,
        'cellId: ' + cellIdsType + OR_UNDEFINED,
        'descending: boolean',
        'offset: number',
        'limit: number' + OR_UNDEFINED,
      ),
      getListenerHookParamsInCall(
        'tableId',
        'cellId',
        'descending',
        'offset',
        'limit',
      ),
    );
    addProxyHook(
      HAS + ROW + LISTENER,
      HAS + ROW + LISTENER,
      VOID,
      getListenerDoc(5, 3, 0, 1),
      getListenerHookParams(
        hasRowListenerType,
        `tableId: ${tableIdType} | null`,
        ROW_ID + `: IdOrNull`,
      ),
      getListenerHookParamsInCall('tableId', ROW_ID),
    );
    addProxyHook(
      ROW + LISTENER,
      ROW + LISTENER,
      VOID,
      getListenerDoc(5, 3),
      getListenerHookParams(
        rowListenerType,
        `tableId: ${tableIdType} | null`,
        ROW_ID + `: IdOrNull`,
      ),
      getListenerHookParamsInCall('tableId', ROW_ID),
    );
    addProxyHook(
      CELL_IDS + LISTENER,
      CELL_IDS + LISTENER,
      VOID,
      getListenerDoc(6, 5, 1),
      getListenerHookParams(
        cellIdsListenerType,
        `tableId: ${tableIdType} | null`,
        ROW_ID + `: IdOrNull`,
      ),
      getListenerHookParamsInCall('tableId', ROW_ID),
    );
    addProxyHook(
      HAS + CELL + LISTENER,
      HAS + CELL + LISTENER,
      VOID,
      getListenerDoc(7, 5, 0, 1),
      getListenerHookParams(
        hasCellListenerType,
        `tableId: ${tableIdType} | null`,
        ROW_ID + `: IdOrNull`,
        `cellId: ${cellIdsType} | null`,
      ),
      getListenerHookParamsInCall('tableId', ROW_ID, 'cellId'),
    );
    addProxyHook(
      CELL + LISTENER,
      CELL + LISTENER,
      VOID,
      getListenerDoc(7, 5),
      getListenerHookParams(
        cellListenerType,
        `tableId: ${tableIdType} | null`,
        ROW_ID + `: IdOrNull`,
        `cellId: ${cellIdsType} | null`,
      ),
      getListenerHookParamsInCall('tableId', ROW_ID, 'cellId'),
    );
  }
  if (!objIsEmpty(valuesSchema)) {
    const [
      valuesType,
      valuesWhenSetType,
      valueIdType,
      hasValuesListenerType,
      valuesListenerType,
      valueIdsListenerType,
      hasValueListenerType,
      valueListenerType,
    ] = sharedValueTypes;
    addImport(null, moduleDefinition, ...sharedValueTypes);
    addImport(1, moduleDefinition, storeType);
    const getDefaultValueComponent = addInternalFunction(
      'getDefaultValueComponent',
      'valueId: Id',
      arrayJoin(
        mapValuesSchema(
          (_, _2, _3, VALUE_ID, valueName) =>
            `valueId == ${VALUE_ID} ? ` + valueName + 'ValueView : ',
        ),
      ) + NullComponent,
    );
    addProxyHook(
      HAS + VALUES,
      HAS + VALUES,
      BOOLEAN,
      getTheContentOfTheStoreDoc(2, 0, 0, 1) + AND_REGISTERS,
    );
    addProxyHook(
      VALUES,
      VALUES,
      valuesType,
      getTheContentOfTheStoreDoc(2, 0) + AND_REGISTERS,
    );
    const useValueIds = addProxyHook(
      VALUE_IDS,
      VALUE_IDS,
      valueIdType + SQUARE_BRACKETS,
      getIdsDoc(VALUE, THE_STORE) + AND_REGISTERS,
    );
    addProxyHook(
      SET + VALUES + CALLBACK,
      SET + VALUES + CALLBACK,
      PARAMETERIZED_CALLBACK,
      getTheContentOfTheStoreDoc(2, 9) + BASED_ON_A_PARAMETER,
      getParameterList(
        getGet(VALUES) + GETTER_ARGS + valuesWhenSetType,
        getGet(VALUES) + DEPS_SUFFIX,
      ),
      getGetAndGetDeps(VALUES),
      GENERIC_PARAMETER,
      getParameterList(
        THEN_PREFIX,
        `values: ${valuesWhenSetType})` + RETURNS_VOID,
        THEN_DEPS,
      ),
      THEN_AND_THEN_DEPS_IN_CALL,
    );
    addProxyHook(
      SET + PARTIAL + VALUES + CALLBACK,
      SET + PARTIAL + VALUES + CALLBACK,
      PARAMETERIZED_CALLBACK,
      getTheContentOfTheStoreDoc(2, 11) + BASED_ON_A_PARAMETER,
      getParameterList(
        getGet(PARTIAL + VALUES) + GETTER_ARGS + valuesWhenSetType,
        getGet(PARTIAL + VALUES) + DEPS_SUFFIX,
      ),
      getGetAndGetDeps(PARTIAL + VALUES),
      GENERIC_PARAMETER,
      getParameterList(
        THEN_PREFIX,
        `partialValues: ${valuesWhenSetType})` + RETURNS_VOID,
        THEN_DEPS,
      ),
      THEN_AND_THEN_DEPS_IN_CALL,
    );
    addProxyHook(
      DEL + VALUES + CALLBACK,
      DEL + VALUES + CALLBACK,
      CALLBACK,
      getTheContentOfTheStoreDoc(2, 12),
      EMPTY_STRING,
      EMPTY_STRING,
      EMPTY_STRING,
      THEN_AND_THEN_DEPS,
      THEN_AND_THEN_DEPS_IN_CALL,
    );
    const valuePropsType = addType(
      VALUE + PROPS,
      getPropTypeList(
        `valueId?: VId`,
        storeInstance + OPTIONAL_COLON + storeType,
        DEBUG_IDS_PROP_TYPE,
      ),
      getPropsDoc('a Value'),
      `<VId extends ${valueIdType}>`,
    );
    const valuesPropsType = addType(
      VALUES + PROPS,
      getPropTypeList(
        storeInstance + OPTIONAL_COLON + storeType,
        'valueComponents?: {readonly [VId in ' +
          valueIdType +
          `]?: ComponentType<${valuePropsType}<VId>>;}`,
        `getValueComponentProps?: (valueId: ${valueIdType}) => ExtraProps`,
        SEPARATOR_PROP_TYPE,
        DEBUG_IDS_PROP_TYPE,
      ),
      getPropsDoc(getTheContentOfDoc(2, 1)),
    );
    addImport(1, uiReactModuleDefinition, valuesPropsType, valuePropsType);
    addComponent(
      VALUES + VIEW,
      '{' +
        storeInstance +
        ', valueComponents, getValueComponentProps' +
        SEPARATOR_AND_DEBUG_IDS +
        '}: ' +
        valuesPropsType,
      [
        wrap + `(${useValueIds}(${storeInstance}).map((valueId) => {`,
        'const Value = valueComponents?.[valueId] ?? ' +
          getDefaultValueComponent +
          '(valueId);',
        'return <Value',
        `{...${getProps}(getValueComponentProps, valueId)}`,
        'key={valueId}',
        storeProp,
        DEBUG_IDS_PROP,
        '/>;',
        '}), separator)',
      ],
      getTheContentOfTheStoreDoc(2, 13) + AND_REGISTERS,
    );
    mapValuesSchema((valueId, type, _, VALUE_ID, valueName) => {
      const mapValueType = 'Map' + camel(type, 1);
      addImport(0, moduleDefinition, mapValueType);
      addImport(1, moduleDefinition, mapValueType);
      addProxyHook(
        HAS + valueName + VALUE,
        HAS + VALUE,
        BOOLEAN,
        getValueContentDoc(valueId, 0, 1) + AND_REGISTERS,
        EMPTY_STRING,
        VALUE_ID,
      );
      const useValue = addProxyHook(
        valueName + VALUE,
        VALUE,
        type,
        getValueContentDoc(valueId) + AND_REGISTERS,
        EMPTY_STRING,
        VALUE_ID,
      );
      addProxyHook(
        SET + valueName + VALUE + CALLBACK,
        SET + VALUE + CALLBACK,
        PARAMETERIZED_CALLBACK,
        getValueContentDoc(valueId, 9) + BASED_ON_A_PARAMETER,
        getParameterList(
          getGet(VALUE) + GETTER_ARGS + type + ' | ' + mapValueType,
          getGet(VALUE) + DEPS_SUFFIX,
        ),
        getParameterList(VALUE_ID, getGetAndGetDeps(VALUE)),
        GENERIC_PARAMETER,
        getParameterList(
          THEN_PREFIX,
          `value: ${type} | ${mapValueType})` + RETURNS_VOID,
          THEN_DEPS,
        ),
        THEN_AND_THEN_DEPS_IN_CALL,
      );
      addProxyHook(
        DEL + valueName + VALUE + CALLBACK,
        DEL + VALUE + CALLBACK,
        CALLBACK,
        getValueContentDoc(valueId, 12),
        EMPTY_STRING,
        VALUE_ID,
        EMPTY_STRING,
        THEN_AND_THEN_DEPS,
        THEN_AND_THEN_DEPS_IN_CALL,
      );
      addComponent(
        valueName + VALUE + VIEW,
        `{${storeInstance}, debugIds}: ${valuePropsType}<'${valueId}'>`,
        [
          wrap +
            `('' + ${useValue}(` +
            storeInstance +
            `) ?? '', undefined, debugIds, ${VALUE_ID})`,
        ],
        getValueContentDoc(valueId, 13) + AND_REGISTERS,
      );
    });
    addProxyHook(
      HAS + VALUES + LISTENER,
      HAS + VALUES + LISTENER,
      VOID,
      getTheContentOfTheStoreDoc(2, 8, 0, 1) + ' changes',
      getListenerHookParams(hasValuesListenerType),
      getListenerHookParamsInCall(),
    );
    addProxyHook(
      VALUES + LISTENER,
      VALUES + LISTENER,
      VOID,
      getTheContentOfTheStoreDoc(2, 8) + ' changes',
      getListenerHookParams(valuesListenerType),
      getListenerHookParamsInCall(),
    );
    addProxyHook(
      VALUE_IDS + LISTENER,
      VALUE_IDS + LISTENER,
      VOID,
      getListenerDoc(10, 0, 1),
      getListenerHookParams(valueIdsListenerType),
      getListenerHookParamsInCall(),
    );
    addProxyHook(
      HAS + VALUE + LISTENER,
      HAS + VALUE + LISTENER,
      VOID,
      getListenerDoc(11, 0, 0, 1),
      getListenerHookParams(
        hasValueListenerType,
        `valueId: ${valueIdType} | null`,
      ),
      getListenerHookParamsInCall('valueId'),
    );
    addProxyHook(
      VALUE + LISTENER,
      VALUE + LISTENER,
      VOID,
      getListenerDoc(11, 0),
      getListenerHookParams(
        valueListenerType,
        `valueId: ${valueIdType} | null`,
      ),
      getListenerHookParamsInCall('valueId'),
    );
  }
  addComponent(
    PROVIDER,
    `{${storeInstance}, ${storeInstance}ById, children}: ` +
      providerPropsType +
      ' & {readonly children: React.ReactNode}',
    [
      '{',
      USE_CONTEXT,
      'return (',
      '<Context.' + PROVIDER,
      'value={useMemo(',
      `() => [${storeInstance} ?? contextValue[0], {...contextValue[1], ...${storeInstance}ById}],`,
      `[${storeInstance}, ${storeInstance}ById, contextValue],`,
      ')}>',
      '{children}',
      `</Context.${PROVIDER}>`,
      ');',
      '}',
    ],
    'Wraps part of an application in a context that provides default objects to be used by hooks and components within',
  );
  return [
    build(...getImports(0), ...getTypes(), ...getFunctions(0)),
    build(...getImports(1), ...getConstants(), ...getFunctions(1)),
  ];
};

const getStoreApi = (tablesSchema, valuesSchema, module) => {
  if (objIsEmpty(tablesSchema) && objIsEmpty(valuesSchema)) {
    return [EMPTY_STRING, EMPTY_STRING, EMPTY_STRING, EMPTY_STRING];
  }
  const [dTsCore, tsCore, sharedTableTypes, sharedValueTypes] = getStoreCoreApi(
    tablesSchema,
    valuesSchema,
    module,
  );
  return [
    dTsCore,
    tsCore,
    ...getStoreUiReactApi(
      tablesSchema,
      valuesSchema,
      module,
      sharedTableTypes,
      sharedValueTypes,
    ),
  ];
};

const jsonParse = JSON.parse;

const PRETTIER = 'prettier/';
const PRETTIER_PLUGINS = PRETTIER + 'plugins/';
const prettierConfig = {
  parser: 'typescript',
  singleQuote: true,
  trailingComma: 'all',
  bracketSpacing: false,
  jsdocSingleLineComment: false,
};
const createTools = getCreateFunction((store) => {
  const getStoreStats = (detail) => {
    let totalTables = 0;
    let totalRows = 0;
    let totalCells = 0;
    const tables = {};
    store.forEachTable((tableId, forEachRow) => {
      totalTables++;
      let tableRows = 0;
      let tableCells = 0;
      const rows = {};
      forEachRow((rowId, forEachCell) => {
        tableRows++;
        let rowCells = 0;
        forEachCell(() => rowCells++);
        tableCells += rowCells;
        if (detail) {
          rows[rowId] = {rowCells};
        }
      });
      totalRows += tableRows;
      totalCells += tableCells;
      if (detail) {
        tables[tableId] = {tableRows, tableCells, rows};
      }
    });
    return {
      totalTables,
      totalRows,
      totalCells,
      totalValues: size(store.getValueIds()),
      jsonLength: size(store.getJson()),
      ...(detail ? {detail: {tables}} : {}),
    };
  };
  const getStoreTablesSchema = () => {
    const tablesSchema = jsonParse(store.getTablesSchemaJson());
    if (
      !objIsEmpty(tablesSchema) ||
      arrayEvery(store.getTableIds(), (tableId) => {
        const rowIds = store.getRowIds(tableId);
        const cellsMeta = mapNew();
        if (
          arrayEvery(rowIds, (rowId) =>
            arrayEvery(store.getCellIds(tableId, rowId), (cellId) => {
              const value = store.getCell(tableId, rowId, cellId);
              const cellMeta = mapEnsure(cellsMeta, cellId, () => [
                getCellOrValueType(value),
                mapNew(),
                [0],
                0,
              ]);
              const [type, values, [maxCount]] = cellMeta;
              const count = mapEnsure(values, value, () => 0) + 1;
              if (count > maxCount) {
                cellMeta[2] = [count, value];
              }
              mapSet(values, value, count);
              cellMeta[3]++;
              return type == getCellOrValueType(value);
            }),
          )
        ) {
          tablesSchema[tableId] = {};
          collForEach(cellsMeta, ([type, , [, maxValue], count], cellId) => {
            tablesSchema[tableId][cellId] = {
              [TYPE]: type,
              ...(count == size(rowIds) ? {[DEFAULT]: maxValue} : {}),
            };
          });
          return 1;
        }
      })
    ) {
      return tablesSchema;
    }
    return {};
  };
  const getStoreValuesSchema = () => {
    const valuesSchema = jsonParse(store.getValuesSchemaJson());
    if (objIsEmpty(valuesSchema)) {
      store.forEachValue((valueId, value) => {
        valuesSchema[valueId] = {
          [TYPE]: getCellOrValueType(value),
        };
      });
    }
    return valuesSchema;
  };
  const getStoreApi$1 = (module) =>
    getStoreApi(getStoreTablesSchema(), getStoreValuesSchema(), module);
  const getPrettyStoreApi = async (module) => {
    const extensions = ['d.ts', 'ts', 'd.ts', 'tsx'];
    const plugins = [];
    let format;
    try {
      format = (await import(PRETTIER + 'standalone')).format;
      arrayPush(
        plugins,
        await import(PRETTIER_PLUGINS + 'estree'),
        await import(PRETTIER_PLUGINS + 'typescript'),
      );
    } catch {
      format = async (str) => str;
    }
    return await promiseAll(
      arrayMap(getStoreApi$1(module), async (file, f) =>
        formatJsDoc(
          await format(file, {
            ...prettierConfig,
            plugins,
            filepath: `_.${extensions[f]}`,
          }),
        ),
      ),
    );
  };
  const getStore = () => store;
  const tools = {
    getStoreStats,
    getStoreTablesSchema,
    getStoreValuesSchema,
    getStoreApi: getStoreApi$1,
    getPrettyStoreApi,
    getStore,
  };
  return objFreeze(tools);
});

exports.createTools = createTools;
