import { __decorate } from "tslib";
import { html, nothing, } from 'lit';
import { property, query, state } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { Events as CoreEvents, } from '@api-client/core/build/browser.js';
import { Patch, Core as JsonCore } from '@api-client/json';
import { Events } from '../../events/Events.js';
import { randomString } from '../../lib/Random.js';
import { StoreBroadcast } from '../../http-client/store/StoreBroadcast.js';
import UiDialog from '../../ui/dialog/UiDialog.js';
import { ElementPositioning } from '../../ui/lib/ElementPositioning.js';
import { SnackNotifications } from '../../ui/notification/SnackNotifications.js';
import '../../define/ui/ui-listbox.js';
import '../../define/ui/ui-list-item.js';
import '../../define/ui/ui-list.js';
import '../../define/ui/ui-button.js';
import '../../define/ui/ui-icon.js';
import '../../define/ui/ui-chip.js';
import '../../define/ui/ui-divider.js';
import '../../define/ui/ui-dropdown-list.js';
import '../../define/user/user-avatar.js';
const NAVIGATION_KEYS = {
    ArrowDown: 'ArrowDown',
    ArrowUp: 'ArrowUp',
    Home: 'Home',
    End: 'End',
    Escape: 'Escape',
    Enter: 'Enter',
    Space: ' ',
    Backspace: 'Backspace',
};
const roles = {
    reader: { label: 'Reader', title: 'Shares the file as read only' },
    commenter: { label: 'Commenter', title: 'Allows users to collaborate by leaving comments (when applicable)' },
    writer: { label: 'Writer', title: "Allows users to edit contents of the file. It also allows to share or delete file." },
    owner: { label: 'Owner', title: 'The same permissions as the owner of the file' },
};
/**
 * A dialog that shows users that the file is shared with and allows to share a file.
 *
 * Listen to the `closed` on the dialog. The CustomEvent has the `detail`
 * object with the `closingReason` retails:
 * - canceled: whether the dialog was cancelled (ESC, outside click, dismiss button)
 * - confirmed: whether the dialog was confirmed by the user
 *
 * ```javascript
 * const dialog = document.createElement('share-file');
 * dialog.key = 'file key';
 * // or
 * dialog.file = file;
 * dialog.appInfo="${AppInfo}"
 * dialog.user="${currentUser}"
 * dialog.opened = true;
 * document.body.appendChild(dialog);
 * dialog.addEventListener('closed', (ev: Event) => {
 *   document.body.removeChild(dialog);
 * });
 * ```
 */
export default class ShareFile extends UiDialog {
    /**
     * The key of the file to share.
     */
    get key() {
        return this.keyInternal;
    }
    set key(value) {
        const old = this.keyInternal;
        if (old === value) {
            return;
        }
        this.keyInternal = value;
        this.processFileKey(value);
    }
    /**
     * When known, set this instead of the `key` to skip requesting the file info.
     * Also, when file is set and then the `key` is set the HTTP request won't be made.
     */
    get file() {
        return this.fileInternal;
    }
    set file(value) {
        const old = this.fileInternal;
        if (old === value) {
            return;
        }
        if (old && value && old.key === value.key) {
            return;
        }
        this.fileInternal = value ? JsonCore.clone(value) : undefined;
        if (value) {
            this.requestFileUsers(value.key);
        }
    }
    /**
     * Computed value of whether currently loading file or file users.
     */
    get loading() {
        return this.loadingFile || this.loadingFileUsers;
    }
    /**
     * Computed value telling whether the main input should be disabled for the current state.
     */
    get inputDisabled() {
        const { file } = this;
        if (!file) {
            return true;
        }
        const { capabilities } = file;
        if (!capabilities) {
            return true;
        }
        return !capabilities.canShare;
    }
    /**
     * Computed value whether there are selected users
     */
    get hasSelectedUsers() {
        const { selectedUsers } = this;
        if (!Array.isArray(selectedUsers) || !selectedUsers.length) {
            return false;
        }
        return true;
    }
    /**
     * Whether there are pending permissions to be set.
     */
    get hasPending() {
        const { pendingPermissions } = this;
        return !!pendingPermissions && !!pendingPermissions.length;
    }
    get fileId() {
        const { key, file } = this;
        if (key) {
            return key;
        }
        if (file) {
            return file.key;
        }
        throw new Error(`Invalid state, file is not set.`);
    }
    constructor() {
        super();
        /**
         * A flag set when requesting the the file meta
         */
        this.loadingFile = false;
        /**
         * A flag set when requesting the current list of users for the file.
         */
        this.loadingFileUsers = false;
        /**
         * Whether the suggestion box is opened.
         */
        this.suggestionsOpened = false;
        /**
         * The list of users that have permission to the file read from the store
         * when the component was initializing.
         */
        this.permissionUsers = [];
        this.filesChannel = new BroadcastChannel(StoreBroadcast.files);
        this.selectedRole = 'reader';
        this.fileMetaMessageHandler = this.fileMetaMessageHandler.bind(this);
    }
    connectedCallback() {
        super.connectedCallback();
        this.filesChannel.addEventListener('message', this.fileMetaMessageHandler);
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        this.filesChannel.removeEventListener('message', this.fileMetaMessageHandler);
    }
    fileMetaMessageHandler(e) {
        const event = e.data;
        if (event.operation === 'patch') {
            this.handleFilePatch(event);
        }
    }
    handleFilePatch(event) {
        const { key, alt } = event;
        if (this.fileId !== key || alt !== 'meta') {
            return;
        }
        const file = this.file;
        const rev = event.data;
        const { patch } = rev;
        const result = Patch.apply(file, patch);
        this.file = result.doc;
        const hasPermission = patch.some(i => i.path.startsWith('/permissions'));
        if (hasPermission) {
            this.requestFileUsers(file.key);
        }
        this.requestUpdate();
    }
    processFileKey(value) {
        if (!value) {
            this.file = undefined;
            this.selectedUsers = undefined;
            this.lastUserQuery = undefined;
            this.pendingPermissions = undefined;
            this.selectedUsers = undefined;
            this.userSuggestions = undefined;
            this.loadingFile = false;
            this.loadingFileUsers = false;
            this.permissionUsers = [];
            return;
        }
        this.requestFileInfo(value);
        this.requestFileUsers(value);
    }
    async requestFileInfo(key) {
        if (this.file && this.file.key === key) {
            return;
        }
        this.loadingFile = true;
        try {
            this.file = await Events.Store.File.read(key, false, this);
        }
        catch (e) {
            CoreEvents.Telemetry.exception(e.message, true, this);
        }
        finally {
            this.loadingFile = false;
        }
    }
    async requestFileUsers(key) {
        this.loadingFileUsers = true;
        try {
            const list = await Events.Store.File.listUsers(key);
            this.permissionUsers = list.items;
        }
        catch (e) {
            CoreEvents.Telemetry.exception(e.message, true, this);
        }
        finally {
            this.loadingFileUsers = false;
        }
    }
    handleInput(e) {
        const input = e.target;
        const { value } = input;
        this.queryUsers(value);
    }
    handleKeydown(event) {
        const { suggestionsOpened, suggestionsList } = this;
        if (!suggestionsList || event.defaultPrevented) {
            return;
        }
        if (Object.values(NAVIGATION_KEYS).indexOf(event.key) === -1)
            return;
        if (event.key === NAVIGATION_KEYS.Escape) {
            if (suggestionsOpened) {
                event.preventDefault();
                event.stopPropagation();
                this.toggleSuggestions(false);
            }
        }
        else if (event.key === NAVIGATION_KEYS.ArrowDown) {
            event.preventDefault();
            if (suggestionsOpened) {
                this.suggestionsList.highlightNext();
            }
            else {
                this.queryUsers('');
            }
        }
        else if (event.key === NAVIGATION_KEYS.ArrowUp) {
            event.preventDefault();
            if (suggestionsOpened) {
                this.suggestionsList.highlightPrevious();
            }
        }
        else if (event.key === NAVIGATION_KEYS.Home) {
            if (suggestionsOpened) {
                event.preventDefault();
                this.suggestionsList.highlightFirst();
            }
        }
        else if (event.key === NAVIGATION_KEYS.End) {
            if (suggestionsOpened) {
                event.preventDefault();
                this.suggestionsList.highlightLast();
            }
        }
        else if (event.key === NAVIGATION_KEYS.Enter) {
            event.preventDefault();
            if (suggestionsOpened) {
                const item = suggestionsList.highlightListItem;
                if (item) {
                    suggestionsList.notifySelect(item);
                }
            }
        }
        else if (event.key === NAVIGATION_KEYS.Backspace) {
            const input = event.target;
            if (!input.value) {
                event.preventDefault();
                this.removeLastSelectedUser();
            }
        }
    }
    handleSuggestionSelect(e) {
        const item = e.detail.item;
        const { key } = item.dataset;
        if (!key) {
            return;
        }
        this.selectUserSuggestion(key);
    }
    selectUserSuggestion(key) {
        this.toggleSuggestions(false);
        const { input, userSuggestions } = this;
        if (!input || !userSuggestions) {
            return;
        }
        input.value = '';
        const user = userSuggestions.find(i => i.key === key);
        if (!user) {
            return;
        }
        if (!this.selectedUsers) {
            this.selectedUsers = [user];
        }
        else {
            this.selectedUsers.push(user);
            this.requestUpdate();
        }
    }
    deselectUserSuggestion(key) {
        const { selectedUsers: users = [] } = this;
        const index = users.findIndex(u => u.key === key);
        if (index >= 0) {
            users.splice(index, 1);
            if (!users.length) {
                this.selectedUsers = undefined;
            }
            this.requestUpdate();
        }
    }
    handleUserChipRemove(e) {
        const chip = e.target;
        const { key } = chip.dataset;
        if (!key) {
            return;
        }
        this.deselectUserSuggestion(key);
    }
    removeLastSelectedUser() {
        const { selectedUsers } = this;
        if (!selectedUsers || !selectedUsers.length) {
            return;
        }
        const index = selectedUsers.length - 1;
        selectedUsers.splice(index, 1);
        if (!selectedUsers.length) {
            this.selectedUsers = undefined;
        }
        this.requestUpdate();
    }
    async queryUsers(q) {
        const { lastUserQuery, selectedUsers, userSuggestions, user } = this;
        let users = [];
        this.lastUserQuery = q;
        if (lastUserQuery && userSuggestions && q.includes(lastUserQuery)) {
            const lower = q.toLowerCase();
            // filter the current list for the continues query.
            // Not necessary to query the server as we have all results.
            users = userSuggestions.filter((i) => {
                const { name, email } = i;
                if (name && name.toLowerCase().includes(lower)) {
                    return true;
                }
                if (email) {
                    for (const item of email) {
                        if (item.email && item.email.toLowerCase().includes(lower)) {
                            return true;
                        }
                    }
                }
                return false;
            });
        }
        else {
            const result = await Events.Store.User.list({
                query: q,
            });
            users = result.items;
        }
        if (selectedUsers) {
            users = users.filter(candidate => !selectedUsers.some(i => i.key === candidate.key));
        }
        if (user) {
            users = users.filter(candidate => candidate.key !== user.key);
        }
        if (users.length) {
            this.userSuggestions = users;
            this.toggleSuggestions(true);
        }
        else {
            this.userSuggestions = undefined;
            this.toggleSuggestions(false);
        }
    }
    toggleSuggestions(opened) {
        if (!opened) {
            this.suggestionsOpened = false;
            this.input.focus();
        }
        else {
            this.suggestionStyles = ElementPositioning.positionOverlay(this.suggestionsContainer, this.input, {
                vertical: 'top',
                noOverlap: true,
                constrain: true,
                matchAnchorWidth: true,
            });
            this.suggestionsOpened = true;
        }
    }
    getUserEmail(user) {
        const { email } = user;
        if (!Array.isArray(email) || !email.length) {
            return undefined;
        }
        const emails = [];
        // we first find a verified email and then we pick any email
        for (const item of email) {
            if (item.verified && item.email) {
                return item.email;
            }
            if (item.email) {
                emails.push(item.email);
            }
        }
        return emails[0];
    }
    handleRoleOption(e) {
        const element = e.detail.item;
        const { role } = element.dataset;
        if (!role) {
            return;
        }
        this.selectedRole = role;
    }
    cancelShareUser() {
        this.selectedUsers = undefined;
        this.lastUserQuery = undefined;
        this.userSuggestions = undefined;
        this.suggestionsOpened = false;
    }
    handleShare() {
        const { selectedRole } = this;
        if (!selectedRole) {
            SnackNotifications.notify('Select a role before sharing', { timeout: 2000 });
            return;
        }
        this.share(selectedRole);
    }
    async share(role) {
        if (!role) {
            throw new Error(`Invalid state. Role is not selected.`);
        }
        const { selectedUsers } = this;
        if (!selectedUsers || !selectedUsers.length) {
            this.cancelShareUser();
            return;
        }
        const { appInfo } = this;
        if (!appInfo) {
            throw new Error(`The appInfo is not set on the <${this.localName}> element.`);
        }
        const id = this.fileId;
        if (!id) {
            throw new Error(`The fileId is not set on the <${this.localName}> element.`);
        }
        const ops = selectedUsers.map(user => {
            const op = {
                op: 'add',
                type: 'user',
                value: role,
                id: user.key,
            };
            return op;
        });
        try {
            await Events.Store.File.patchUsers(id, randomString(), ops, appInfo);
            this.cancelShareUser();
        }
        catch (e) {
            const cause = e;
            CoreEvents.Telemetry.exception(cause.message, false, this);
            throw cause;
        }
    }
    handleRoleChangeOption(e) {
        const list = e.target;
        const { user, current } = list.dataset;
        const element = e.detail.item;
        const role = element.dataset.role;
        if (!role || !user || !current) {
            return;
        }
        if (!this.pendingPermissions) {
            this.pendingPermissions = [];
        }
        const { pendingPermissions = [] } = this;
        const index = pendingPermissions.findIndex(i => i.id === user);
        if (index >= 0) {
            // removes prior selection
            pendingPermissions.splice(index, 1);
        }
        if (role === current) {
            // the user selected the role that was originally assigned to the user.
            // we don't change anything.
            if (!pendingPermissions.length) {
                this.pendingPermissions = undefined;
            }
            this.requestUpdate();
            return;
        }
        let op;
        if (role === 'remove') {
            op = {
                op: 'remove',
                type: 'user',
                id: user,
            };
        }
        else {
            op = {
                op: 'add',
                type: 'user',
                value: role,
                id: user,
            };
        }
        pendingPermissions.push(op);
        this.requestUpdate();
    }
    async handleCommitChanges() {
        const { key, file, pendingPermissions } = this;
        if (!pendingPermissions) {
            return;
        }
        const { appInfo } = this;
        if (!appInfo) {
            throw new Error(`The appInfo is not set on the <${this.localName}> element.`);
        }
        const id = key || file && file.key;
        if (!id) {
            throw new Error(`Either "key" or "file" must be set on the <${this.localName}> element.`);
        }
        try {
            await Events.Store.File.patchUsers(id, randomString(), pendingPermissions, appInfo);
            this.pendingPermissions = undefined;
        }
        catch (e) {
            const cause = e;
            CoreEvents.Telemetry.exception(cause.message, false, this);
            throw cause;
        }
    }
    renderIcon() {
        const classes = {
            icon: true,
            'with-icon': true,
        };
        return html `
    <div class="${classMap(classes)}" part="icon"><ui-icon icon="personAdd"></ui-icon></div>
    `;
    }
    renderTitle() {
        const classes = {
            title: true,
            'with-title': true,
        };
        return html `
    <div class="${classMap(classes)}" part="title"><span>Share with others</span></div>
    `;
    }
    renderBody() {
        return html `
    <div class="content">
      ${this.userInputTemplate()}
      ${this.renderCurrentUsers()}
    </div>
    `;
    }
    renderButtons() {
        const classes = {
            buttons: true,
            'with-buttons': true,
        };
        let content;
        if (this.hasPending) {
            content = this.pendingActionsTemplate();
        }
        else if (this.hasSelectedUsers) {
            content = this.selectedUsersActionsTemplate();
        }
        else {
            content = this.overviewActionsTemplate();
        }
        return html `
    <div class="${classMap(classes)}" part="button">${content}</div>
    `;
    }
    overviewActionsTemplate() {
        return html `
    <ui-button value="confirm" type="text" class="internal-button" @click="${this.handleConfirm}">Done</ui-button>
    `;
    }
    selectedUsersActionsTemplate() {
        return html `
    <ui-button type="text" class="internal-button" @click="${this.cancelShareUser}">Cancel</ui-button>
    <ui-button type="text" class="internal-button" @click="${this.handleShare}">Share</ui-button>
    `;
    }
    pendingActionsTemplate() {
        return html `
    <span class="pending-info">Pending changes</span>
    <ui-button type="text" class="internal-button" @click="${this.handleCommitChanges}">Save</ui-button>
    `;
    }
    userInputTemplate() {
        const classes = {
            'name-input': true,
            autocomplete: this.suggestionsOpened,
        };
        return html `
    <div class="input-line">
      <div class="${classMap(classes)}">
        ${this.renderInput()}
        ${this.renderAutocomplete()}
      </div>
      ${this.roleSelectorTemplate()}
    </div>
    `;
    }
    renderInput() {
        return html `
    <div class="input-wrapper">
      ${this.renderSelectedUserChips()}
      <input 
        type="text"
        id="userInput"
        name="name"
        ?disabled="${this.inputDisabled}"
        @input="${this.handleInput}"
        @keydown="${this.handleKeydown}"
        placeholder="Name or email"
        aria-label="Name or email"
        autocomplete="off"
      />
    </div>
    `;
    }
    renderAutocomplete() {
        const { userSuggestions } = this;
        let opened = this.suggestionsOpened;
        if (opened && (!userSuggestions || !userSuggestions.length)) {
            opened = false;
        }
        const classes = {
            suggestions: true,
            opened,
        };
        return html `
    <div class="${classMap(classes)}" style="${styleMap(this.suggestionStyles || {})}">
      <ui-listbox 
        class="user-autocomplete" 
        role="listbox" 
        aria-label="Suggested users from your organization."
        tabindex="-1"
        @select="${this.handleSuggestionSelect}"
      >
        ${this.renderSuggestionList()}
      </ui-listbox>
    </div>
    `;
    }
    /**
     * @returns The template for the suggestions list.
     */
    renderSuggestionList() {
        const items = this.userSuggestions;
        if (!Array.isArray(items) || !items.length) {
            return '';
        }
        return items.map(i => this.renderSuggestionItem(i));
    }
    /**
     * @returns The template for an URL suggestion item.
     */
    renderSuggestionItem(user) {
        const { name, key } = user;
        const email = this.getUserEmail(user);
        return html `
    <ui-list-item 
      class="suggestion-item" 
      data-key="${key}"
      role="option"
      aria-selected="false"
      lines="two"
      image="avatar"
    >
      <user-avatar slot="start" .user="${user}"></user-avatar>
      <div>${name || key}</div>
      <p slot="supporting-text">${email}</p>
    </ui-list-item>`;
    }
    renderSelectedUserChips() {
        if (!this.hasSelectedUsers) {
            return '';
        }
        return html `
    ${this.selectedUsers.map((user) => this.renderUserChip(user))}
    `;
    }
    renderUserChip(user) {
        const email = this.getUserEmail(user);
        return html `
    <ui-chip type="input" closable  @close="${this.handleUserChipRemove}" data-key="${user.key}" title="${ifDefined(email)}">
      <user-avatar slot="avatar" .user="${user}"></user-avatar>
      ${user.name || email || user.key}
    </ui-chip>
    `;
    }
    roleSelectorTemplate() {
        if (!this.hasSelectedUsers) {
            return '';
        }
        const { selectedRole } = this;
        let label;
        const info = roles[selectedRole];
        if (info) {
            label = info.label;
        }
        else {
            label = 'Role';
        }
        return html `
    <ui-dropdown-list @select="${this.handleRoleOption}" closeOnOutsideClick class="dropdown">
      <ui-button aria-label="Select user role" type="tonal">${label} <ui-icon icon="arrowDropDown" role="presentation"></ui-icon></ui-button>
      <ui-list slot="dropdown" role="menu">
        ${Object.keys(roles).map((key) => html `
        <ui-list-item role="menuitem" data-role="${key}" title="${roles[key].title}">
          ${roles[key].label}
        </ui-list-item>`)}
      </ui-list>
    </ui-dropdown-list>
    `;
    }
    renderCurrentUsers() {
        if (this.hasSelectedUsers) {
            return '';
        }
        const { file, loading, permissionUsers } = this;
        if (!file || !permissionUsers || loading) {
            return '';
        }
        const { permissions = [] } = file;
        return html `
    <div class="current-users">
      ${permissions.map(p => this.renderUserPermission(p))}
    </div>
    `;
    }
    renderUserPermission(permission) {
        const { type, role } = permission;
        if (type === 'group') {
            return nothing;
        }
        if (type === 'anyone') {
            return html `<p>Shared within the organization.</p>`;
        }
        const { permissionUsers = [] } = this;
        const userId = permission.owner;
        const user = permissionUsers.find(u => u.key === userId);
        if (!user) {
            // @TODO: should render something...
            return nothing;
        }
        return html `
    <div class="permission-item">
      <user-avatar class="user-icon" .user="${user}"></user-avatar>
      <div class="permission-user">
        <span class="user-name">${user.name || user.key}</span>
        <span class="user-email">${this.getUserEmail(user)}</span>
      </div>
      <div class="user-role">
        ${this.permissionRoleTemplate(role, userId)}
      </div>
    </div>
    `;
    }
    permissionRoleTemplate(role, userId) {
        const { pendingPermissions } = this;
        const pending = pendingPermissions && pendingPermissions.find(i => i.type === 'user' && i.id === userId);
        const pendingRole = pending && pending.value;
        const selectionRole = pendingRole || role;
        let label;
        const info = roles[selectionRole];
        if (info) {
            label = info.label;
        }
        else {
            label = 'Role';
        }
        return html `
    <ui-dropdown-list 
      @select="${this.handleRoleChangeOption}" 
      closeOnOutsideClick class="dropdown" 
      data-user="${userId}"
      data-current="${role}"
    >
      <ui-button aria-label="Select user role" type="text">${label} <ui-icon icon="arrowDropDown" role="presentation"></ui-icon></ui-button>
      <ui-list slot="dropdown" role="menu">
        ${this.roleItemTemplate('reader', selectionRole)}
        ${this.roleItemTemplate('commenter', selectionRole)}
        ${this.roleItemTemplate('writer', selectionRole)}
        ${this.roleItemTemplate('owner', selectionRole)}
        <ui-divider type="inset"></ui-divider>
        <ui-list-item role="menuitem" data-role="remove" title="Removes the user">
          Remove
        </ui-list-item>
      </ui-list>
    </ui-dropdown-list>
    `;
    }
    roleItemTemplate(role, userRole) {
        return html `
    <ui-list-item role="menuitem" data-role="${role}" title="${roles[role].title}" image="icon">
      ${role === userRole ? html `<ui-icon icon="check" slot="end"></ui-icon>` : ''}
      ${roles[role].label}
    </ui-list-item>
    `;
    }
}
__decorate([
    property({ type: String })
], ShareFile.prototype, "key", null);
__decorate([
    property({ type: Object })
], ShareFile.prototype, "file", null);
__decorate([
    property({ type: Object })
], ShareFile.prototype, "user", void 0);
__decorate([
    property({ type: Object })
], ShareFile.prototype, "appInfo", void 0);
__decorate([
    state()
], ShareFile.prototype, "loadingFile", void 0);
__decorate([
    state()
], ShareFile.prototype, "loadingFileUsers", void 0);
__decorate([
    query('#userInput')
], ShareFile.prototype, "input", void 0);
__decorate([
    query('.suggestions')
], ShareFile.prototype, "suggestionsContainer", void 0);
__decorate([
    query('ui-listbox.user-autocomplete')
], ShareFile.prototype, "suggestionsList", void 0);
__decorate([
    state()
], ShareFile.prototype, "userSuggestions", void 0);
__decorate([
    state()
], ShareFile.prototype, "suggestionsOpened", void 0);
__decorate([
    state()
], ShareFile.prototype, "selectedUsers", void 0);
__decorate([
    state()
], ShareFile.prototype, "permissionUsers", void 0);
__decorate([
    state()
], ShareFile.prototype, "pendingPermissions", void 0);
__decorate([
    state()
], ShareFile.prototype, "selectedRole", void 0);
//# sourceMappingURL=ShareFile.element.js.map