import type Nymph from './Nymph.js';
import type { Options } from './Nymph.types.js';
import type { ACProperties, EntityConstructor, EntityData, EntityInterface, EntityJson, EntityObject, EntityPatch, EntityReference, SerializedEntityData } from './Entity.types.js';
export type EntityDataType<T> = T extends Entity<infer DataType> ? DataType : never;
export type EntityInstanceType<T extends EntityConstructor> = T extends new () => infer E ? E & EntityDataType<E> : never;
export type EntityObjectType<T extends EntityConstructor> = T extends new () => infer E ? EntityObject & EntityDataType<E> : never;
/**
 * Database abstraction object.
 *
 * Provides a way to access, manipulate, and store data in Nymph.
 *
 * The GUID is not set until the entity is saved. GUIDs must be unique forever,
 * even after deletion. It's the job of the Nymph DB driver to make sure no two
 * entities ever have the same GUID. This is generally done by using a large
 * randomly generated ID.
 *
 * Each entity class has an etype that determines which table(s) in the database
 * it belongs to. If two entity classes have the same etype, their data will be
 * stored in the same table(s). This isn't a good idea, however, because
 * references to an entity store a class name, not an etype.
 *
 * Tags are used to classify entities. Where an etype is used to separate data
 * by tables, tags can be used to separate entities within a table. You can
 * define specific tags to be protected, meaning they cannot be added/removed
 * from the client. It can be useful to allow user defined tags, such as for
 * blog posts.
 *
 * Simply calling $delete() will not unset the entity. It will still take up
 * memory. Likewise, simply calling unset will not delete the entity from the
 * DB.
 *
 * Some notes about $equals() and $is(), the replacements for "==":
 *
 * The == operator will likely not give you the result you want, since two
 * instances of the same entity will fail that check, even though they represent
 * the same data in the database.
 *
 * $equals() performs a more strict comparison of the entity to another. Use
 * $equals() instead of the == operator when you want to check both the entities
 * they represent, and the data inside them. In order to return true for
 * $equals(), the entity and object must meet the following criteria:
 *
 * - They must be entities.
 * - They must have equal GUIDs, or both must have no GUID.
 * - Their data and tags must be equal.
 *
 * $is() performs a less strict comparison of the entity to another. Use $is()
 * instead of the == operator when the entity's data may have been changed, but
 * you only care if they represent the same entity. In order to return true, the
 * entity and object must meet the following criteria:
 *
 * - They must be entities.
 * - They must have equal GUIDs, or both must have no GUID.
 * - If they have no GUIDs, their data and tags must be equal.
 *
 * Some notes about saving entities in other entity's properties:
 *
 * Entities use references in the DB to store an entity in their properties. The
 * reference is stored as an array with the values:
 *
 * - 0 => The string 'nymph_entity_reference'
 * - 1 => The referenced entity's GUID.
 * - 2 => The referenced entity's class name.
 *
 * Since the referenced entity's class name (meaning the `class` static
 * property, not the name of the class itself) is stored in the reference on the
 * parent entity, if you change the class name in an update, you need to
 * reassign all referenced entities of that class and resave.
 *
 * When an entity is loaded, it does not request its referenced entities from
 * Nymph. Instead, it creates instances without data called sleeping references.
 * When you first access an entity's data, if it is a sleeping reference, it
 * will fill its data from the DB. You can call clearCache() to turn all the
 * entities back into sleeping references.
 */
export default class Entity<T extends EntityData = EntityData> implements EntityInterface {
    /**
     * The instance of Nymph to use for queries.
     */
    static nymph: Nymph;
    /**
     * A unique name for this type of entity used to separate its data from other
     * types of entities in the database.
     */
    static ETYPE: string;
    /**
     * The lookup name for this entity.
     *
     * This is used for reference arrays (and sleeping references) and client
     * requests.
     */
    static class: string;
    $nymph: Nymph;
    guid: string | null;
    cdate: number | null;
    mdate: number | null;
    tags: string[];
    /**
     * The data proxy handler.
     */
    protected $dataHandler: Object;
    /**
     * The actual data store.
     */
    protected $dataStore: T;
    /**
     * The actual sdata store.
     */
    protected $sdata: SerializedEntityData;
    /**
     * The data proxy object.
     */
    protected $data: T;
    /**
     * Whether this instance is a sleeping reference.
     */
    protected $isASleepingReference: boolean;
    /**
     * The reference to use to wake.
     */
    protected $sleepingReference: EntityReference | null;
    /**
     * A promise that resolved when the entity's data is wake.
     */
    protected $wakePromise: Promise<Entity<T>> | null;
    /**
     * Properties that will not be serialized into JSON with toJSON(). This
     * can be considered a denylist, because these properties will not be set
     * with incoming JSON.
     *
     * Clients CAN still determine what is in these properties, unless they are
     * also listed in searchRestrictedData.
     */
    protected $privateData: string[];
    /**
     * Whether this entity should publish changes to PubSub servers.
     */
    static pubSubEnabled: boolean;
    /**
     * Whether this entity should be accessible on the frontend through the REST
     * server.
     *
     * If this is false, any request from the client that attempts to use this
     * entity will fail.
     */
    static restEnabled: boolean;
    /**
     * Properties that will not be searchable from the frontend. If the frontend
     * includes any of these properties in any of their clauses, they will be
     * filtered out before the search is executed.
     */
    static searchRestrictedData: string[];
    /**
     * Properties that can only be modified by server side code. They will still
     * be visible on the frontend, unlike $privateData, but any changes to them
     * that come from the frontend will be ignored.
     *
     * In addition to what's listed here, all of the access control properties
     * will be included when Tilmeld is being used. These are:
     *
     * - acUser
     * - acGroup
     * - acOther
     * - acRead
     * - acWrite
     * - acFull
     * - user
     * - group
     *
     * You should modify these through client enabled methods or the $save method
     * instead, for safety.
     */
    protected $protectedData: string[];
    /**
     * If this is defined, then it lists the only properties that will be
     * accepted from incoming JSON. Any other properties will be ignored.
     *
     * If you use an allowlist, you don't need to use protectedData, since you
     * can simply leave those entries out of allowlistData.
     */
    protected $allowlistData?: string[];
    /**
     * Tags that can only be added/removed by server side code. They will still be
     * visible on the frontend, but any changes to them that come from the
     * frontend will be ignored.
     */
    protected $protectedTags: string[];
    /**
     * If this is defined, then it lists the only tags that will be accepted from
     * incoming JSON. Any other tags will be ignored.
     */
    protected $allowlistTags?: string[];
    /**
     * The names of methods allowed to be called by the frontend with serverCall.
     */
    protected $clientEnabledMethods: string[];
    /**
     * The names of static methods allowed to be called by the frontend with
     * serverCallStatic.
     */
    static clientEnabledStaticMethods: string[];
    /**
     * Whether to use "skipAc" when accessing entity references.
     */
    private $skipAc;
    /**
     * The AC properties' values when the entity was loaded.
     */
    private $originalAcValues;
    /**
     * This is used to hold a generated GUID for a new entity.
     */
    private $guaranteedGUID;
    /**
     * Alter the options for a query for this entity.
     *
     * @param options The current options.
     * @returns The altered options.
     */
    static alterOptions?<T extends Options>(options: T): T;
    /**
     * Initialize an entity.
     */
    constructor(..._rest: any[]);
    /**
     * Create or retrieve a new entity instance.
     *
     * Note that this will always return an entity, even if the GUID is not found.
     *
     * @param guid An optional GUID to retrieve.
     */
    static factory<E extends Entity>(this: {
        new (): E;
    }, guid?: string): Promise<E & EntityDataType<E>>;
    /**
     * Create a new entity instance.
     */
    static factorySync<E extends Entity>(this: {
        new (): E;
    }): E & EntityDataType<E>;
    /**
     * Create a new sleeping reference instance.
     *
     * Sleeping references won't retrieve their data from the database until they
     * are readied with `$wake()` or a parent's `$wakeAll()`.
     *
     * @param reference The Nymph Entity Reference to use to wake.
     * @returns The new instance.
     */
    static factoryReference<E extends Entity>(this: {
        new (): E;
    }, reference: EntityReference): E & EntityDataType<E>;
    /**
     * Get an array of strings that **must** be unique across the current etype.
     *
     * When you try to save another entity with any of the same unique strings,
     * Nymph will throw an error.
     *
     * The default implementation of this static method instantiates the entity,
     * assigns all of the given data, then calls `$getUniques` and returns its
     * output. This can have a performance impact if a lot of extra processing
     * happens during any of these steps. You can override this method to
     * calculate the unique strings faster, but you must return the same strings
     * that would be returned by `$getUniques`.
     *
     * @returns Resolves to an array of entity's unique constraint strings.
     */
    static getUniques({ guid, cdate, mdate, tags, data, sdata, }: {
        guid?: string;
        cdate?: number;
        mdate?: number;
        tags: string[];
        data: EntityData;
        sdata?: SerializedEntityData;
    }): Promise<string[]>;
    toJSON(): EntityReference | EntityJson | null;
    $getGuaranteedGUID(): string;
    $addTag(...tags: string[]): void;
    $arraySearch(array: any[], strict?: boolean): number;
    $clearCache(): void;
    $getClientEnabledMethods(): string[];
    $delete(): Promise<boolean>;
    $equals(object: any): boolean;
    $getData(includeSData?: boolean, referenceOnlyExisting?: boolean): any;
    $getSData(): SerializedEntityData;
    $getUniques(): Promise<string[]>;
    $getOriginalAcValues(): ACProperties;
    $getCurrentAcValues(): ACProperties;
    $getAcUid(): any;
    $getAcGid(): any;
    $getAcReadIds(): (string | null)[] | null;
    $getAcWriteIds(): (string | null)[] | null;
    $getAcFullIds(): (string | null)[] | null;
    $getValidatable(): any;
    $getTags(): string[];
    $hasTag(...tags: string[]): boolean;
    $inArray(array: any[], strict?: boolean): boolean;
    $is(object: any): boolean;
    $jsonAcceptData(input: EntityJson, allowConflict?: boolean): void;
    $jsonAcceptPatch(patch: EntityPatch, allowConflict?: boolean): void;
    $putData(data: EntityData, sdata?: SerializedEntityData, _source?: 'server'): void;
    /**
     * Set up a sleeping reference.
     * @param array $reference The reference to use to wake.
     */
    protected $referenceSleep(reference: EntityReference): void;
    /**
     * Check if this is a sleeping reference and throw an error if so.
     */
    protected $check(): void;
    /**
     * Check if this is a sleeping reference.
     */
    $asleep(): boolean;
    /**
     * Wake from a sleeping reference.
     */
    $wake(): Promise<Entity<T>>;
    $wakeAll(level?: number): Promise<Entity<T>>;
    $refresh(): Promise<boolean | 0>;
    $removeTag(...tags: string[]): void;
    $save(): Promise<boolean>;
    $toReference(existingOnly?: boolean): this | EntityReference;
    $useSkipAc(skipAc: boolean): void;
}
