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, query, state } from "lit/decorators.js";
import manager, { DefaultColor3 } from "@openremote/core";
import "@openremote/or-components/or-panel";
import "@openremote/or-translate";
import { Page } from "@openremote/or-app";
import { when } from "lit/directives/when.js";
import { until } from "lit/directives/until.js";
import { map } from 'lit/directives/map.js';
import { guard } from "lit/directives/guard.js";
import { i18next } from "@openremote/or-translate";
import { InputType, OrMwcInput } from "@openremote/or-mwc-components/or-mwc-input";
import { showSnackbar } from "@openremote/or-mwc-components/or-mwc-snackbar";
export function pageAccountProvider(store) {
    return {
        name: "account",
        routes: [
            "/account"
        ],
        pageCreator: () => new PageAccount(store)
    };
}
let PageAccount = class PageAccount extends Page {
    constructor() {
        super(...arguments);
        this._dirty = false;
        this._invalid = false;
        this._passwordPolicy = [];
    }
    static _getStyle() {
        return 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 10px;
                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-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;
                gap: 20px;
            }

            h5 {
                margin-bottom: 0;
            }

            @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;
                }
            }
        `;
    }
    static get styles() {
        return this._getStyle();
    }
    get name() {
        return "account";
    }
    stateChanged(_state) {
    }
    connectedCallback() {
        super.connectedCallback();
        this._getPasswordPolicy();
    }
    render() {
        if (!manager.authenticated) {
            return html `
                <or-translate value="notAuthenticated"></or-translate>
            `;
        }
        if (!manager.isKeycloak()) {
            return html `
                <or-translate value="notSupported"></or-translate>
            `;
        }
        const readonly = !manager.hasRole("write:user" /* ClientRole.WRITE_USER */);
        return html `
            <div id="wrapper">
                <div id="title">
                    <or-icon icon="account"></or-icon>
                    <or-translate value="account">
                </div>
                <div id="content" class="panel">

                    <p class="panel-title">${i18next.t("user")} ${i18next.t("settings")}</p>

                    <!-- Account settings row -->
                    ${guard([this._user], () => until(this._getAccountRowTemplate(this._user, readonly, (_user, dirty, invalid) => {
            this._dirty = dirty;
            this._invalid = invalid;
        })))}

                    <!-- Actions row (such as the save button) -->
                    ${when(this._user, () => until(this._getActionsRowTemplate(this._user)))}

                </div>

                ${when(manager.isKeycloak(), () => html `
                    <div class="panel">
                        <p class="panel-title">
                            <or-translate value="twoFactorAuth"></or-translate>
                        </p>
                        <div class="row">
                            <div class="column">
                                <or-mwc-input .type="${InputType.BUTTON}" label="${i18next.t('twoFactorConfigure')}"
                                              outlined
                                              @or-mwc-input-changed="${() => manager.login({ action: "CONFIGURE_TOTP" })}"
                                ></or-mwc-input>
                            </div>
                        </div>
                    </div>
                `)}
            </div>
        `;
    }
    /**
     * Asynchronous function that returns the account settings template.
     * Will call {@link onchange} when an input field submits a new value.
     */
    _getAccountRowTemplate(user_1) {
        return __awaiter(this, arguments, void 0, function* (user, readonly = true, onchange) {
            const realmResponse = yield manager.rest.api.RealmResource.get(manager.displayRealm);
            const registrationEmailAsUsername = realmResponse.data.registrationEmailAsUsername;
            if (!user) {
                user = yield this._getUser();
            }
            return html `
            <div class="row">
                <div class="column">
                    <!-- user details -->
                    <h5>${i18next.t("details")}</h5>
                    <or-mwc-input id="new-username" class="validate"
                                  .label="${i18next.t("username")}"
                                  .type="${InputType.TEXT}"
                                  ?required="${!registrationEmailAsUsername}"
                                  .disabled="${true}"
                                  minLength="3" maxLength="255" pattern="[A-Za-z0-9\\-_+@\\.ßçʊÇʊ]+"
                                  .validationMessage="${i18next.t("invalidUsername")}"
                                  .value="${user === null || user === void 0 ? void 0 : user.username}" autocomplete="false"
                                  @or-mwc-input-changed="${(e) => {
                user.username = e.detail.value;
                onchange === null || onchange === void 0 ? void 0 : onchange(user, true, this._isInvalid());
            }}"
                    ></or-mwc-input>
                    <!-- if identity provider is set to use email as username, make it required -->
                    <or-mwc-input id="new-email" class="validate"
                                  .label="${i18next.t("email")}"
                                  .type="${InputType.EMAIL}"
                                  ?required="${registrationEmailAsUsername}"
                                  ?readonly="${(!!(user === null || user === void 0 ? void 0 : user.id) && registrationEmailAsUsername) || readonly}"
                                  .disabled="${!user || (!!(user === null || user === void 0 ? void 0 : user.id) && registrationEmailAsUsername)}"
                                  pattern="^[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w]{2,4}$"
                                  .validationMessage="${i18next.t("invalidEmail")}"
                                  .value="${user === null || user === void 0 ? void 0 : user.email}" autocomplete="false"
                                  @or-mwc-input-changed="${(e) => {
                user.email = e.detail.value;
                onchange === null || onchange === void 0 ? void 0 : onchange(user, true, this._isInvalid());
            }}"
                    ></or-mwc-input>
                    <or-mwc-input id="new-firstName" class="validate"
                                  .label="${i18next.t("firstName")}"
                                  .type="${InputType.TEXT}"
                                  ?readonly="${readonly}"
                                  .disabled="${!user}"
                                  minLength="5" autocomplete="false"
                                  .value="${user === null || user === void 0 ? void 0 : user.firstName}"
                                  @or-mwc-input-changed="${(e) => {
                user.firstName = e.detail.value;
                onchange === null || onchange === void 0 ? void 0 : onchange(user, true, this._isInvalid());
            }}"
                    ></or-mwc-input>
                    <or-mwc-input id="new-surname" class="validate"
                                  .label="${i18next.t("surname")}"
                                  .type="${InputType.TEXT}"
                                  ?readonly="${readonly}"
                                  .disabled="${!user}"
                                  minLength="1" autocomplete="false"
                                  .value="${user === null || user === void 0 ? void 0 : user.lastName}"
                                  @or-mwc-input-changed="${(e) => {
                user.lastName = e.detail.value;
                onchange === null || onchange === void 0 ? void 0 : onchange(user, true, this._isInvalid());
            }}">
                    </or-mwc-input>
                </div>
                <div class="column">
                    <h5>${i18next.t("password")}</h5>
                    ${registrationEmailAsUsername ? html `
                        <!-- Reset password button when email as username is configured -->
                        <or-mwc-input id="reset-password" raised
                                      .label="${i18next.t("resetPassword")}"
                                      .type="${InputType.BUTTON}"
                                      .disabled="${!user.email}"
                                      @or-mwc-input-changed="${(e) => this._onResetPasswordBtnClick(e)}"
                        ></or-mwc-input>
                    ` : html `
                        <!-- Direct password input fields when email as username not configured -->
                        <or-mwc-input id="new-password" class="validate"
                                      .label="${i18next.t("password")}"
                                      .type="${InputType.PASSWORD}"
                                      ?readonly="${readonly}"
                                      .disabled="${!user}"
                                      min="1" autocomplete="false"
                                      @or-mwc-input-changed="${(_e) => {
                const changed = this._onPasswordChanged(user);
                onchange === null || onchange === void 0 ? void 0 : onchange(user, changed, this._isInvalid());
            }}"
                        ></or-mwc-input>
                        <or-mwc-input id="new-repeatPassword"
                                      .label="${i18next.t("repeatPassword")}"
                                      .type="${InputType.PASSWORD}"
                                      helperPersistent
                                      ?readonly="${readonly}"
                                      .disabled="${!user}"
                                      min="1" autocomplete="false"
                                      @or-mwc-input-changed="${(_e) => {
                const changed = this._onPasswordChanged(user);
                onchange === null || onchange === void 0 ? void 0 : onchange(user, changed, this._isInvalid());
            }}"
                        ></or-mwc-input>
                        ${when(this._passwordPolicy, () => until(this._getPasswordPolicyTemplate(user, this._passwordPolicy)))}
                    `}
                </div>
            </div>

        `;
        });
    }
    /**
     * Asynchronous function that returns the actions template, such as the 'save' button.
     */
    _getActionsRowTemplate(user) {
        return __awaiter(this, void 0, void 0, function* () {
            const invalid = this._isInvalid();
            const dirty = this._isDirty();
            return html `
            <div class="row" style="justify-content: end; margin-top: 20px;">
                <or-mwc-input id="savebtn" style="margin: 0;" raised
                              .label="${i18next.t(user.id ? "save" : "create")}"
                              .type="${InputType.BUTTON}"
                              .disabled="${invalid || !dirty}"
                              @or-mwc-input-changed="${(e) => this._onSaveBtnClick(e)}"
                ></or-mwc-input>
            </div>
        `;
        });
    }
    /**
     * HTML callback function when any of the password input fields change.
     * Checks whether {@link _passwordElem} and {@link _repeatPasswordElem} are the same,
     * and updates the {@link user} object with the new password.
     */
    _onPasswordChanged(user) {
        var _a, _b, _c, _d;
        const password = (_a = this._passwordElem) === null || _a === void 0 ? void 0 : _a.nativeValue;
        const repeatPassword = (_b = this._repeatPasswordElem) === null || _b === void 0 ? void 0 : _b.nativeValue;
        if (password && repeatPassword) {
            if (password !== repeatPassword) {
                const error = i18next.t("passwordMismatch");
                (_c = this._repeatPasswordElem) === null || _c === void 0 ? void 0 : _c.setCustomValidity(error);
                user.password = "";
            }
            else {
                (_d = this._repeatPasswordElem) === null || _d === void 0 ? void 0 : _d.setCustomValidity(undefined);
                user.password = password;
                return true;
            }
        }
        else {
            console.warn("Could not update password; some fields are empty;", password, repeatPassword);
        }
        return false;
    }
    /**
     * HTML callback function for when the 'Save' button is clicked.
     * Checks if the input fields are valid, and calls the {@link _updateUser} function.
     */
    _onSaveBtnClick(_e) {
        if (this._user && !this._isInvalid()) {
            this._updateUser(this._user);
        }
        else {
            console.warn("The fields are invalid!");
            showSnackbar(undefined, "saveUserFailed");
        }
    }
    /**
     * HTML callback function for when the 'Reset Password' button is clicked.
     */
    _onResetPasswordBtnClick(_e) {
        if (this._user && !this._isInvalid()) {
            manager.rest.api.UserResource.requestPasswordResetCurrent()
                .then(() => showSnackbar(undefined, i18next.t("resetPasswordConfirmation")))
                .catch((reason) => showSnackbar(undefined, i18next.t("errorOccurred")));
        }
    }
    /**
     * Function that returns whether the input fields are all valid.
     * Listens for HTML elements with the 'validate' class.
     */
    _isInvalid() {
        const validateArray = this.shadowRoot.querySelectorAll(".validate");
        if (validateArray.length === 0) {
            return true;
        }
        return Array.from(validateArray).filter(e => e instanceof OrMwcInput).some(input => !input.valid);
    }
    /**
     * Function that returns if the input fields are 'dirty'. (if they have been changed by the user)
     */
    _isDirty() {
        return this._dirty;
    }
    /**
     * Function that fetches (and returns) the currently logged in {@link UserModel} from the Manager HTTP API.
     */
    _getUser() {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this._user) {
                try {
                    const usersResponse = yield manager.rest.api.UserResource.getCurrent();
                    if (usersResponse.status < 200 || usersResponse.status > 299) {
                        throw new Error(usersResponse.statusText);
                    }
                    if (!usersResponse.data) {
                        throw new Error("No user could be found.");
                    }
                    this._user = usersResponse.data;
                }
                catch (e) {
                    console.error(e);
                    showSnackbar(undefined, "errorOccurred");
                }
            }
            return this._user;
        });
    }
    /**
     * Function that updates the {@link UserModel} through the Manager HTTP API.
     * If the password has been changed, it will, after updating the user, also request to reset the password.
     */
    _updateUser(user) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                yield manager.rest.api.UserResource.updateCurrent(user);
                if (user.password) {
                    const credentials = { value: user.password };
                    try {
                        yield manager.rest.api.UserResource.updatePasswordCurrent(credentials);
                        showSnackbar(undefined, "saveUserSucceeded");
                    }
                    catch (e) {
                        showSnackbar(undefined, "saveUserFailed");
                        return;
                    }
                }
                else {
                    showSnackbar(undefined, "saveUserSucceeded");
                }
                // Update the stored user with the saved version and reset dirty flag
                this._user = Object.assign({}, user);
                this._dirty = false;
            }
            catch (e) {
                console.error("Failed to update user:", e);
                showSnackbar(undefined, "saveUserFailed");
            }
        });
    }
    /**
     * Function that formats the password policy from the currently authenticated realm.
     */
    _getPasswordPolicy() {
        return __awaiter(this, void 0, void 0, function* () {
            yield manager.rest.api.RealmResource.get(manager.getRealm()).then((response) => {
                var _a;
                this._passwordPolicy = (_a = response.data.passwordPolicy) !== null && _a !== void 0 ? _a : this._passwordPolicy;
            });
        });
    }
    /**
     * Function that formats the password policy into a displayable html format.
     */
    _getPasswordPolicyTemplate(user_1) {
        return __awaiter(this, arguments, void 0, function* (user, passwordPolicy = this._passwordPolicy) {
            const policyMap = new Map(passwordPolicy.map(policyStr => {
                var _a;
                const name = policyStr.split("(")[0];
                const value = (_a = policyStr.split("(")[1]) === null || _a === void 0 ? void 0 : _a.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>
        `;
        });
    }
};
__decorate([
    state()
], PageAccount.prototype, "_user", void 0);
__decorate([
    state()
], PageAccount.prototype, "_dirty", void 0);
__decorate([
    state()
], PageAccount.prototype, "_invalid", void 0);
__decorate([
    state()
], PageAccount.prototype, "_passwordPolicy", void 0);
__decorate([
    query("#new-password")
], PageAccount.prototype, "_passwordElem", void 0);
__decorate([
    query("#new-repeatPassword")
], PageAccount.prototype, "_repeatPasswordElem", void 0);
PageAccount = __decorate([
    customElement("page-account")
], PageAccount);
export { PageAccount };
//# sourceMappingURL=page-account.js.map