import { nanoid } from '../../nanoid.js';
import { Permission } from './Permission.js';
import { Kind as UserKind } from './User.js';
import { Thing } from '../Thing.js';
import { FolderKind } from '../kinds.js';
const parentsSymbol = Symbol('parents');
const deletedSymbol = Symbol('deleted');
const deletedInfoSymbol = Symbol('deletedInfo');
const lastModifiedSymbol = Symbol('lastModified');
const capabilitiesSymbol = Symbol('capabilities');
const permissionsSymbol = Symbol('permissions');
const permissionIdsSymbol = Symbol('permissionIds');
const shortcutTargetSymbol = Symbol('shortcutTarget');
export class StoredFile {
    /**
     * The kind of the File
     */
    kind = '';
    /**
     * The identifier of the entity
     */
    key = '';
    /**
     * The name of the environment.
     */
    info = Thing.fromName('');
    /**
     * The color of the icon to render for this file in the file explorer.
     */
    iconColor;
    /**
     * Whether the file is a shortcut to another file.
     * Many properties cannot be changed of a shortcut, like, shortcuts cannot be shared.
     */
    get isShortcut() {
        return !!this.shortcutTarget;
    }
    [shortcutTargetSymbol];
    /**
     * The ID of the target file.
     */
    get shortcutTarget() {
        return this[shortcutTargetSymbol];
    }
    set shortcutTarget(value) {
        this[shortcutTargetSymbol] = value;
    }
    /**
     * Creates a file schema object with the given properties.
     * The `key` is generated if not set.
     *
     * @param input The input to create the file schema.
     * @returns The file schema object.
     */
    static createSchema(input = {}) {
        const { key = nanoid(), kind, iconColor, shortcutTarget } = input;
        const result = {
            key,
            kind: kind || '',
            info: Thing.fromJSON(input.info, { name: 'Unnamed file' }).toJSON(),
        };
        if (iconColor) {
            result.iconColor = iconColor;
        }
        if (shortcutTarget) {
            result.isShortcut = true;
            result.shortcutTarget = shortcutTarget;
        }
        return result;
    }
    constructor(state) {
        this.kind = state.kind;
        this.key = state.key;
        this.info = new Thing(state.info);
        if (state.iconColor) {
            this.iconColor = state.iconColor;
        }
        if (state.shortcutTarget) {
            this.shortcutTarget = state.shortcutTarget;
        }
    }
    /**
     * @deprecated Use `createSchema()` and setup values in the constructor instead.
     */
    new(init) {
        const { key = nanoid(), info, kind, iconColor, shortcutTarget } = init;
        this.key = key;
        this.kind = kind;
        if (info) {
            this.info = new Thing(info);
        }
        else {
            this.info = Thing.fromName('');
        }
        if (iconColor) {
            this.iconColor = iconColor;
        }
        else {
            this.iconColor = undefined;
        }
        if (shortcutTarget) {
            this.shortcutTarget = shortcutTarget;
        }
        else {
            this.shortcutTarget = undefined;
        }
        return this;
    }
    toJSON() {
        const result = {
            key: this.key,
            kind: this.kind,
            info: this.info.toJSON(),
        };
        if (this.iconColor) {
            result.iconColor = this.iconColor;
        }
        if (this.shortcutTarget) {
            result.isShortcut = true;
            result.shortcutTarget = this.shortcutTarget;
        }
        return result;
    }
    /**
     * Creates the Capabilities object for a file giving user level.
     *
     * @param file The file object to create the capabilities to. The object is not mutated.
     * @param role The user role to the file.
     */
    static createFileCapabilities(file, role) {
        const isCommenter = Permission.hasRole('commenter', role);
        const isOwner = Permission.hasRole('owner', role);
        const isReader = Permission.hasRole('reader', role);
        const isWriter = Permission.hasRole('writer', role);
        const isFolder = file.kind === FolderKind;
        const result = {
            canReadMedia: !isFolder && isReader,
            canListChildren: isFolder && isReader,
            canComment: !isFolder && isCommenter,
            canEditMedia: !isFolder && isWriter,
            canEditMeta: isWriter,
            canReadRevisions: isWriter,
            canAddChildren: isFolder && isWriter,
            canDelete: isOwner,
            canTrash: isOwner,
            canUntrash: isOwner,
            canShare: isWriter,
            canMove: isWriter,
        };
        return result;
    }
    createFileCapabilities(role) {
        return File.createFileCapabilities(this, role);
    }
    /**
     * Mutates the file object by setting the `byMe` properties (on deleted and modified info)
     *
     * Note, this can be done only on file schema (IFile). The `File` object has
     * this properties frozen.
     *
     * @param file The file to mutate
     * @param user The user key to compare.
     */
    static updateByMeMeta(file, user) {
        if (file.deletedInfo) {
            file.deletedInfo.byMe = file.deletedInfo.user === user;
        }
        if (file.lastModified) {
            file.lastModified.byMe = file.lastModified.user === user;
        }
    }
    /**
     * Static method to set the `lastModified` property on a file without creating an instance of a file meta.
     * Useful when the meta kind is unknown.
     *
     * @param file The file to update. Note, this method mutates the file.
     * @param user The user that modifies the file.
     * @returns The same file.
     */
    static setLastModified(file, user) {
        if (!file) {
            throw new Error(`The file is required.`);
        }
        if (!user) {
            throw new Error(`The user is required.`);
        }
        if (user.kind !== UserKind) {
            throw new Error(`Invalid value for the user when setting "lastModified".`);
        }
        file.lastModified = {
            byMe: false,
            time: Date.now(),
            user: user.key,
            name: user.name,
        };
        return file;
    }
    /**
     * Similar to the instance method `setDeleted()` but without creating
     * an instance of the file when the type is unknown.
     *
     * @param file The file to change. Note, this operation mutates the file.
     * @param user The deleting user
     * @returns The same file reference.
     */
    static setDeleted(file, user) {
        if (!file) {
            throw new Error(`The file is required.`);
        }
        if (!user) {
            throw new Error(`The user is required.`);
        }
        if (user.kind !== UserKind) {
            throw new Error(`Invalid value for the user when setting "deletedInfo".`);
        }
        file.deleted = true;
        file.deletedInfo = {
            byMe: false,
            time: Date.now(),
            user: user.key,
            name: user.name,
        };
        return file;
    }
}
export class File extends StoredFile {
    [parentsSymbol] = [];
    /**
     * The list of parents of the object. It is an ordered list of parents
     * from the top (first element) to the lowest parent in the tree (last element).
     *
     * This property cannot be manipulated directly by the client. Should be treated as
     * opaque value.
     */
    get parents() {
        return this[parentsSymbol];
    }
    [permissionIdsSymbol] = [];
    /**
     * The list of permissions to this file object.
     *
     * This property cannot be manipulated directly by the client. Should be treated as
     * opaque value.
     */
    get permissionIds() {
        return this[permissionIdsSymbol];
    }
    [deletedSymbol];
    /**
     * Whether the file object is deleted.
     */
    get deleted() {
        return this[deletedSymbol] || false;
    }
    [deletedInfoSymbol];
    /**
     * The information about the delete information.
     * Always set when the `delete` is true.
     */
    get deletedInfo() {
        return this[deletedInfoSymbol];
    }
    [lastModifiedSymbol] = { user: '', time: 0, byMe: false };
    /**
     * The last modification made to this file.
     */
    get lastModified() {
        return this[lastModifiedSymbol];
    }
    /**
     * An arbitrary list of labels applied to the file.
     */
    labels;
    [capabilitiesSymbol];
    /**
     * This is populated when reading a file from the store.
     * A list of actions the user can perform on the file.
     *
     * This is a readonly field and it is ignored when creating / updating the file.
     */
    get capabilities() {
        return this[capabilitiesSymbol];
    }
    [permissionsSymbol] = [];
    /**
     * Populated by the server when reading the file. The list of permissions to the object.
     *
     * This property cannot be manipulated directly by the client. Should be treated as
     * opaque value.
     */
    get permissions() {
        return this[permissionsSymbol];
    }
    static createSchema(input = {}) {
        const result = StoredFile.createSchema(input);
        if (Array.isArray(input.parents)) {
            result.parents = [...input.parents];
        }
        else {
            result.parents = [];
        }
        if (Array.isArray(input.permissionIds)) {
            result.permissionIds = [...input.permissionIds];
        }
        else {
            result.permissionIds = [];
        }
        if (Array.isArray(input.permissions)) {
            result.permissions = input.permissions.map((i) => ({ ...i }));
        }
        else {
            result.permissions = [];
        }
        if (input.capabilities) {
            result.capabilities = { ...input.capabilities };
        }
        if (input.lastModified) {
            result.lastModified = { ...input.lastModified };
        }
        else {
            result.lastModified = { user: '', time: 0, byMe: false };
        }
        if (typeof input.deleted === 'boolean') {
            result.deleted = input.deleted;
            result.deletedInfo = input.deletedInfo ? { ...input.deletedInfo } : undefined;
        }
        if (Array.isArray(input.labels)) {
            result.labels = [...input.labels];
        }
        if (typeof input.shortcutTarget === 'string') {
            result.shortcutTarget = input.shortcutTarget;
        }
        return result;
    }
    constructor(state) {
        const init = File.createSchema(state);
        super(init);
        this[parentsSymbol] = [...init.parents];
        this[permissionIdsSymbol] = [...init.permissionIds];
        this[permissionsSymbol] = init.permissions.map((i) => ({ ...i }));
        this[capabilitiesSymbol] = init.capabilities ? { ...init.capabilities } : undefined;
        this[lastModifiedSymbol] = Object.freeze({ ...init.lastModified });
        if (typeof init.deleted === 'boolean') {
            this[deletedSymbol] = init.deleted;
            this[deletedInfoSymbol] = init.deletedInfo ? Object.freeze({ ...init.deletedInfo }) : undefined;
        }
        if (Array.isArray(init.labels)) {
            this.labels = [...init.labels];
        }
    }
    /**
     * @deprecated Use `createSchema()` and setup values in the constructor instead.
     */
    new(init) {
        super.new(init);
        const { permissions = [], parents = [], permissionIds = [], deleted, deletedInfo, lastModified, labels } = init;
        this[permissionsSymbol] = permissions.map((i) => ({ ...i }));
        this[parentsSymbol] = [...parents];
        this[permissionIdsSymbol] = [...permissionIds];
        this[lastModifiedSymbol] = lastModified
            ? Object.freeze({ ...lastModified })
            : Object.freeze({ user: '', time: 0, byMe: false });
        if (typeof deleted === 'boolean') {
            this[deletedSymbol] = deleted;
            this[deletedInfoSymbol] = deletedInfo ? Object.freeze({ ...deletedInfo }) : undefined;
        }
        else {
            this[deletedSymbol] = undefined;
            this[deletedInfoSymbol] = undefined;
        }
        if (Array.isArray(labels)) {
            this.labels = [...labels];
        }
        else {
            this.labels = undefined;
        }
        return this;
    }
    toJSON() {
        const result = {
            ...super.toJSON(),
            parents: [...this.parents],
            permissionIds: [...this.permissionIds],
            lastModified: { ...this.lastModified },
            permissions: [...this.permissions],
        };
        if (this.deleted) {
            result.deleted = this.deleted;
            result.deletedInfo = { ...this.deletedInfo };
        }
        if (Array.isArray(this.labels)) {
            result.labels = [...this.labels];
        }
        return result;
    }
    /**
     * @param name The name to set.
     * @param kind The kind of the file to create.
     */
    static fromName(name, kind) {
        const definition = new File({ kind, info: { name } });
        return definition;
    }
    shortcutTo(fileId) {
        this.shortcutTarget = fileId;
        return this;
    }
    /**
     * Updates the "lastModified" value.
     * A helper method for a common task.
     *
     * @param user The user that modifies the entity.
     */
    setLastModified(user) {
        if (!user) {
            throw new Error(`The user is required.`);
        }
        if (user.kind !== UserKind) {
            throw new Error(`Invalid value for the user when setting "lastModified".`);
        }
        this[lastModifiedSymbol] = {
            byMe: false,
            time: Date.now(),
            user: user.key,
            name: user.name,
        };
    }
    /**
     * Adds a label to the list of labels.
     *
     * It makes sure the value is a valid, non-empty string and the `labels` array is set.
     *
     * It does nothing when the label already exists.
     *
     * @param label The label to set.
     */
    addLabel(label) {
        if (typeof label !== 'string') {
            throw new Error(`The label must be a string.`);
        }
        if (!label.trim()) {
            throw new Error(`The label is required.`);
        }
        if (!Array.isArray(this.labels)) {
            this.labels = [];
        }
        if (this.labels.includes(label)) {
            return;
        }
        this.labels.push(label);
    }
    /**
     * Marks the entity as deleted.
     *
     * A helper method for a common task.
     *
     * @param user The user that deletes the entity.
     */
    setDeleted(user) {
        if (!user) {
            throw new Error(`The user is required.`);
        }
        if (user.kind !== UserKind) {
            throw new Error(`Invalid value for the user when setting "deletedInfo".`);
        }
        this[deletedSymbol] = true;
        this[deletedInfoSymbol] = {
            byMe: false,
            time: Date.now(),
            user: user.key,
            name: user.name,
        };
    }
    isFile() {
        return this.kind !== FolderKind;
    }
    isFolder() {
        return this.kind === FolderKind;
    }
}
//# sourceMappingURL=File.js.map