'use strict';

const getTypeOf = (thing) => typeof thing;
const EMPTY_STRING = '';
const STRING = getTypeOf(EMPTY_STRING);
const T = 't';
const V = 'v';
const strStartsWith = (str, prefix) => str.startsWith(prefix);

var __async$1 = (__this, __arguments, generator) => {
  return new Promise((resolve, reject) => {
    var fulfilled = (value) => {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    };
    var rejected = (value) => {
      try {
        step(generator.throw(value));
      } catch (e) {
        reject(e);
      }
    };
    var step = (x) =>
      x.done
        ? resolve(x.value)
        : Promise.resolve(x.value).then(fulfilled, rejected);
    step((generator = generator.apply(__this, __arguments)).next());
  });
};
const promise = Promise;
const isInstanceOf = (thing, cls) => thing instanceof cls;
const isUndefined = (thing) => thing == undefined;
const ifNotUndefined = (value, then, otherwise) =>
  isUndefined(value)
    ? otherwise == null
      ? undefined
      : otherwise()
    : then(value);
const isString = (thing) => getTypeOf(thing) == STRING;
const slice = (arrayOrString, start, end) => arrayOrString.slice(start, end);
const size = (arrayOrString) => arrayOrString.length;
const promiseAll = (promises) =>
  __async$1(undefined, null, function* () {
    return promise.all(promises);
  });

const arrayEvery = (array, cb) => array.every(cb);
const arrayMap = (array, cb) => array.map(cb);
const arrayIsEmpty = (array) => size(array) == 0;
const arrayPush = (array, ...values) => array.push(...values);
const arrayUnshift = (array, ...values) => array.unshift(...values);

const object = Object;
const objEntries = object.entries;
const objNew = (entries = []) => object.fromEntries(entries);
const objHas = (obj, id) => id in obj;
const objToArray = (obj, cb) =>
  arrayMap(objEntries(obj), ([id, value]) => cb(value, id));
const objEnsure = (obj, id, getDefaultValue) => {
  if (!objHas(obj, id)) {
    obj[id] = getDefaultValue();
  }
  return obj[id];
};

const jsonString = JSON.stringify;
const jsonParse = JSON.parse;
const jsonStringWithMap = (obj) =>
  jsonString(obj, (_key, value) =>
    isInstanceOf(value, Map) ? object.fromEntries([...value]) : value,
  );

const SET_CHANGES = 's';
const STORE_PATH = '/store';
const PUT = 'PUT';
const construct = (prefix, type, payload) =>
  prefix + type + (isString(payload) ? payload : jsonStringWithMap(payload));
const deconstruct = (prefix, message, stringified) => {
  const prefixSize = size(prefix);
  return strStartsWith(message, prefix)
    ? [
        message[prefixSize],
        (stringified ? jsonParse : String)(slice(message, prefixSize + 1)),
      ]
    : undefined;
};

const collForEach = (coll, cb) => (coll == null ? undefined : coll.forEach(cb));

const mapForEach = (map, cb) =>
  collForEach(map, (value, key) => cb(key, value));

var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) =>
  key in obj
    ? __defProp(obj, key, {
        enumerable: true,
        configurable: true,
        writable: true,
        value,
      })
    : (obj[key] = value);
var __publicField = (obj, key, value) => __defNormalProp(obj, key + '', value);
var __async = (__this, __arguments, generator) => {
  return new Promise((resolve, reject) => {
    var fulfilled = (value) => {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    };
    var rejected = (value) => {
      try {
        step(generator.throw(value));
      } catch (e) {
        reject(e);
      }
    };
    var step = (x) =>
      x.done
        ? resolve(x.value)
        : Promise.resolve(x.value).then(fulfilled, rejected);
    step((generator = generator.apply(__this, __arguments)).next());
  });
};
const HAS_STORE = 'hasStore';
const RESPONSE_HEADERS = objNew(
  arrayMap(['Origin', 'Methods', 'Headers'], (suffix) => [
    'Access-Control-Allow-' + suffix,
    '*',
  ]),
);
const hasStoreInStorage = (_0, ..._1) =>
  __async(
    undefined,
    [_0, ..._1],
    function* (storage, storagePrefix = EMPTY_STRING) {
      return !!(yield storage.get(storagePrefix + HAS_STORE));
    },
  );
const loadStoreFromStorage = (_0, ..._1) =>
  __async(
    undefined,
    [_0, ..._1],
    function* (storage, storagePrefix = EMPTY_STRING) {
      const tables = {};
      const values = {};
      mapForEach(yield storage.list(), (key, cellOrValue) =>
        ifNotUndefined(deconstruct(storagePrefix, key), ([type, ids]) => {
          if (type == T) {
            const [tableId, rowId, cellId] = jsonParse('[' + ids + ']');
            objEnsure(objEnsure(tables, tableId, objNew), rowId, objNew)[
              cellId
            ] = cellOrValue;
          } else if (type == V) {
            values[ids] = cellOrValue;
          }
        }),
      );
      return [tables, values];
    },
  );
const broadcastChanges = (server, changes, without) =>
  __async(undefined, null, function* () {
    return server.party.broadcast(
      construct(server.config.messagePrefix, SET_CHANGES, changes),
      without,
    );
  });
const saveStore = (that, changes, initialSave, requestOrConnection) =>
  __async(undefined, null, function* () {
    const storage = that.party.storage;
    const storagePrefix = that.config.storagePrefix;
    const keysToSet = {
      [storagePrefix + HAS_STORE]: 1,
    };
    const keysToDel = [];
    const keyPrefixesToDel = [];
    yield promiseAll(
      objToArray(changes[0], (table, tableId) =>
        __async(undefined, null, function* () {
          return isUndefined(table)
            ? !initialSave &&
                (yield that.canDelTable(tableId, requestOrConnection)) &&
                arrayUnshift(
                  keyPrefixesToDel,
                  constructStorageKey(storagePrefix, T, tableId),
                )
            : (yield that.canSetTable(
                tableId,
                initialSave,
                requestOrConnection,
              )) &&
                (yield promiseAll(
                  objToArray(table, (row, rowId) =>
                    __async(undefined, null, function* () {
                      return isUndefined(row)
                        ? !initialSave &&
                            (yield that.canDelRow(
                              tableId,
                              rowId,
                              requestOrConnection,
                            )) &&
                            arrayPush(
                              keyPrefixesToDel,
                              constructStorageKey(
                                storagePrefix,
                                T,
                                tableId,
                                rowId,
                              ),
                            )
                        : (yield that.canSetRow(
                            tableId,
                            rowId,
                            initialSave,
                            requestOrConnection,
                          )) &&
                            (yield promiseAll(
                              objToArray(row, (cell, cellId) =>
                                __async(undefined, null, function* () {
                                  const ids = [tableId, rowId, cellId];
                                  const key = constructStorageKey(
                                    storagePrefix,
                                    T,
                                    ...ids,
                                  );
                                  if (isUndefined(cell)) {
                                    if (
                                      !initialSave &&
                                      (yield that.canDelCell(
                                        ...ids,
                                        requestOrConnection,
                                      ))
                                    ) {
                                      arrayPush(keysToDel, key);
                                    }
                                  } else if (
                                    yield that.canSetCell(
                                      ...ids,
                                      cell,
                                      initialSave,
                                      requestOrConnection,
                                      yield storage.get(key),
                                    )
                                  ) {
                                    keysToSet[key] = cell;
                                  }
                                }),
                              ),
                            ));
                    }),
                  ),
                ));
        }),
      ),
    );
    yield promiseAll(
      objToArray(changes[1], (value, valueId) =>
        __async(undefined, null, function* () {
          const key = storagePrefix + V + valueId;
          if (isUndefined(value)) {
            if (
              !initialSave &&
              (yield that.canDelValue(valueId, requestOrConnection))
            ) {
              arrayPush(keysToDel, key);
            }
          } else if (
            yield that.canSetValue(
              valueId,
              value,
              initialSave,
              requestOrConnection,
              yield storage.get(key),
            )
          ) {
            keysToSet[key] = value;
          }
        }),
      ),
    );
    if (!arrayIsEmpty(keyPrefixesToDel)) {
      mapForEach(yield storage.list(), (key) =>
        arrayEvery(
          keyPrefixesToDel,
          (keyPrefixToDelete) =>
            !strStartsWith(key, keyPrefixToDelete) ||
            (arrayPush(keysToDel, key) && 0),
        ),
      );
    }
    yield storage.delete(keysToDel);
    yield storage.put(keysToSet);
  });
const constructStorageKey = (storagePrefix, type, ...ids) =>
  construct(storagePrefix, type, slice(jsonStringWithMap(ids), 1, -1));
const createResponse = (that, status, body = null) =>
  __async(undefined, null, function* () {
    return new Response(body, {
      status,
      headers: that.config.responseHeaders,
    });
  });
class TinyBasePartyKitServer {
  constructor(party) {
    this.party = party;
    __publicField(this, 'config', {});
    var _a, _b, _c, _d, _e, _f, _g, _h;
    (_b = (_a = this.config).storePath) != null
      ? _b
      : (_a.storePath = STORE_PATH);
    (_d = (_c = this.config).messagePrefix) != null
      ? _d
      : (_c.messagePrefix = EMPTY_STRING);
    (_f = (_e = this.config).storagePrefix) != null
      ? _f
      : (_e.storagePrefix = EMPTY_STRING);
    (_h = (_g = this.config).responseHeaders) != null
      ? _h
      : (_g.responseHeaders = RESPONSE_HEADERS);
  }
  onRequest(request) {
    return __async(this, null, function* () {
      const {
        party: {storage},
        config: {storePath, storagePrefix},
      } = this;
      if (new URL(request.url).pathname.endsWith(storePath)) {
        const hasExistingStore = yield hasStoreInStorage(
          storage,
          storagePrefix,
        );
        const text = yield request.text();
        if (request.method == PUT) {
          if (hasExistingStore) {
            return createResponse(this, 205);
          }
          yield saveStore(this, jsonParse(text), true, request);
          return createResponse(this, 201);
        }
        return createResponse(
          this,
          200,
          hasExistingStore
            ? jsonStringWithMap(
                yield loadStoreFromStorage(storage, storagePrefix),
              )
            : EMPTY_STRING,
        );
      }
      return createResponse(this, 404);
    });
  }
  onMessage(message, connection) {
    return __async(this, null, function* () {
      const {
        config: {messagePrefix, storagePrefix},
      } = this;
      yield ifNotUndefined(deconstruct(messagePrefix, message, 1), (_0) =>
        __async(this, [_0], function* ([type, payload]) {
          if (
            type == SET_CHANGES &&
            (yield hasStoreInStorage(this.party.storage, storagePrefix))
          ) {
            yield saveStore(this, payload, false, connection);
            broadcastChanges(this, payload, [connection.id]);
          }
        }),
      );
    });
  }
  canSetTable(_tableId, _initialSave, _requestOrConnection) {
    return __async(this, null, function* () {
      return true;
    });
  }
  canDelTable(_tableId, _connection) {
    return __async(this, null, function* () {
      return true;
    });
  }
  canSetRow(_tableId, _rowId, _initialSave, _requestOrConnection) {
    return __async(this, null, function* () {
      return true;
    });
  }
  canDelRow(_tableId, _rowId, _connection) {
    return __async(this, null, function* () {
      return true;
    });
  }
  canSetCell(
    _tableId,
    _rowId,
    _cellId,
    _cell,
    _initialSave,
    _requestOrConnection,
    _oldCell,
  ) {
    return __async(this, null, function* () {
      return true;
    });
  }
  canDelCell(_tableId, _rowId, _cellId, _connection) {
    return __async(this, null, function* () {
      return true;
    });
  }
  canSetValue(_valueId, _value, _initialSave, _requestOrConnection, _oldValue) {
    return __async(this, null, function* () {
      return true;
    });
  }
  canDelValue(_valueId, _connection) {
    return __async(this, null, function* () {
      return true;
    });
  }
}

exports.TinyBasePartyKitServer = TinyBasePartyKitServer;
exports.broadcastChanges = broadcastChanges;
exports.hasStoreInStorage = hasStoreInStorage;
exports.loadStoreFromStorage = loadStoreFromStorage;
