var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { css, html, unsafeCSS } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import manager, { DefaultColor3, DefaultColor4, OPENREMOTE_CLIENT_ID, Util } from "@openremote/core";
import "@openremote/or-components/or-panel";
import "@openremote/or-translate";
import { Page, router } from "@openremote/or-app";
import { i18next } from "@openremote/or-translate";
import { InputType, OrMwcInput } from "@openremote/or-mwc-components/or-mwc-input";
import { OrMwcDialog, showDialog, showOkCancelDialog } from "@openremote/or-mwc-components/or-mwc-dialog";
import { showSnackbar } from "@openremote/or-mwc-components/or-mwc-snackbar";
import { isAxiosError } from "@openremote/rest";
import { getNewUserRoute, getUsersRoute } from "../routes";
import { when } from 'lit/directives/when.js';
import { until } from 'lit/directives/until.js';
import { map } from 'lit/directives/map.js';
const tableStyle = require("@material/data-table/dist/mdc.data-table.css");
export function pageUsersProvider(store) {
    return {
        name: "users",
        routes: [
            "users",
            "users/:id",
            "users/new/:type"
        ],
        pageCreator: () => {
            return new PageUsers(store);
        },
    };
}
let PageUsers = class PageUsers extends Page {
    constructor() {
        super(...arguments);
        this._users = [];
        this._serviceUsers = [];
        this._userFilter = this.getDefaultUserFilter(false);
        this._serviceUserFilter = this.getDefaultUserFilter(true);
        this._passwordPolicy = [];
        this._roles = [];
        this._realmRoles = [];
        this._registrationEmailAsUsername = false;
        this._compositeRoles = [];
    }
    static get styles() {
        // language=CSS
        return [
            unsafeCSS(tableStyle),
            css `
                #wrapper {
                    height: 100%;
                    width: 100%;
                    display: flex;
                    flex-direction: column;
                }

                #title {
                    padding: 0 20px;
                    font-size: 18px;
                    font-weight: bold;
                    width: calc(100% - 40px);
                    max-width: 1360px;
                    margin: 20px auto;
                    align-items: center;
                    display: flex;
                    color: var(--or-app-color3, ${unsafeCSS(DefaultColor3)});
                }

                #title or-icon {
                    margin-right: 10px;
                    margin-left: 14px;
                }

                .panel {
                    flex: 0;
                    width: 100%;
                    box-sizing: border-box;
                    max-width: 1360px;
                    background-color: white;
                    border: 1px solid #e5e5e5;
                    border-radius: 5px;
                    position: relative;
                    margin: 0 auto 16px;
                    padding: 12px 24px 24px;
                    display: flex;
                    flex-direction: column;
                }

                .panel-title {
                    display: flex;
                    text-transform: uppercase;
                    font-weight: bolder;
                    color: var(--or-app-color3, ${unsafeCSS(DefaultColor3)});
                    line-height: 1em;
                    margin-bottom: 10px;
                    margin-top: 0;
                    flex: 0 0 auto;
                    letter-spacing: 0.025em;
                    align-items: center;
                    min-height: 36px;
                }

                or-mwc-input {
                    margin-bottom: 20px;
                }

                or-icon {
                    vertical-align: middle;
                    --or-icon-width: 20px;
                    --or-icon-height: 20px;
                    margin-right: 2px;
                    margin-left: -5px;
                }

                .row {
                    display: flex;
                    flex-direction: row;
                    flex: 1 1 0;
                    gap: 24px;
                }

                .column {
                    display: flex;
                    flex-direction: column;
                    margin: 0px;
                    flex: 1 1 0;
                }

                .hidden {
                    display: none;
                }

                .breadcrumb-container {
                    padding: 0 20px;
                    width: calc(100% - 40px);
                    max-width: 1360px;
                    margin: 12px auto 0;
                    display: flex;
                    align-items: center;
                }

                .breadcrumb-clickable {
                    cursor: pointer;
                    color: var(--or-app-color4, ${unsafeCSS(DefaultColor4)});
                }

                .breadcrumb-arrow {
                    margin: 0 5px -3px 5px;
                    --or-icon-width: 16px;
                    --or-icon-height: 16px;
                }
                
                #session-table {
                    --or-mwc-table-column-width-3: 0;
                }
                
                .button-row {
                    display: flex !important;
                    flex-direction: row !important;
                    margin-bottom: 0;
                    justify-content: space-between;
                }

                @media screen and (max-width: 768px) {
                    #title {
                        padding: 0;
                        width: 100%;
                    }

                    .row {
                        display: block;
                        flex-direction: column;
                        gap: 0;
                    }

                    .panel {
                        border-radius: 0;
                        border-left: 0px;
                        border-right: 0px;
                    }
                }
            `,
        ];
    }
    get name() {
        return "user_plural";
    }
    shouldUpdate(changedProperties) {
        if (changedProperties.has("realm") && changedProperties.get("realm") != undefined) {
            this.reset();
            this.loadData();
        }
        if (changedProperties.has('userId')) {
            this._sessionLoader = undefined; // Reset the MQTT sessions view
            this._updateRoute();
        }
        else if (changedProperties.has('creationState')) {
            this._updateNewUserRoute();
        }
        return super.shouldUpdate(changedProperties);
    }
    connectedCallback() {
        super.connectedCallback();
        this.loadData();
    }
    disconnectedCallback() {
        super.disconnectedCallback();
    }
    responseAndStateOK(stateChecker, response, errorMsg) {
        if (!stateChecker()) {
            return false;
        }
        if (!response.data) {
            showSnackbar(undefined, errorMsg, "dismiss");
            console.error(errorMsg + ": response = " + response.statusText);
            return false;
        }
        return true;
    }
    loadData() {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this._loadDataPromise) {
                this._loadDataPromise = this.fetchUsers();
                this._loadDataPromise.finally(() => {
                    this._loadDataPromise = undefined;
                });
            }
            return this._loadDataPromise;
        });
    }
    fetchUsers() {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.realm || !this.isConnected) {
                return;
            }
            this._compositeRoles = [];
            this._roles = [];
            this._realmRoles = [];
            this._users = [];
            this._serviceUsers = [];
            if (!manager.authenticated || !manager.hasRole("read:users" /* ClientRole.READ_USERS */)) {
                console.warn("Not authenticated or insufficient access");
                return;
            }
            // After async op check that the response still matches current state and that the component is still loaded in the UI
            const stateChecker = () => {
                return this.getState().app.realm === this.realm && this.isConnected;
            };
            const roleResponse = yield manager.rest.api.UserResource.getClientRoles(manager.displayRealm, OPENREMOTE_CLIENT_ID);
            if (!this.responseAndStateOK(stateChecker, roleResponse, "loadFailedRoles")) {
                return;
            }
            const realmResponse = yield manager.rest.api.RealmResource.get(manager.displayRealm);
            if (!this.responseAndStateOK(stateChecker, realmResponse, "loadFailedRoles")) {
                return;
            }
            const usersResponse = yield manager.rest.api.UserResource.query({ realmPredicate: { name: manager.displayRealm } });
            if (!this.responseAndStateOK(stateChecker, usersResponse, "loadFailedUsers")) {
                return;
            }
            this._compositeRoles = roleResponse.data.filter(role => role.composite).sort(Util.sortByString(role => role.name));
            this._roles = roleResponse.data.filter(role => !role.composite).sort(Util.sortByString(role => role.name));
            this._registrationEmailAsUsername = realmResponse.data.registrationEmailAsUsername;
            this._realmRoles = (realmResponse.data.realmRoles || []).map(role => role.name).sort();
            this._passwordPolicy = realmResponse.data.passwordPolicy;
            this._users = usersResponse.data.filter(user => !user.serviceAccount).sort(Util.sortByString(u => u.username));
            this._serviceUsers = usersResponse.data.filter(user => user.serviceAccount).sort(Util.sortByString(u => u.username));
        });
    }
    _createUpdateUser(user, action) {
        return __awaiter(this, void 0, void 0, function* () {
            let result = false;
            if (this._registrationEmailAsUsername && !user.serviceAccount && !user.email) {
                showSnackbar(undefined, "noEmailSet", "dismiss");
                return false;
            }
            else if ((!this._registrationEmailAsUsername || user.serviceAccount) && !user.username) {
                showSnackbar(undefined, "noUsernameSet", "dismiss");
                return false;
            }
            // New service users with the 'gateway-' prefix are not allowed
            if (user.serviceAccount && user.username.startsWith('gateway-')) {
                showSnackbar(undefined, "noGatewayUsername", "dismiss");
                return false;
            }
            if (user.password === "") {
                // Means a validation failure shouldn't get here
                return false;
            }
            const isUpdate = !!user.id;
            try {
                const response = action === 'update'
                    ? yield manager.rest.api.UserResource.update(manager.displayRealm, user)
                    : yield manager.rest.api.UserResource.create(manager.displayRealm, user);
                // Ensure user ID is set
                user.id = response.data.id;
                yield this._updateRoles(user, false);
                yield this._updateRoles(user, true);
                yield this._updateUserAssetLinks(user);
                result = true;
            }
            catch (e) {
                if (isAxiosError(e)) {
                    console.error((isUpdate ? "save user failed" : "create user failed") + ": response = " + e.response.statusText);
                    if (e.response.status === 400) {
                        showSnackbar(undefined, (isUpdate ? "saveUserFailed" : "createUserFailed"), "dismiss");
                    }
                    else if (e.response.status === 403) {
                        showSnackbar(undefined, "userAlreadyExists");
                    }
                }
                result = false;
                throw e; // Throw exception anyhow to handle individual cases
            }
            finally {
                yield this.loadData();
                // After updating the user, reset the password if it has changed.
                // This is handled asynchronously, so it will not 'wait' before the request has succeeded.
                if (user.password) {
                    const credentials = { value: user.password };
                    manager.rest.api.UserResource.updatePassword(manager.displayRealm, user.id, credentials).catch(e => {
                        if (isAxiosError(e) && e.response.status !== 404) {
                            showSnackbar(undefined, "savePasswordFailed");
                        }
                    });
                }
                return result;
            }
        });
    }
    /**
     * Backend only uses name of role not the ID so although service client roles are not the same as composite roles
     * the names will match so that's ok
     */
    _updateRoles(user, realmRoles) {
        return __awaiter(this, void 0, void 0, function* () {
            if (realmRoles) {
                if (!Util.objectsEqual(user.realmRoles, user.previousRealmRoles)) {
                    yield manager.rest.api.UserResource.updateUserRealmRoles(manager.displayRealm, user.id, user.realmRoles);
                }
            }
            else {
                const roles = user.roles;
                const previousRoles = user.previousRoles;
                const removedRoles = previousRoles.filter(previousRole => !roles.some(role => role === previousRole));
                const addedRoles = roles.filter(role => !previousRoles.some(previousRole => previousRole === role));
                if (removedRoles.length === 0 && addedRoles.length === 0) {
                    return;
                }
                yield manager.rest.api.UserResource.updateUserClientRoles(manager.displayRealm, user.id, OPENREMOTE_CLIENT_ID, roles);
            }
        });
    }
    _updateUserAssetLinks(user) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!user.previousAssetLinks) {
                return;
            }
            const removedLinks = user.previousAssetLinks.filter(assetLink => !user.userAssetLinks.some(newLink => assetLink.id.assetId === newLink.id.assetId));
            const addedLinks = user.userAssetLinks.filter(assetLink => !user.previousAssetLinks.some(oldLink => assetLink.id.assetId === oldLink.id.assetId)).map(link => {
                // Ensure user ID is added as new users wouldn't have had an ID at the time the links were created in the UI
                link.id.userId = user.id;
                return link;
            });
            if (removedLinks.length > 0) {
                yield manager.rest.api.AssetResource.deleteUserAssetLinks(removedLinks);
            }
            if (addedLinks.length > 0) {
                yield manager.rest.api.AssetResource.createUserAssetLinks(addedLinks);
            }
        });
    }
    _deleteUser(user) {
        showOkCancelDialog(i18next.t("deleteUser"), i18next.t("deleteUserConfirm", { userName: user.username }), i18next.t("delete"))
            .then((ok) => {
            if (ok) {
                this.doDelete(user);
            }
        });
    }
    doDelete(user) {
        manager.rest.api.UserResource.delete(manager.displayRealm, user.id).then(response => {
            if (user.serviceAccount) {
                this._serviceUsers = [...this._serviceUsers.filter(u => u.id !== user.id)];
                this.reset();
            }
            else {
                this._users = [...this._users.filter(u => u.id !== user.id)];
                this.reset();
            }
        });
    }
    render() {
        var _a;
        if (!manager.authenticated) {
            return html `
                <or-translate value="notAuthenticated"></or-translate>
            `;
        }
        // Apply filter (such as search input)
        const users = this._userFilter(this._users);
        const serviceUsers = this._serviceUserFilter(this._serviceUsers);
        const compositeRoleOptions = this._compositeRoles.map(cr => cr.name);
        const realmRoleOptions = this._realmRoles.map(r => [r, i18next.t("realmRole." + r, Util.camelCaseToSentenceCase(r.replace("_", " ").replace("-", " ")))]);
        const readonly = !manager.hasRole("write:admin" /* ClientRole.WRITE_ADMIN */);
        // Content of User Table
        const userTableColumns = [
            { title: i18next.t('username') },
            { title: i18next.t('firstName') },
            { title: i18next.t('surname') },
            { title: i18next.t('email'), hideMobile: true },
            { title: i18next.t('status') }
        ];
        const userTableRows = users.map((user) => {
            return {
                content: [user.username, user.firstName, user.lastName, user.email, user.enabled ? i18next.t('enabled') : i18next.t('disabled')],
                clickable: true
            };
        });
        // Content of Service user Table
        const serviceUserTableColumns = [
            { title: i18next.t('username') },
            { title: i18next.t('status') }
        ];
        const serviceUserTableRows = serviceUsers.map((user) => {
            return {
                content: [user.username, user.enabled ? i18next.t('enabled') : i18next.t('disabled')],
                clickable: true
            };
        });
        // Configuration
        const tableConfig = {
            columnFilter: [],
            stickyFirstColumn: false,
            pagination: {
                enable: true
            }
        };
        const mergedUserList = [...users, ...serviceUsers];
        const index = (this.userId ? mergedUserList.findIndex((user) => user.id === this.userId) : undefined);
        return html `
            <div id="wrapper">

                <!-- Breadcrumb on top of the page-->
                ${when((this.userId && index !== undefined) || this.creationState, () => {
            var _a;
            return html `
                    <div class="breadcrumb-container">
                        <span class="breadcrumb-clickable"
                              @click="${() => this.reset()}">${i18next.t("user_plural")}</span>
                        <or-icon class="breadcrumb-arrow" icon="chevron-right"></or-icon>
                        <span style="margin-left: 2px;">${index !== undefined ? (_a = mergedUserList[index]) === null || _a === void 0 ? void 0 : _a.username : (this.creationState.userModel.serviceAccount ? i18next.t('creating_serviceUser') : i18next.t('creating_regularUser'))}</span>
                    </div>
                `;
        })}

                <div id="title">
                    <or-icon icon="account-group"></or-icon>
                    <span>${this.userId && index !== undefined ? (_a = mergedUserList[index]) === null || _a === void 0 ? void 0 : _a.username : i18next.t('user_plural')}</span>
                </div>

                <!-- User Specific page -->
                ${when((this.userId && index !== undefined) || this.creationState, () => html `
                    ${when(mergedUserList[index] !== undefined || this.creationState, () => {
            const user = (index !== undefined ? mergedUserList[index] : this.creationState.userModel);
            const showMqttSessions = user.serviceAccount && this.userId;
            return html `
                            <div id="content" class="panel">
                                <p class="panel-title">
                                    ${user.serviceAccount ? i18next.t('serviceUser') : i18next.t('user')}
                                    ${i18next.t('settings')}</p>
                                ${this.getSingleUserView(user, compositeRoleOptions, realmRoleOptions, ("user" + index), (readonly || this._saveUserPromise != undefined))}
                            </div>
                            
                            ${when(showMqttSessions, () => this.getMQTTSessionTemplate(user))}
                        `;
        })}

                    <!-- List of Users page -->
                `, () => html `
                    <div id="content" class="panel">
                        <div class="panel-title" style="justify-content: space-between;">
                            <p>${i18next.t("regularUser_plural")}</p>
                            <div style="display: flex; align-items: center; gap: 12px;">
                                <or-mwc-input type="${InputType.TEXT}" placeholder="${i18next.t('search')}" 
                                              style="margin: 0; text-transform: none;" iconTrailing="magnify" compact outlined
                                              @input="${(ev) => this.onRegularUserSearch(ev)}"
                                ></or-mwc-input>
                                <or-mwc-input style="margin: 0;" type="${InputType.BUTTON}" icon="plus" label="${i18next.t('add')} ${i18next.t("user")}"
                                              @or-mwc-input-changed="${() => this.creationState = { userModel: this.getNewUserModel(false) }}"
                                ></or-mwc-input>
                            </div>
                        </div>
                        ${until(this.getUsersTable(userTableColumns, userTableRows, tableConfig, (ev) => {
            this.userId = users[ev.detail.index].id;
        }), html `${i18next.t('loading')}`)}
                    </div>

                    <div id="content" class="panel">
                        <div class="panel-title" style="justify-content: space-between;">
                            <p>${i18next.t("serviceUser_plural")}</p>
                            <div style="display: flex; align-items: center; gap: 12px;">
                                <or-mwc-input style="margin: 0; text-transform: none;" type="${InputType.TEXT}" iconTrailing="magnify" placeholder="${i18next.t('search')}" compact outlined
                                              @input="${(ev) => this.onServiceUserSearch(ev)}"
                                ></or-mwc-input>
                                <or-mwc-input style="margin: 0;" type="${InputType.BUTTON}" icon="plus" label="${i18next.t('add')} ${i18next.t("user")}"
                                              @or-mwc-input-changed="${() => this.creationState = { userModel: this.getNewUserModel(true) }}"
                                ></or-mwc-input>
                            </div>
                        </div>
                        ${until(this.getUsersTable(serviceUserTableColumns, serviceUserTableRows, tableConfig, (ev) => {
            this.userId = serviceUsers[ev.detail.index].id;
        }), html `${i18next.t('loading')}`)}
                    </div>
                `)}
            </div>
        `;
    }
    getUsersTable(columns, rows, config, onRowClick) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this._loadDataPromise) {
                yield this._loadDataPromise;
            }
            return html `
            <or-mwc-table .columns="${columns instanceof Array ? columns : undefined}"
                          .columnsTemplate="${!(columns instanceof Array) ? columns : undefined}"
                          .rows="${rows instanceof Array ? rows : undefined}"
                          .rowsTemplate="${!(rows instanceof Array) ? rows : undefined}"
                          .config="${config}"
                          @or-mwc-table-row-click="${rows instanceof Array ? onRowClick : undefined}"
            ></or-mwc-table>
        `;
        });
    }
    getNewUserModel(serviceAccount) {
        return {
            enabled: true,
            password: undefined,
            realm: manager.displayRealm,
            roles: [],
            previousRoles: [],
            realmRoles: [],
            previousRealmRoles: [],
            userAssetLinks: [],
            serviceAccount: serviceAccount
        };
    }
    getDefaultUserFilter(serviceUser) {
        return (users) => users;
    }
    onRegularUserSearch(ev) {
        var _a;
        const value = (_a = ev.target.nativeValue) === null || _a === void 0 ? void 0 : _a.toLowerCase();
        if (!value) {
            this._userFilter = this.getDefaultUserFilter(false);
        }
        else {
            this._userFilter = (users) => {
                return users.filter(u => {
                    var _a, _b, _c, _d;
                    return ((_a = u.username) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(value)) ||
                        ((_b = u.firstName) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes(value)) ||
                        ((_c = u.lastName) === null || _c === void 0 ? void 0 : _c.toLowerCase().includes(value)) ||
                        ((_d = u.email) === null || _d === void 0 ? void 0 : _d.toLowerCase().includes(value));
                });
            };
        }
    }
    onServiceUserSearch(ev) {
        var _a;
        const value = (_a = ev.target.nativeValue) === null || _a === void 0 ? void 0 : _a.toLowerCase();
        if (!value) {
            this._serviceUserFilter = this.getDefaultUserFilter(true);
        }
        else {
            this._serviceUserFilter = (users) => {
                return users.filter(u => { var _a; return (_a = u.username) === null || _a === void 0 ? void 0 : _a.includes(value); });
            };
        }
    }
    loadUser(user) {
        return __awaiter(this, void 0, void 0, function* () {
            if (user.roles || user.realmRoles) {
                return;
            }
            // Load users assigned roles
            const userRolesResponse = yield (manager.rest.api.UserResource.getUserClientRoles(manager.displayRealm, user.id, OPENREMOTE_CLIENT_ID));
            if (!this.responseAndStateOK(() => true, userRolesResponse, "loadFailedUserInfo")) {
                return;
            }
            const userRealmRolesResponse = yield manager.rest.api.UserResource.getUserRealmRoles(manager.displayRealm, user.id);
            if (!this.responseAndStateOK(() => true, userRolesResponse, "loadFailedUserInfo")) {
                return;
            }
            const userAssetLinksResponse = yield manager.rest.api.AssetResource.getUserAssetLinks({
                realm: manager.displayRealm,
                userId: user.id
            });
            if (!this.responseAndStateOK(() => true, userAssetLinksResponse, "loadFailedUserInfo")) {
                return;
            }
            user.roles = userRolesResponse.data;
            user.realmRoles = userRealmRolesResponse.data;
            user.previousRealmRoles = [...user.realmRoles];
            user.previousRoles = [...user.roles];
            user.userAssetLinks = userAssetLinksResponse.data;
            user.loaded = true;
            user.loading = false;
            // Update the dom
            this.requestUpdate();
        });
    }
    _openAssetSelector(ev, user, readonly, suffix) {
        const openBtn = ev.target;
        openBtn.disabled = true;
        user.previousAssetLinks = [...user.userAssetLinks];
        const onAssetSelectionChanged = (e) => {
            user.userAssetLinks = e.detail.newNodes.map(node => {
                const userAssetLink = {
                    id: {
                        userId: user.id,
                        realm: user.realm,
                        assetId: node.asset.id
                    }
                };
                return userAssetLink;
            });
        };
        const dialog = showDialog(new OrMwcDialog()
            .setHeading(i18next.t("linkedAssets"))
            .setContent(html `
                <or-asset-tree
                        id="chart-asset-tree" readonly .selectedIds="${user.userAssetLinks.map(ual => ual.id.assetId)}"
                        .showSortBtn="${false}" expandNodes checkboxes
                        @or-asset-tree-request-selection="${(e) => {
            if (readonly) {
                e.detail.allow = false;
            }
        }}"
                        @or-asset-tree-selection="${(e) => {
            if (!readonly) {
                onAssetSelectionChanged(e);
            }
        }}"></or-asset-tree>
            `)
            .setActions([
            {
                default: true,
                actionName: "cancel",
                content: "cancel",
                action: () => {
                    user.userAssetLinks = user.previousAssetLinks;
                    user.previousAssetLinks = undefined;
                    openBtn.disabled = false;
                }
            },
            {
                actionName: "ok",
                content: "ok",
                action: () => {
                    openBtn.disabled = false;
                    this.onUserChanged(suffix);
                    this.requestUpdate();
                }
            }
        ])
            .setDismissAction({
            actionName: "cancel",
            action: () => {
                user.userAssetLinks = user.previousAssetLinks;
                user.previousAssetLinks = undefined;
                openBtn.disabled = false;
            }
        }));
    }
    onUserChanged(suffix) {
        // Don't have form-associated custom element support in lit at time of writing which would be the way to go here
        const validateArray = this.shadowRoot.querySelectorAll(".validate");
        const saveBtn = this.shadowRoot.getElementById("savebtn-" + suffix);
        const saveDisabled = Array.from(validateArray).filter(e => e instanceof OrMwcInput).some(input => !input.valid);
        saveBtn.disabled = saveDisabled;
    }
    _onPasswordChanged(user, suffix) {
        const passwordComponent = this.shadowRoot.getElementById("new-password-" + suffix);
        const repeatPasswordComponent = this.shadowRoot.getElementById("new-repeatPassword-" + suffix);
        if (repeatPasswordComponent.value !== passwordComponent.value) {
            const error = i18next.t("passwordMismatch");
            repeatPasswordComponent.setCustomValidity(error);
            user.password = "";
        }
        else {
            repeatPasswordComponent.setCustomValidity(undefined);
            user.password = passwordComponent.value;
        }
    }
    _regenerateSecret(ev, user, secretInputId) {
        return __awaiter(this, void 0, void 0, function* () {
            const btnElem = ev.currentTarget;
            const secretElem = this.shadowRoot.getElementById(secretInputId);
            if (!btnElem || !secretElem) {
                showSnackbar(undefined, "errorOccurred");
                return;
            }
            btnElem.disabled = true;
            secretElem.disabled = true;
            const resetResponse = yield manager.rest.api.UserResource.resetSecret(manager.displayRealm, user.id);
            if (resetResponse.data) {
                secretElem.value = resetResponse.data;
            }
            btnElem.disabled = false;
            secretElem.disabled = false;
        });
    }
    _updateUserSelectedRoles(user, suffix) {
        const roleCheckboxes = [...this.shadowRoot.getElementById("role-list-" + suffix).children];
        const implicitRoleNames = this.getImplicitUserRoles(user);
        roleCheckboxes.forEach((checkbox) => {
            const roleName = checkbox.label;
            const r = this._roles.find(role => roleName === role.name);
            checkbox.disabled = !!implicitRoleNames.find(name => r.name === name);
            checkbox.value = !!user.roles.find(userRole => userRole === r.name) || implicitRoleNames.some(implicitRoleName => implicitRoleName === r.name);
        });
    }
    getImplicitUserRoles(user) {
        return this._compositeRoles.filter((role) => user.roles.some(ur => ur === role.name)).flatMap((role) => role.compositeRoleIds).map(id => this._roles.find(r => r.id === id).name);
    }
    getSingleUserView(user, compositeRoleOptions, realmRoleOptions, suffix, readonly = true) {
        return html `
            ${when((user.loaded || (user.roles && user.realmRoles)), () => {
            return this.getSingleUserTemplate(user, compositeRoleOptions, realmRoleOptions, suffix, readonly);
        }, () => {
            const getTemplate = () => __awaiter(this, void 0, void 0, function* () {
                yield this.loadUser(user);
                return this.getSingleUserTemplate(user, compositeRoleOptions, realmRoleOptions, suffix, readonly);
            });
            const content = getTemplate();
            return html `
                    ${until(content, html `${i18next.t('loading')}`)}
                `;
        })}
        `;
    }
    getMQTTSessionTemplate(user) {
        if (!this._sessionLoader) {
            this._sessionLoader = this.getSessionLoader(user);
        }
        return html `
            <div id="content" class="panel">
                <p class="panel-title">${i18next.t('mqttSessions')}</p>
                ${until(this._sessionLoader, html `${i18next.t('loading')}`)}
            </div>
        `;
    }
    getSessionLoader(user) {
        return __awaiter(this, void 0, void 0, function* () {
            const userSessionsResponse = yield (manager.rest.api.UserResource.getUserSessions(manager.displayRealm, user.id));
            if (!this.responseAndStateOK(() => userSessionsResponse.status === 200, userSessionsResponse, "loadFailedUserInfo")) {
                return html ``;
            }
            const cols = [i18next.t("address"), i18next.t("since"), ""];
            const rows = userSessionsResponse.data.map((session) => {
                return [session.remoteAddress, new Date(session.startTimeMillis), html `<or-mwc-input .type="${InputType.BUTTON}" label="disconnect" @or-mwc-input-changed="${() => { this.disconnectSession(user, session); }}"></or-mwc-input>`];
            });
            if (rows.length < 1) {
                return html `<or-mwc-table .rows="${[['This user has no active MQTT sessions', null]]}" .config="${{ stickyFirstColumn: false }}" .columns="${cols}"></or-mwc-table>`;
            }
            return html `<or-mwc-table id="session-table" .rows="${rows}" .config="${{ stickyFirstColumn: false }}" .columns="${cols}"></or-mwc-table>`;
        });
    }
    getSingleUserTemplate(user, compositeRoleOptions, realmRoleOptions, suffix, readonly = true) {
        var _a;
        const isServiceUser = user.serviceAccount;
        const isSameUser = user.username === manager.username;
        const isGatewayServiceUser = isServiceUser && ((_a = user.username) === null || _a === void 0 ? void 0 : _a.startsWith("gateway-"));
        const implicitRoleNames = user.loaded ? this.getImplicitUserRoles(user) : [];
        return html `
            <div class="row">
                <div class="column">
                    <h5>${i18next.t("details")}</h5>
                    <!-- user details -->
                    <or-mwc-input id="new-username-${suffix}" ?readonly="${!!user.id || readonly}" .disabled="${!!user.id || (!isServiceUser && this._registrationEmailAsUsername)}"
                                  class = "validate"
                                  .label="${i18next.t("username")}"
                                  .type="${InputType.TEXT}" minLength="3" maxLength="255" 
                                  ?required="${isServiceUser || !this._registrationEmailAsUsername}"
                                  pattern="[A-Za-z0-9_+@\\.\\-ßçʊÇʊ]+"
                                  .value="${user.username}" autocomplete="false"
                                  .validationMessage="${i18next.t("invalidUsername")}"
                                  @or-mwc-input-changed="${(e) => {
            user.username = e.detail.value;
            this.onUserChanged(suffix);
        }}"></or-mwc-input>
                    <!-- if identity provider is set to use email as username, make it required -->
                    <or-mwc-input id="new-email" ?readonly="${(!!user.id && this._registrationEmailAsUsername) || readonly}"
                                  .disabled="${!!user.id && this._registrationEmailAsUsername}"
                                  class="${isServiceUser ? "hidden" : "validate"}"
                                  .label="${i18next.t("email")}"
                                  .type="${InputType.EMAIL}"
                                  .value="${user.email}" autocomplete="false"
                                  ?required="${!isServiceUser && this._registrationEmailAsUsername}"
                                  pattern="^[\\w\\.\\-\\+\\%]+@([\\w\\-]+\\.)+[\\w]{2,}$"
                                  .validationMessage="${i18next.t("invalidEmail")}"
                                  @or-mwc-input-changed="${(e) => {
            if (this._registrationEmailAsUsername) {
                user.username = e.detail.value;
            }
            user.email = e.detail.value;
            this.onUserChanged(suffix);
            this.requestUpdate(); // in case of username update, we trigger a state change
        }}"></or-mwc-input>
                    <or-mwc-input id="new-firstName" ?readonly="${readonly}"
                                  class="${isServiceUser ? "hidden" : "validate"}"
                                  .label="${i18next.t("firstName")}"
                                  .type="${InputType.TEXT}" minLength="1"
                                  .value="${user.firstName}" autocomplete="false"
                                  @or-mwc-input-changed="${(e) => {
            user.firstName = e.detail.value;
            this.onUserChanged(suffix);
        }}"></or-mwc-input>
                    <or-mwc-input id="new-surname" ?readonly="${readonly}"
                                  class="${isServiceUser ? "hidden" : "validate"}"
                                  .label="${i18next.t("surname")}"
                                  .type="${InputType.TEXT}" minLength="1"
                                  .value="${user.lastName}" autocomplete="false"
                                  @or-mwc-input-changed="${(e) => {
            user.lastName = e.detail.value;
            this.onUserChanged(suffix);
        }}"></or-mwc-input>

                    <!-- password -->
                    <h5>${i18next.t("password")}</h5>
                    ${isServiceUser ? html `
                        ${when(user.secret, () => html `
                            <or-mwc-input id="new-password-${suffix}" readonly
                                          class = "validate"
                                          .label="${i18next.t("secret")}"
                                          .value="${user.secret}"
                                          .type="${InputType.TEXT}"></or-mwc-input>
                            <or-mwc-input ?readonly="${!user.id || readonly}"
                                          ?disabled="${isGatewayServiceUser}"
                                          .label="${i18next.t("regenerateSecret")}"
                                          .type="${InputType.BUTTON}"
                                          @or-mwc-input-changed="${(ev) => {
            this._regenerateSecret(ev, user, "new-password-" + suffix).catch(() => showSnackbar(undefined, 'errorOccurred'));
            this.onUserChanged(suffix);
        }}"></or-mwc-input>
                        `, () => html `
                            <span>${i18next.t("generateSecretInfo")}</span>
                        `)}
                    ` : html `
                        <or-mwc-input id="new-password-${suffix}"
                                      ?readonly="${readonly}"
                                      class = "validate"
                                      .label="${i18next.t("password")}"
                                      .type="${InputType.PASSWORD}" min="1" autocomplete="false"
                                      @or-mwc-input-changed="${(e) => {
            this._onPasswordChanged(user, suffix);
            this.onUserChanged(suffix);
        }}"
                        ></or-mwc-input>
                        <or-mwc-input id="new-repeatPassword-${suffix}"
                                      helperPersistent ?readonly="${readonly}"
                                      .label="${i18next.t("repeatPassword")}"
                                      .type="${InputType.PASSWORD}" min="1" autocomplete="false"
                                      style="${this._passwordPolicy ? 'margin-bottom: 0' : undefined}"
                                      @or-mwc-input-changed="${(e) => {
            this._onPasswordChanged(user, suffix);
            this.onUserChanged(suffix);
        }}"
                        ></or-mwc-input>
                        ${when(this._passwordPolicy, () => until(this._getPasswordPolicyTemplate(user, this._passwordPolicy)))}
                    `}
                </div>

                <div class="column">
                    <h5>${i18next.t("settings")}</h5>
                    <!-- enabled -->
                    <or-mwc-input ?readonly="${readonly}"
                                  class="validate"
                                  .label="${i18next.t("active")}"
                                  .type="${InputType.CHECKBOX}"
                                  .value="${user.enabled}"
                                  @or-mwc-input-changed="${(e) => {
            user.enabled = e.detail.value;
            this.onUserChanged(suffix);
        }}"
                                  style="height: 56px;"
                    ></or-mwc-input>

                    <!-- realm roles -->
                    <or-mwc-input
                            ?readonly="${readonly}"
                            ?disabled="${isSameUser || isGatewayServiceUser}"
                            class = "validate"
                            .value="${user.realmRoles}"
                            .type="${InputType.SELECT}" multiple
                            .options="${realmRoleOptions}"
                            .label="${i18next.t("realm_role_plural")}"
                            @or-mwc-input-changed="${(e) => {
            this.onUserChanged(suffix);
            user.realmRoles = e.detail.value;
        }}"></or-mwc-input>

                    <!-- composite client roles -->
                    <or-mwc-input
                            ?readonly="${readonly}"
                            ?disabled="${isSameUser || isGatewayServiceUser}"
                            class = "validate"
                            .value="${user.roles && user.roles.length > 0 ? user.roles.filter(r => this._compositeRoles.some(cr => cr.name === r)) : undefined}"
                            .type="${InputType.SELECT}" multiple
                            .options="${compositeRoleOptions}"
                            .label="${i18next.t("manager_role_plural")}"
                            @or-mwc-input-changed="${(e) => {
            user.roles = e.detail.value;
            this._updateUserSelectedRoles(user, suffix);
            this.onUserChanged(suffix);
        }}"></or-mwc-input>

                    <!-- roles -->
                    <div style="display:flex;flex-wrap:wrap;margin-bottom: 20px;"
                         id="role-list-${suffix}">
                        ${this._roles.map(r => {
            return html `
                                <or-mwc-input
                                        ?readonly="${readonly}"
                                        ?disabled="${implicitRoleNames.find(name => r.name === name) || isGatewayServiceUser}"
                                        class = "validate"
                                        .value="${!!user.roles.find(userRole => userRole === r.name) || implicitRoleNames.some(implicitRoleName => implicitRoleName === r.name)}"
                                        .type="${InputType.CHECKBOX}"
                                        .label="${r.name}"
                                        title="${r.description}"
                                        style="flex: 0 1 160px; margin: 0; overflow: hidden;"
                                        @or-mwc-input-changed="${(e) => {
                if (!!e.detail.value) {
                    user.roles.push(r.name);
                }
                else {
                    user.roles = user.roles.filter(ur => ur !== r.name);
                }
                this.onUserChanged(suffix);
            }}"></or-mwc-input>
                            `;
        })}
                    </div>

                    <!-- Asset-User links -->
                    <div>
                        <span>${i18next.t("linkedAssets")}:</span>
                        <or-mwc-input outlined ?disabled="${readonly || isGatewayServiceUser}" style="margin-left: 4px;"
                                      .type="${InputType.BUTTON}"
                                      .label="${i18next.t("selectRestrictedAssets", { number: user.userAssetLinks.length })}"
                                      @or-mwc-input-changed="${(ev) => this._openAssetSelector(ev, user, readonly, suffix)}"></or-mwc-input>
                    </div>
                </div>
            </div>
            <!-- Bottom controls (save/update and delete button) -->
            ${when(!(readonly && !this._saveUserPromise), () => html `
                <div class="row button-row">

                    ${when((!isSameUser && !isGatewayServiceUser && user.id), () => html `
                        <or-mwc-input style="margin: 0;" outlined ?disabled="${readonly}"
                                      .label="${i18next.t("delete")}"
                                      .type="${InputType.BUTTON}"
                                      @or-mwc-input-changed="${() => this._deleteUser(user)}"
                        ></or-mwc-input>
                    `)}
                    <div style="display: flex; align-items: center; gap: 16px; margin: 0 0 0 auto;">
                        <!-- Button disabled until an input has input, and by that a valid check is done -->
                        <or-mwc-input id="savebtn-${suffix}" style="margin: 0;" raised disabled
                                      .label="${i18next.t(user.id ? "save" : "create")}"
                                      .type="${InputType.BUTTON}"
                                      @or-mwc-input-changed="${(e) => {
            let error;
            this._saveUserPromise = this._createUpdateUser(user, user.id ? 'update' : 'create').then((result) => {
                // Return to the users page on successful user create/update
                if (result) {
                    showSnackbar(undefined, "saveUserSucceeded");
                    this.reset();
                }
            }).catch((ex) => {
                console.error(ex);
                if (isAxiosError(ex)) {
                    error = {
                        status: ex.response.status,
                        text: (ex.response.status == 403 ? i18next.t('userAlreadyExists') : i18next.t('errorOccurred'))
                    };
                }
            }).finally(() => {
                this._saveUserPromise = undefined;
                if (error) {
                    this.updateComplete.then(() => {
                        showSnackbar(undefined, error.text);
                        if (error.status === 403) {
                            const elem = this.shadowRoot.getElementById('username-' + suffix);
                            elem.setCustomValidity(error.text);
                            elem.shadowRoot.getElementById("elem").reportValidity(); // manually reporting was required since we're not editing the username at all.
                            this.onUserChanged(suffix); // onUserChanged to trigger validation of all fields again.
                        }
                    });
                }
            });
        }}">
                        </or-mwc-input>
                    </div>
                </div>
            `)}
        `;
    }
    _getPasswordPolicyTemplate(user_1) {
        return __awaiter(this, arguments, void 0, function* (user, passwordPolicy = this._passwordPolicy) {
            const policyMap = new Map(passwordPolicy.map(policyStr => {
                const name = policyStr.split("(")[0];
                const value = policyStr.split("(")[1].split(")")[0];
                return [name, value];
            }));
            const policies = Array.from(policyMap.keys());
            const policyTexts = [];
            // Minimum / maximum length warning
            if (policies.includes("length") && policies.includes("maxLength")) {
                policyTexts.push(html `<or-translate value="password-policy-invalid-length" .options="${{ 0: policyMap.get("length"), 1: policyMap.get("maxLength") }}"></or-translate>`);
            }
            else if (policies.includes("length")) {
                policyTexts.push(html `<or-translate value="password-policy-invalid-length-too-short" .options="${{ 0: policyMap.get("length") }}"></or-translate>`);
            }
            else if (policies.includes("maxLength")) {
                policyTexts.push(html `<or-translate value="password-policy-invalid-length-too-long" .options="${{ 0: policyMap.get("maxLength") }}"></or-translate>`);
            }
            // Special characters
            if (policies.includes("specialChars")) {
                const value = policyMap.get("specialChars");
                const translation = value == "1" ? "password-policy-special-chars-single" : "password-policy-special-chars";
                policyTexts.push(html `<or-translate value="${translation}" .options="${{ 0: value }}"></or-translate>`);
            }
            // Digits/numbers
            if (policies.includes("digits")) {
                const value = policyMap.get("digits");
                const translation = value == "1" ? "password-policy-digits-single" : "password-policy-digits";
                policyTexts.push(html `<or-translate value="${translation}" .options="${{ 0: value }}"></or-translate>`);
            }
            // Uppercase / lowercase letters
            if (policies.includes("upperCase")) {
                const value = policyMap.get("upperCase");
                const translation = value == "1" ? "password-policy-uppercase-single" : "password-policy-uppercase";
                policyTexts.push(html `<or-translate value="${translation}" .options="${{ 0: value }}"></or-translate>`);
            }
            // Warn for recently used passwords
            if (policies.includes("passwordHistory")) {
                policyTexts.push(html `<or-translate value="password-policy-recently-used"></or-translate>`);
            }
            // Cannot be username and/or email
            if (policies.includes("notUsername") && policies.includes("notEmail")) {
                policyTexts.push(html `<or-translate value="password-policy-not-email-username"></or-translate>`);
            }
            else if (policies.includes("notUsername")) {
                policyTexts.push(html `<or-translate value="password-policy-not-username"></or-translate>`);
            }
            else if (policies.includes("notEmail")) {
                policyTexts.push(html `<or-translate value="password-policy-not-email"></or-translate>`);
            }
            return html `
            <ul>
                ${map(policyTexts, text => html `<li>${text}</li>`)}
            </ul>
        `;
        });
    }
    // Reset selected user or creation page, and go back to the list
    reset() {
        this.userId = undefined;
        this.creationState = undefined;
        this._userFilter = this.getDefaultUserFilter(false);
        this._serviceUserFilter = this.getDefaultUserFilter(true);
    }
    stateChanged(state) {
        var _a;
        if (state.app.page == 'users') { // it seems that the check is necessary here
            this.realm = state.app.realm;
            this.userId = (state.app.params && state.app.params.id) ? state.app.params.id : undefined;
            this.creationState = (((_a = state.app.params) === null || _a === void 0 ? void 0 : _a.type) ? { userModel: this.getNewUserModel(state.app.params.type == 'serviceuser') } : undefined);
        }
    }
    _updateRoute(silent = false) {
        router.navigate(getUsersRoute(this.userId), {
            callHooks: !silent,
            callHandler: !silent
        });
    }
    _updateNewUserRoute(silent = false) {
        var _a;
        router.navigate(getNewUserRoute((_a = this.creationState) === null || _a === void 0 ? void 0 : _a.userModel.serviceAccount), {
            callHooks: !silent,
            callHandler: !silent
        });
    }
    disconnectSession(user, session) {
        this._sessionLoader = manager.rest.api.UserResource.disconnectUserSession(manager.displayRealm, session.ID)
            .then(() => showSnackbar(undefined, "userDisconnected"))
            .catch((e) => {
            showSnackbar(undefined, "userDisconnectFailed");
            console.error("Failed to disconnect user", e);
        })
            .then(() => this.getSessionLoader(user));
    }
};
__decorate([
    property()
], PageUsers.prototype, "realm", void 0);
__decorate([
    property()
], PageUsers.prototype, "userId", void 0);
__decorate([
    property()
], PageUsers.prototype, "creationState", void 0);
__decorate([
    state()
], PageUsers.prototype, "_users", void 0);
__decorate([
    state()
], PageUsers.prototype, "_serviceUsers", void 0);
__decorate([
    state()
], PageUsers.prototype, "_userFilter", void 0);
__decorate([
    state()
], PageUsers.prototype, "_serviceUserFilter", void 0);
__decorate([
    state()
], PageUsers.prototype, "_passwordPolicy", void 0);
__decorate([
    state()
], PageUsers.prototype, "_roles", void 0);
__decorate([
    state()
], PageUsers.prototype, "_realmRoles", void 0);
__decorate([
    state()
], PageUsers.prototype, "_registrationEmailAsUsername", void 0);
__decorate([
    state()
], PageUsers.prototype, "_compositeRoles", void 0);
__decorate([
    state()
], PageUsers.prototype, "_loadDataPromise", void 0);
__decorate([
    state()
], PageUsers.prototype, "_saveUserPromise", void 0);
__decorate([
    state()
], PageUsers.prototype, "_sessionLoader", void 0);
PageUsers = __decorate([
    customElement("page-users")
], PageUsers);
export { PageUsers };
//# sourceMappingURL=page-users.js.map