import { deferred, isDebugMode, storePointer } from "./utils.js";
import { assert, get as get$1, getCurrentValue, getEntries, getEntry, invalidate, invalidateAll, sync as sync$1 } from "./cache.js";
//#region node_modules/hybrids/src/store.js
var connect = Symbol("store.connect");
var definitions = /* @__PURE__ */ new WeakMap();
var stales = /* @__PURE__ */ new WeakMap();
var observers = /* @__PURE__ */ new WeakMap();
function resolve(config, model, lastModel) {
	if (lastModel) {
		definitions.set(lastModel, null);
		stales.set(lastModel, model);
	}
	definitions.set(model, config);
	if (observers.has(config)) {
		const modelValue = model && config.isInstance(model) ? model : null;
		const lastModelValue = lastModel && config.isInstance(lastModel) ? lastModel : null;
		if (modelValue !== lastModelValue) {
			let id = lastModelValue ? lastModelValue.id : modelValue.id;
			observers.get(config).forEach((fn) => {
				try {
					fn(id, modelValue, lastModelValue);
				} catch (e) {
					console.error(e);
				}
			});
		}
	}
	return model;
}
function resolveWithInvalidate(config, model, lastModel) {
	resolve(config, model, lastModel);
	if (config.invalidate && (config.storage.loose || !lastModel || !config.isInstance(lastModel))) config.invalidate();
	return model;
}
function syncCache(config, id, model, invalidate = true) {
	sync$1(config, id, invalidate ? resolveWithInvalidate : resolve, model);
	return model;
}
var currentTimestamp;
function getCurrentTimestamp() {
	if (!currentTimestamp) {
		currentTimestamp = Date.now();
		deferred.then(() => {
			currentTimestamp = void 0;
		});
	}
	return currentTimestamp;
}
var timestamps = /* @__PURE__ */ new WeakMap();
function getTimestamp(model) {
	let timestamp = timestamps.get(model);
	if (!timestamp) {
		timestamp = getCurrentTimestamp();
		timestamps.set(model, timestamp);
	}
	return timestamp;
}
function setTimestamp(model) {
	timestamps.set(model, getCurrentTimestamp());
	return model;
}
function invalidateTimestamp(model) {
	timestamps.set(model, 1);
	return model;
}
function hashCode(str) {
	return globalThis.btoa(Array.from(str).reduce((s, c) => Math.imul(31, s) + c.charCodeAt(0) | 0, 0));
}
var offlinePrefix = "hybrids:store:cache";
var offlineKeys = {};
var clearPromise;
function setupOfflineKey(config, threshold) {
	const key = `${offlinePrefix}:${hashCode(JSON.stringify(config.model))}`;
	offlineKeys[key] = getCurrentTimestamp() + threshold;
	if (!clearPromise) clearPromise = Promise.resolve().then(() => {
		const previousKeys = JSON.parse(globalThis.localStorage.getItem(offlinePrefix)) || {};
		const timestamp = getCurrentTimestamp();
		/* istanbul ignore next */
		for (const k of Object.keys(previousKeys)) if (!offlineKeys[k] && previousKeys[k] < timestamp) {
			globalThis.localStorage.removeItem(k);
			delete previousKeys[k];
		}
		globalThis.localStorage.setItem(offlinePrefix, JSON.stringify({
			...previousKeys,
			...offlineKeys
		}));
		clearPromise = null;
	});
	return key;
}
function observe(Model, fn) {
	const config = bootstrap(Model);
	if (typeof fn !== "function") throw TypeError(`The second argument must be a function: ${typeof fn}`);
	const set = observers.get(config) || /* @__PURE__ */ new Set();
	set.add(fn);
	observers.set(config, set);
	return () => {
		set.delete(fn);
		if (set.size === 0) observers.delete(config);
	};
}
function setupStorage(config, options) {
	if (typeof options === "function") options = { get: options };
	const result = {
		cache: true,
		loose: false,
		...options
	};
	if (result.observe) {
		const fn = result.observe;
		if (typeof fn !== "function") throw TypeError(`Storage 'observe' property must be a function: ${typeof fn}`);
		const set = observers.get(config) || /* @__PURE__ */ new Set();
		set.add(fn);
		observers.set(config, set);
	}
	if (result.cache === false || result.cache === 0) result.validate = (cachedModel) => !cachedModel || getTimestamp(cachedModel) === getCurrentTimestamp();
	else if (typeof result.cache === "number") result.validate = (cachedModel) => !cachedModel || getTimestamp(cachedModel) + result.cache > getCurrentTimestamp();
	else {
		if (result.cache !== true) throw TypeError(`Storage 'cache' property must be a boolean or number: ${typeof result.cache}`);
		result.validate = (cachedModel) => getTimestamp(cachedModel) !== 1;
	}
	if (!result.get) result.get = (id) => {
		throw notFoundError(stringifyId(id));
	};
	if (result.offline) try {
		const isBool = result.offline === true;
		const threshold = isBool ? 1e3 * 60 * 60 * 24 * 30 : result.offline;
		const offlineKey = setupOfflineKey(config, threshold);
		const items = JSON.parse(globalThis.localStorage.getItem(offlineKey)) || {};
		let flush;
		result.offline = Object.freeze({
			key: offlineKey,
			threshold,
			get: isBool ? (id) => {
				if (hasOwnProperty.call(items, id)) return JSON.parse(items[id][1]);
				return null;
			} : (id) => {
				if (hasOwnProperty.call(items, id)) {
					const item = items[id];
					if (item[0] + threshold < getCurrentTimestamp()) {
						delete items[id];
						return null;
					}
					return JSON.parse(item[1]);
				}
				return null;
			},
			set(id, values) {
				if (values) items[id] = [getCurrentTimestamp(), JSON.stringify(values, function replacer(key, value) {
					if (value === this[""]) return value;
					if (value && typeof value === "object") {
						const valueConfig = definitions.get(value);
						if (valueConfig === config && value.id === id) return String(value);
						if (valueConfig && valueConfig.storage.offline) {
							if (valueConfig.list) return value.map((model) => {
								configs.get(valueConfig.model).storage.offline.set(model.id, model);
								return `${model}`;
							});
							valueConfig.storage.offline.set(value.id, value);
							return `${value}`;
						}
					}
					return value;
				})];
				else delete items[id];
				if (!flush) flush = Promise.resolve().then(() => {
					const timestamp = getCurrentTimestamp();
					for (const key of Object.keys(items)) if (items[key][0] + threshold < timestamp) delete items[key];
					globalThis.localStorage.setItem(offlineKey, JSON.stringify(items));
					flush = null;
				});
				return values;
			}
		});
	} catch (e) 	/* istanbul ignore next */ {
		console.error("Error while setup offline cache", e);
		result.offline = false;
	}
	return Object.freeze(result);
}
function memoryStorage(config) {
	return {
		get: config.enumerable ? () => null : () => config.create({}),
		set: config.enumerable ? (id, values) => values : (id, values) => values === null ? { id } : values,
		list: config.enumerable && function list(id) {
			if (id) throw TypeError(`Memory-based model definition does not support id`);
			const result = [];
			for (const { key, value } of getEntries(config)) if (key !== config && value && !error(value)) result.push(key);
			return result;
		},
		loose: true
	};
}
function bootstrap(Model, nested) {
	if (Array.isArray(Model)) return setupListModel(Model[0], nested);
	return setupModel(Model, nested);
}
function getTypeConstructor(type, key) {
	switch (type) {
		case "string": return (v) => v !== void 0 && v !== null ? String(v) : "";
		case "number": return Number;
		case "boolean": return Boolean;
		default: throw TypeError(`The value of the '${key}' must be a string, number or boolean: ${type}`);
	}
}
function setModelState(model, state, value) {
	const lastConfig = getEntry(model, "state").value;
	assert(model, "state", {
		state,
		value,
		error: (state === "error" ? value : lastConfig?.error) || false
	}, true);
	return model;
}
function getModelState(model) {
	return get$1(model, "state", (model, config = {
		state: "ready",
		error: false
	}) => config);
}
function uuid(temp) {
	return temp ? (temp ^ Math.random() * 16 >> temp / 4).toString(16) : "10000000-1000-4000-8000-100000000000".replace(/[018]/g, uuid);
}
var refs = /* @__PURE__ */ new WeakSet();
var records = /* @__PURE__ */ new WeakMap();
function ref(fn) {
	if (typeof fn !== "function") throw TypeError(`The first argument must be a function: ${typeof fn}`);
	refs.add(fn);
	return fn;
}
function record(value) {
	if (value === void 0 || value === null) throw TypeError(`The value must be defined: ${value}`);
	if (!refs.has(value) && typeof value === "function") throw TypeError(`A function is not supported as the value of the record`);
	const model = Object.freeze({});
	records.set(model, value);
	return model;
}
var validationMap = /* @__PURE__ */ new WeakMap();
function resolveKey(Model, key, config) {
	let defaultValue = config.model[key];
	if (refs.has(defaultValue)) defaultValue = defaultValue();
	if (records.has(defaultValue)) {
		let value = records.get(defaultValue);
		value = typeof value === "function" ? value() : value;
		return {
			defaultValue: {
				id: true,
				[key]: value
			},
			type: "record"
		};
	}
	let type = typeof defaultValue;
	if (defaultValue instanceof String || defaultValue instanceof Number || defaultValue instanceof Boolean) {
		const check = validationMap.get(defaultValue);
		if (!check) throw TypeError(stringifyModel(Model, `You must use primitive ${typeof defaultValue.valueOf()} value for '${key}' property of the provided model definition`));
		defaultValue = defaultValue.valueOf();
		type = typeof defaultValue;
		config.checks.set(key, check);
	}
	return {
		defaultValue,
		type
	};
}
var parentModels = /* @__PURE__ */ new WeakMap();
function stringifyModel(Model, msg) {
	const ResolvedModel = parentModels.get(Model) || Model;
	return `${msg}\n\n${`Model definition\n${JSON.stringify(ResolvedModel, null, 2)}`.split("\n").map((s) => `| ${s}`).join("\n")}\n`;
}
var resolvedPromise = Promise.resolve();
var configs = /* @__PURE__ */ new WeakMap();
function setupModel(Model, nested) {
	if (typeof Model !== "object" || Model === null) throw TypeError(`Model definition must be an object: ${typeof Model}`);
	let config = configs.get(Model);
	if (config && !config.enumerable) {
		if (nested && !config.nested) throw TypeError(stringifyModel(Model, "Provided model definition for nested object already used as a root definition"));
		if (!nested && config.nested) throw TypeError(stringifyModel(Model, "Nested model definition cannot be used outside of the parent definition"));
	}
	if (!config) {
		const storage = Model[connect];
		if (typeof storage === "object") Object.freeze(storage);
		let invalidatePromise;
		const enumerable = hasOwnProperty.call(Model, "id");
		const external = !!storage;
		const checks = /* @__PURE__ */ new Map();
		const proto = {};
		Object.defineProperty(proto, "toString", { value: function() {
			return this.id;
		} });
		const placeholder = Object.create(proto);
		config = {
			model: Model,
			external,
			enumerable,
			nested: !enumerable && !external && nested,
			placeholder: (id) => {
				const model = Object.create(placeholder);
				definitions.set(model, config);
				storePointer.set(model, store);
				if (enumerable) model.id = id;
				return Object.freeze(model);
			},
			isInstance: (model) => Object.getPrototypeOf(model) !== placeholder,
			invalidate: () => {
				if (!invalidatePromise) invalidatePromise = resolvedPromise.then(() => {
					invalidate(config, config, { clearValue: true });
					invalidatePromise = null;
				});
			},
			checks
		};
		configs.set(Model, config);
		config.storage = setupStorage(config, storage || memoryStorage(config));
		const transform = Object.keys(Object.freeze(Model)).map((key) => {
			if (key !== "id") Object.defineProperty(placeholder, key, {
				get() {
					throw Error(stringifyModel(Model, `Model instance in ${getModelState(this).state} state - use store.pending(), store.error(), or store.ready() guards`));
				},
				enumerable: true
			});
			if (key === "id") {
				if (Model[key] !== true) throw TypeError(stringifyModel(Model, "The 'id' property in the model definition must be set to 'true' or not be defined"));
				return (model, data, lastModel) => {
					let id;
					if (hasOwnProperty.call(data, "id")) id = normalizeId(data.id);
					else if (lastModel) id = lastModel.id;
					else id = uuid();
					Object.defineProperty(model, "id", {
						value: id,
						enumerable: true
					});
				};
			}
			const { defaultValue, type } = resolveKey(Model, key, config);
			switch (type) {
				case "function": return (model) => {
					Object.defineProperty(model, key, { get() {
						return get$1(this, key, () => defaultValue(this));
					} });
				};
				case "object": {
					if (defaultValue === null) throw TypeError(stringifyModel(Model, `The value for the '${key}' must be an object instance: ${defaultValue}`));
					if (Array.isArray(defaultValue)) {
						const nestedType = typeof defaultValue[0];
						if (nestedType === "undefined") throw TypeError(stringifyModel(Model, `The first item of the '${key}' array must be defined`));
						if (nestedType !== "object") {
							if (nestedType === "function" && ![
								String,
								Number,
								Boolean
							].includes(defaultValue[0])) throw TypeError(stringifyModel(Model, `The array item for the '${key}' must be one of the primitive types constructor: String, Number, or Boolean`));
							const Constructor = nestedType === "function" ? defaultValue[0] : getTypeConstructor(nestedType, key);
							const defaultArray = nestedType === "function" ? [] : Object.freeze(defaultValue.map(Constructor));
							return (model, data, lastModel) => {
								if (hasOwnProperty.call(data, key)) {
									if (!Array.isArray(data[key])) throw TypeError(`The value for '${key}' property must be an array: ${typeof data[key]}`);
									model[key] = Object.freeze(data[key].map(Constructor));
								} else if (lastModel && hasOwnProperty.call(lastModel, key)) model[key] = lastModel[key];
								else model[key] = defaultArray;
							};
						}
						const localConfig = bootstrap(defaultValue, true);
						if (localConfig.external && config.storage.offline && localConfig.storage.offline && localConfig.storage.offline.threshold < config.storage.offline.threshold) throw Error(stringifyModel(Model, `External nested model for '${key}' property has lower offline threshold (${localConfig.storage.offline.threshold} ms) than the parent definition (${config.storage.offline.threshold} ms)`));
						if (localConfig.enumerable && defaultValue[1]) {
							const nestedOptions = defaultValue[1];
							if (typeof nestedOptions !== "object") throw TypeError(stringifyModel(Model, `Options for '${key}' array property must be an object instance: ${typeof nestedOptions}`));
							if (nestedOptions.loose) {
								config.contexts = config.contexts || /* @__PURE__ */ new Set();
								config.contexts.add(bootstrap(defaultValue[0]));
							}
						}
						return (model, data, lastModel) => {
							if (hasOwnProperty.call(data, key)) {
								if (!Array.isArray(data[key])) throw TypeError(`The value for '${key}' property must be an array: ${typeof data[key]}`);
								model[key] = localConfig.create(data[key], true);
							} else model[key] = lastModel && lastModel[key] || !localConfig.enumerable && localConfig.create(defaultValue) || [];
						};
					}
					const nestedConfig = bootstrap(defaultValue, true);
					if (nestedConfig.enumerable || nestedConfig.external) {
						if (config.storage.offline && nestedConfig.storage.offline && nestedConfig.storage.offline.threshold < config.storage.offline.threshold) throw Error(stringifyModel(Model, `External nested model for '${key}' property has lower offline threshold (${nestedConfig.storage.offline.threshold} ms) than the parent definition (${config.storage.offline.threshold} ms)`));
						return (model, data, lastModel) => {
							let resultModel;
							if (hasOwnProperty.call(data, key)) {
								const nestedData = data[key];
								if (typeof nestedData !== "object" || nestedData === null) {
									if (nestedData !== void 0 && nestedData !== null) resultModel = { id: nestedData };
								} else {
									const dataConfig = definitions.get(nestedData);
									if (dataConfig) {
										if (dataConfig.model !== defaultValue) throw TypeError("Model instance must match the definition");
										resultModel = nestedData;
									} else {
										const lastNestedModel = getEntry(nestedConfig, data[key].id).value;
										resultModel = nestedConfig.create(nestedData, lastNestedModel && nestedConfig.isInstance(lastNestedModel) ? lastNestedModel : void 0);
										syncCache(nestedConfig, resultModel.id, resultModel);
									}
								}
							} else resultModel = lastModel && lastModel[key];
							if (resultModel) {
								const id = resultModel.id;
								Object.defineProperty(model, key, {
									get() {
										return get$1(this, key, () => get(defaultValue, id));
									},
									enumerable: true
								});
							} else model[key] = void 0;
						};
					}
					return (model, data, lastModel) => {
						if (hasOwnProperty.call(data, key)) model[key] = data[key] === null ? nestedConfig.create({}) : nestedConfig.create(data[key], lastModel && lastModel[key]);
						else model[key] = lastModel ? lastModel[key] : nestedConfig.create({});
					};
				}
				case "record": {
					parentModels.set(defaultValue, Model);
					const localConfig = bootstrap(defaultValue, true);
					return (model, data, lastModel) => {
						if (data[key] === null) {
							model[key] = {};
							return;
						}
						if (data[key] === void 0) {
							model[key] = lastModel?.[key] ?? {};
							return;
						}
						if (typeof data[key] !== "object") throw TypeError(stringifyModel(Model, `The value for the '${key}' must be an object instance: ${typeof data[key]}`));
						const record = data[key];
						let result = {};
						if (lastModel) {
							for (const id of Object.keys(lastModel[key])) if (!hasOwnProperty.call(record, id)) Object.defineProperty(result, id, {
								get() {
									return lastModel[key][id];
								},
								enumerable: true,
								configurable: true
							});
						}
						for (const id of Object.keys(record)) {
							if (record[id] === null) continue;
							const item = localConfig.create({
								id,
								[key]: record[id]
							}, {
								id,
								[key]: lastModel && lastModel[key][id]
							});
							Object.defineProperty(result, id, {
								get() {
									return get$1(this, id, () => item[key]);
								},
								enumerable: true
							});
						}
						model[key] = result;
					};
				}
				default: {
					const Constructor = getTypeConstructor(type, key);
					return (model, data, lastModel) => {
						if (hasOwnProperty.call(data, key)) model[key] = Constructor(data[key]);
						else if (lastModel && hasOwnProperty.call(lastModel, key)) model[key] = lastModel[key];
						else model[key] = defaultValue;
					};
				}
			}
		});
		config.create = function create(data, lastModel) {
			if (data === null) return null;
			if (typeof data !== "object") throw TypeError(`Model values must be an object instance: ${data}`);
			const model = Object.create(proto);
			for (const fn of transform) fn(model, data, lastModel);
			definitions.set(model, config);
			storePointer.set(model, store);
			return Object.freeze(model);
		};
		Object.freeze(placeholder);
		Object.freeze(config);
	}
	return config;
}
var listPlaceholderPrototype = Object.getOwnPropertyNames(Array.prototype).reduce((acc, key) => {
	if (key === "length" || key === "constructor") return acc;
	Object.defineProperty(acc, key, { get() {
		throw Error(stringifyModel(get(definitions.get(this).model), `Model list instance in ${getModelState(this).state} state - use store.pending(), store.error(), or store.ready() guards`));
	} });
	return acc;
}, []);
var lists = /* @__PURE__ */ new WeakMap();
function setupListModel(Model, nested) {
	let config = lists.get(Model);
	if (config && !config.enumerable) {
		if (!nested && config.nested) throw TypeError(stringifyModel(Model, "Nested model definition cannot be used outside of the parent definition"));
	}
	if (!config) {
		const modelConfig = setupModel(Model);
		const contexts = /* @__PURE__ */ new Set();
		if (modelConfig.storage.loose) contexts.add(modelConfig);
		if (!nested) {
			if (!modelConfig.enumerable) throw TypeError(stringifyModel(Model, "Provided model definition does not support listing (it must be enumerable - set `id` property to `true`)"));
			if (!modelConfig.storage.list) throw TypeError(stringifyModel(Model, "Provided model definition storage does not support `list` action"));
		}
		nested = !modelConfig.enumerable && !modelConfig.external && nested;
		config = {
			list: true,
			nested,
			model: Model,
			contexts,
			enumerable: modelConfig.enumerable,
			external: modelConfig.external,
			placeholder: (id) => {
				const model = Object.create(listPlaceholderPrototype);
				definitions.set(model, config);
				Object.defineProperties(model, {
					id: { value: id },
					toString: { value: function() {
						return this.id;
					} }
				});
				return Object.freeze(model);
			},
			isInstance: (model) => Object.getPrototypeOf(model) !== listPlaceholderPrototype,
			create(items, invalidate = false) {
				if (items === null) return null;
				const result = [];
				for (const data of items) {
					let id;
					if (typeof data === "object" && data !== null) {
						id = stringifyId(data.id);
						const dataConfig = definitions.get(data);
						let model = data;
						if (dataConfig) {
							if (dataConfig.model !== Model) throw TypeError("Model instance must match the definition");
						} else {
							const lastModel = modelConfig.enumerable && getEntry(modelConfig, id).value;
							model = modelConfig.create(data, lastModel && modelConfig.isInstance(lastModel) ? lastModel : void 0);
							if (modelConfig.enumerable) {
								id = stringifyId(model.id);
								syncCache(modelConfig, id, model, invalidate);
							}
						}
						if (!modelConfig.enumerable) result.push(model);
					} else {
						if (!modelConfig.enumerable) throw TypeError(`Model instance must be an object: ${typeof data}`);
						id = stringifyId(data);
					}
					if (modelConfig.enumerable) {
						const key = result.length;
						Object.defineProperty(result, key, {
							get() {
								return get$1(this, key, () => get(Model, id));
							},
							enumerable: true
						});
					}
				}
				Object.defineProperties(result, {
					id: { value: items.id },
					toString: { value: function() {
						return this.id;
					} }
				});
				definitions.set(result, config);
				storePointer.set(result, store);
				return Object.freeze(result);
			}
		};
		config.storage = Object.freeze({
			...setupStorage(config, {
				cache: modelConfig.storage.cache,
				get: !nested && ((id) => modelConfig.storage.list(id))
			}),
			offline: modelConfig.storage.offline && {
				threshold: modelConfig.storage.offline.threshold,
				get: (id) => {
					const stringId = stringifyId(id);
					let result = modelConfig.storage.offline.get(hashCode(String(stringId)));
					if (result) {
						result = result.map((item) => modelConfig.storage.offline.get(item));
						result.id = stringId;
						return result;
					}
					return null;
				},
				set: (id, values) => {
					modelConfig.storage.offline.set(hashCode(String(stringifyId(id))), values.map((item) => {
						modelConfig.storage.offline.set(item.id, item);
						return item.id;
					}));
				}
			}
		});
		lists.set(Model, Object.freeze(config));
	}
	return config;
}
function normalizeId(id) {
	if (typeof id !== "object") return id !== void 0 ? String(id) : id;
	const result = {};
	for (const key of Object.keys(id).sort()) {
		if (typeof id[key] === "object" && id[key] !== null) throw TypeError(`You must use primitive value for '${key}' key: ${typeof id[key]}`);
		result[key] = id[key];
	}
	return result;
}
function stringifyId(id) {
	id = normalizeId(id);
	return typeof id === "object" ? JSON.stringify(id) : id;
}
var notFoundErrors = /* @__PURE__ */ new WeakSet();
function notFoundError(Model, stringId) {
	const err = Error(stringifyModel(Model, `Model instance ${stringId !== void 0 ? `with '${stringId}' id ` : ""}does not exist`));
	notFoundErrors.add(err);
	return err;
}
function mapError(model, err) {
	if (isDebugMode() && !notFoundErrors.has(err)) console.error(err);
	return setModelState(model, "error", err);
}
function get(Model, id) {
	const config = bootstrap(Model);
	let stringId;
	if (config.enumerable) {
		stringId = stringifyId(id);
		if (!stringId && !config.list && !draftMap.get(config)) throw TypeError(stringifyModel(Model, `Provided model definition requires non-empty id: "${stringId}"`));
	} else if (id !== void 0) throw TypeError(stringifyModel(Model, `Provided model definition does not support id: ${JSON.stringify(id)}`));
	const { offline, validate } = config.storage;
	const entry = getEntry(config, stringId);
	const cachedModel = entry.value;
	if (cachedModel) entry.resolved = entry.resolved && validate(cachedModel) || getModelState(cachedModel).state === "pending";
	return get$1(config, stringId, () => {
		id = normalizeId(id);
		if (config.contexts) for (const context of config.contexts) get$1(context, context, getCurrentTimestamp);
		const fallback = () => cachedModel || offline && config.create(offline.get(stringId)) || config.placeholder(id);
		let result;
		try {
			result = config.storage.get(id);
		} catch (e) {
			return setTimestamp(mapError(fallback(), e));
		}
		if (!(result instanceof Promise) && result !== void 0 && typeof result !== "object") throw TypeError(stringifyModel(Model, `Storage 'get' method must return a Promise, an instance, or null: ${result}`));
		try {
			if (typeof result !== "object" || result === null) {
				if (offline) offline.set(stringId, null);
				throw notFoundError(Model, stringId);
			}
		} catch (e) {
			return setTimestamp(mapError(fallback(), e));
		}
		if (result instanceof Promise) {
			result = result.then((data) => {
				if (data !== void 0 && typeof data !== "object") throw TypeError(stringifyModel(Model, `Storage 'get' method must resolve to an instance, or null: ${data}`));
				if (typeof data !== "object" || data === null) {
					if (offline) offline.set(stringId, null);
					throw notFoundError(Model, stringId);
				}
				if (data.id !== id) data.id = id;
				const model = config.create(data);
				if (offline) offline.set(stringId, model);
				return syncCache(config, stringId, setTimestamp(model));
			}).catch((e) => syncCache(config, stringId, mapError(fallback(), e)));
			return setModelState(fallback(), "pending", result);
		}
		if (result.id !== id) result.id = id;
		const model = config.create(result);
		if (offline) Promise.resolve().then(() => {
			offline.set(stringId, model);
		});
		return resolve(config, setTimestamp(model), cachedModel);
	});
}
var draftMap = /* @__PURE__ */ new WeakMap();
function getValidationError(errors) {
	const e = Error(`Model validation failed (${Object.keys(errors).join(", ")}) - read the details from 'errors' property`);
	e.errors = errors;
	return e;
}
function set(model, values = {}) {
	let config = definitions.get(model);
	if (config === null) {
		model = stales.get(model);
		config = definitions.get(model);
	}
	if (config === null) throw Error("Provided model instance has expired. Haven't you used stale value?");
	let isInstance = !!config;
	if (!config) config = bootstrap(model);
	if (config.nested) throw stringifyModel(config.model, TypeError("Setting provided nested model instance is not supported, use the root model instance"));
	if (config.list) throw TypeError("Listing model definition does not support 'set' method");
	if (!config.storage.set) throw stringifyModel(config.model, TypeError("Provided model definition storage does not support 'set' method"));
	if (!isInstance && !config.enumerable) {
		isInstance = true;
		model = get(model);
	}
	if (isInstance) {
		const promise = pending(model);
		if (promise) return promise.then((m) => set(m, values));
	}
	const isDraft = draftMap.get(config);
	let id;
	if (config.enumerable && !isInstance && (!values || typeof values !== "object")) throw TypeError(`Values must be an object instance: ${values}`);
	if (!isDraft && values && hasOwnProperty.call(values, "id")) {
		if (!config.enumerable) throw TypeError(`Values must not contain 'id' property: ${values.id}`);
		else if (isInstance && values.id !== model.id) throw TypeError(`You cannot change the 'id' property of the model instance: ${values.id}`);
	}
	const localModel = config.create(values, isInstance ? model : void 0);
	const keys = values ? Object.keys(values) : [];
	const errors = {};
	const lastError = isInstance && isDraft && error(model);
	let hasErrors = false;
	if (localModel && config.checks.size) for (const [key, fn] of config.checks.entries()) {
		if (keys.indexOf(key) === -1) {
			if (lastError && lastError.errors && lastError.errors[key]) {
				hasErrors = true;
				errors[key] = lastError.errors[key];
			}
			if (isDraft && localModel[key] == config.model[key]) continue;
		}
		let checkResult;
		try {
			checkResult = fn(localModel[key], key, localModel);
		} catch (e) {
			checkResult = e;
		}
		if (checkResult !== true && checkResult !== void 0) {
			hasErrors = true;
			errors[key] = checkResult || true;
		}
	}
	let result;
	try {
		if (hasErrors && !isDraft) throw getValidationError(errors);
		id = localModel ? localModel.id : model.id;
		result = config.storage.set(isInstance ? id : void 0, localModel, keys);
	} catch (e) {
		if (isInstance) setModelState(model, "error", e);
		return Promise.reject(e);
	}
	if (!(result instanceof Promise) && result !== void 0 && typeof result !== "object") throw TypeError(stringifyModel(config.model, `Storage 'set' method must return a Promise, an instance, or null: ${result}`));
	result = Promise.resolve(result).then((data) => {
		if (data !== void 0 && typeof data !== "object") throw TypeError(stringifyModel(config.model, `Storage 'set' method must resolve to an instance or null: ${data}`));
		const resultModel = data === localModel ? localModel : config.create(data);
		if (isInstance && resultModel && id !== resultModel.id) throw TypeError(stringifyModel(config.model, `Local and storage data must have the same id: '${id}', '${resultModel.id}'`));
		let resultId = resultModel ? resultModel.id : id;
		if (hasErrors && isDraft) setModelState(resultModel, "error", getValidationError(errors));
		if (isDraft && isInstance && hasOwnProperty.call(data, "id") && (!localModel || localModel.id !== model.id)) resultId = model.id;
		else if (config.storage.offline) config.storage.offline.set(resultId, resultModel);
		return syncCache(config, resultId, resultModel || mapError(config.placeholder(resultId), notFoundError(config.model, id)), true);
	}).catch((err) => {
		err = err !== void 0 ? err : Error("Undefined error");
		if (isInstance) setModelState(model, "error", err);
		throw err;
	});
	if (isInstance) setModelState(model, "pending", result);
	return result;
}
function sync(model, values) {
	if (typeof values !== "object") throw TypeError(`Values must be an object instance: ${values}`);
	let config = definitions.get(model);
	if (config === null) {
		model = stales.get(model);
		config = definitions.get(model);
	}
	if (config === null) throw Error("Provided model instance has expired. Haven't you used stale value?");
	if (config === void 0) {
		if (!values) throw TypeError("Values must be defined for usage with model definition");
		config = bootstrap(model);
		model = void 0;
	} else if (values && hasOwnProperty.call(values, "id")) throw TypeError(`Values must not contain 'id' property: ${values.id}`);
	if (config.list) throw TypeError("Listing model definition is not supported in sync method");
	const resultModel = config.create(values, model);
	const id = values ? resultModel.id : model.id;
	return syncCache(config, id, resultModel || mapError(config.placeholder(id), notFoundError(config.model, id)));
}
function clear(model, clearValue = true) {
	if (typeof model !== "object" || model === null) throw TypeError(`The first argument must be a model instance or a model definition: ${model}`);
	let config = definitions.get(model);
	if (config === null) throw Error("Provided model instance has expired. Haven't you used stale value from the outer scope?");
	if (config) {
		const offline = clearValue && config.storage.offline;
		if (offline) offline.set(model.id, null);
		invalidateTimestamp(model);
		invalidate(config, model.id, {
			clearValue,
			deleteEntry: clearValue
		});
	} else {
		if (!configs.get(model) && !lists.get(model[0])) return;
		config = bootstrap(model);
		const offline = clearValue && config.storage.offline;
		for (const entry of getEntries(config)) {
			if (entry.key === config) continue;
			if (offline) offline.set(entry.key, null);
			invalidateTimestamp(entry.value);
		}
		invalidateAll(config, {
			clearValue,
			deleteEntry: clearValue
		});
	}
}
function pending(...models) {
	let isPending = false;
	const result = models.map((model) => {
		try {
			const { state, value } = getModelState(model);
			if (state === "pending") {
				isPending = true;
				return value;
			}
		} catch (e) {}
		return Promise.resolve(model);
	});
	return isPending && (models.length > 1 ? Promise.all(result) : result[0]);
}
function resolveToLatest(model, id) {
	model = stales.get(model) || model;
	if (!definitions.get(model)) model = get(model, id);
	const promise = pending(model);
	if (!promise) {
		const e = error(model);
		return e ? Promise.reject(e) : Promise.resolve(model);
	}
	return promise.then((m) => resolveToLatest(m));
}
function error(model, property) {
	if (model === null || typeof model !== "object") return false;
	const state = getModelState(model);
	if (property !== void 0) {
		const errors = typeof state.error === "object" && state.error && state.error.errors;
		return property === null ? !errors && state.error : errors[property];
	}
	return state.error;
}
function ready(...models) {
	return models.length > 0 && models.every((model) => {
		const config = definitions.get(model);
		return !!(config && config.isInstance(model));
	});
}
function getValuesFromModel(model, values) {
	model = {
		...model,
		...values
	};
	delete model.id;
	return model;
}
function submit(draft, values = {}) {
	const config = definitions.get(draft);
	if (!config || !draftMap.has(config)) throw TypeError(`Provided model instance is not a draft: ${draft}`);
	if (pending(draft)) throw Error("Model draft in pending state");
	const modelConfig = draftMap.get(config);
	let result;
	if (getEntry(modelConfig, draft.id).value) {
		const model = get(modelConfig.model, draft.id);
		result = Promise.resolve(pending(model) || model).then((resolvedModel) => set(resolvedModel, getValuesFromModel(draft, values)));
	} else result = set(modelConfig.model, getValuesFromModel(draft, values));
	result = result.then((resultModel) => {
		setModelState(draft, "ready");
		return set(draft, resultModel).then(() => resultModel);
	}).catch((e) => {
		setModelState(draft, "error", e);
		return Promise.reject(e);
	});
	setModelState(draft, "pending", result);
	return result;
}
function required(value, key) {
	return !!value || `${key} is required`;
}
function valueWithValidation(defaultValue, validate = required, errorMessage = "") {
	switch (typeof defaultValue) {
		case "string":
			defaultValue = new String(defaultValue);
			break;
		case "number":
			defaultValue = new Number(defaultValue);
			break;
		case "boolean":
			defaultValue = new Boolean(defaultValue);
			break;
		default: throw TypeError(`Default value must be a string, number or boolean: ${typeof defaultValue}`);
	}
	let fn;
	if (validate instanceof RegExp) fn = (value) => validate.test(value) || errorMessage;
	else if (typeof validate === "function") fn = (...args) => {
		const result = validate(...args);
		return result !== true && result !== void 0 ? errorMessage || result : result;
	};
	else throw TypeError(`The second argument must be a RegExp instance or a function: ${typeof validate}`);
	validationMap.set(defaultValue, fn);
	return defaultValue;
}
function resolveId(value) {
	if (value && definitions.has(value)) return value.id;
	return value ?? void 0;
}
function resolveModel(Model, config, id) {
	id = resolveId(id);
	if (!config.enumerable && !config.list) return get(Model, id);
	const lastModel = getCurrentValue();
	const nextModel = id !== void 0 || config.list ? get(Model, id) : void 0;
	if (lastModel && nextModel && nextModel.id !== lastModel.id && ready(lastModel) && !ready(nextModel)) {
		const config = definitions.get(lastModel);
		const clone = Object.freeze(Object.create(lastModel));
		definitions.set(clone, config);
		assert(clone, "state", getModelState(nextModel), true);
		return clone;
	}
	return nextModel;
}
function resolveDraft(Model, config, id, value) {
	const lastValue = getCurrentValue();
	id = resolveId(id ?? lastValue?.id);
	if (id === void 0 && !lastValue && (value === void 0 || value === null)) if (config.enumerable) {
		const draftModel = config.create({});
		id = draftModel.id;
		syncCache(config, draftModel.id, draftModel, false);
	} else clear(config.model);
	return get(Model, id);
}
function store(Model, options = {}) {
	const config = bootstrap(Model);
	if (options.id !== void 0 && typeof options.id !== "function") {
		const id = options.id;
		options.id = (host) => host[id];
	}
	if (options.id && !config.enumerable) throw TypeError("Store factory for singleton model definition does not support 'id' option");
	let draft;
	if (options.draft) {
		if (config.list) throw TypeError("Draft mode is not supported for listing model definition");
		draft = bootstrap({
			...Model,
			[connect]: {
				get(id) {
					const value = getCurrentValue();
					if (value) return value;
					const model = get(config.model, id);
					return pending(model) || model;
				},
				set(id, values) {
					return values === null ? { id } : values;
				}
			}
		});
		draftMap.set(draft, config);
		Model = draft.model;
		return {
			value: options.id ? (host, value) => resolveDraft(Model, draft, options.id(host), value) : (host, value) => resolveDraft(Model, draft, value, value),
			connect: config.enumerable ? (host, key) => () => {
				clear(host[key], true);
			} : void 0
		};
	}
	return { value: options.id ? (host) => resolveModel(Model, config, options.id(host)) : (host, value) => resolveModel(Model, config, value) };
}
var store_default = Object.freeze(Object.assign(store, {
	connect,
	get,
	set,
	sync,
	clear,
	pending,
	error,
	ready,
	submit,
	value: valueWithValidation,
	resolve: resolveToLatest,
	ref,
	record,
	observe
}));
//#endregion
export { store_default as default };
