// Copyright 2021 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import React, {Suspense, lazy} from "react";
import {Button, Checkbox, Col, Form, Input, Result, Tabs, message} from "antd";
import Loading from "../common/Loading";
import {ArrowLeftOutlined, LockOutlined, UserOutlined} from "@ant-design/icons";
import {withRouter} from "react-router-dom";
import * as UserWebauthnBackend from "../backend/UserWebauthnBackend";
import OrganizationSelect from "../common/select/OrganizationSelect";
import * as Conf from "../Conf";
import * as Obfuscator from "./Obfuscator";
import * as AuthBackend from "./AuthBackend";
import * as OrganizationBackend from "../backend/OrganizationBackend";
import * as ApplicationBackend from "../backend/ApplicationBackend";
import * as Provider from "./Provider";
import * as Util from "./Util";
import * as Setting from "../Setting";
import * as AgreementModal from "../common/modal/AgreementModal";
import SelfLoginButton from "./SelfLoginButton";
import i18next from "i18next";
import CustomGithubCorner from "../common/CustomGithubCorner";
import {SendCodeInput} from "../common/SendCodeInput";
import LanguageSelect from "../common/select/LanguageSelect";
import {CaptchaModal} from "../common/modal/CaptchaModal";
import RedirectForm from "../common/RedirectForm";
import {RequiredMfa} from "./mfa/MfaAuthVerifyForm";
import {GoogleOneTapLoginVirtualButton} from "./GoogleLoginButton";
import * as ProviderButton from "./ProviderButton";
import {createFormAndSubmit, goToLink} from "../Setting";
import WeChatLoginPanel from "./WeChatLoginPanel";
import DeviceLoginPanel from "./DeviceLoginPanel";
import NativeSsoPanel from "./NativeSsoPanel";
import {CountryCodeSelect} from "../common/select/CountryCodeSelect";
const FaceRecognitionCommonModal = lazy(() => import("../common/modal/FaceRecognitionCommonModal"));
const FaceRecognitionModal = lazy(() => import("../common/modal/FaceRecognitionModal"));

class LoginPage extends React.Component {
  constructor(props) {
    super(props);
    this.captchaRef = React.createRef();
    const urlParams = new URLSearchParams(this.props.location?.search);
    this.state = {
      classes: props,
      type: props.type,
      applicationName: props.applicationName ?? (props.match?.params?.applicationName ?? null),
      owner: props.owner ?? (props.match?.params?.owner ?? null),
      mode: props.mode ?? (props.match?.params?.mode ?? null), // "signup" or "signin"
      msg: null,
      username: null,
      validEmailOrPhone: false,
      validEmail: false,
      openCaptchaModal: false,
      openFaceRecognitionModal: false,
      verifyCaptcha: undefined,
      samlResponse: "",
      relayState: "",
      redirectUrl: "",
      isTermsOfUseVisible: false,
      termsOfUseContent: "",
      orgChoiceMode: new URLSearchParams(props.location?.search).get("orgChoiceMode") ?? null,
      userLang: null,
      loginLoading: false,
      userCode: props.userCode ?? (props.match?.params?.userCode ?? null),
      userCodeStatus: "",
      prefilledUsername: urlParams.get("username") || urlParams.get("login_hint"),
      nativeSsoActive: false,
      nativeSsoSuppressed: false,
      nativeSsoFallbackVisible: false,
      nativeSsoKnownAgent: null,
      nativeSsoRestartKey: 0,
      nativeSsoOverlay: "",
    };
    this.nativeSsoOverlayTimer = null;

    if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
      this.state.owner = props.match?.params?.owner;
      this.state.applicationName = props.match?.params?.casApplicationName;
    }

    localStorage.setItem("signinUrl", window.location.pathname + window.location.search);

    this.form = React.createRef();
    this.refreshInlineCaptcha = this.refreshInlineCaptcha.bind(this);
  }

  componentWillUnmount() {
    if (this.nativeSsoOverlayTimer !== null) {
      clearTimeout(this.nativeSsoOverlayTimer);
      this.nativeSsoOverlayTimer = null;
    }
  }

  refreshInlineCaptcha() {
    this.captchaRef.current?.loadCaptcha?.();
  }

  componentDidMount() {
    if (this.getApplicationObj() === undefined) {
      if (this.state.type === "login" || this.state.type === "saml") {
        this.getApplication();
      } else if (this.state.type === "code" || this.state.type === "cas" || this.state.type === "device") {
        this.getApplicationLogin();
      } else {
        Setting.showMessage("error", `${i18next.t("general:Unknown authentication type")}: ${this.state.type}`);
      }
    }
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (prevState.loginMethod === undefined && this.state.loginMethod === undefined) {
      const application = this.getApplicationObj();
      this.setState({loginMethod: this.getDefaultLoginMethod(application)});
    }
    if (prevProps.application !== this.props.application) {
      this.setState({loginMethod: this.getDefaultLoginMethod(this.props.application)});
    }
    if (this.props.account !== undefined) {
      if (prevProps.account === this.props.account && prevProps.application === this.props.application) {
        return;
      }

      if (this.props.account && this.props.account.owner === this.props.application?.organization) {
        const params = new URLSearchParams(this.props.location.search);
        const silentSignin = params.get("silentSignin");
        if (silentSignin !== null) {
          this.sendSilentSigninData("signing-in");

          const values = {};
          values["application"] = this.props.application.name;
          this.login(values);
        }

        if (params.get("popup") === "1") {
          window.addEventListener("beforeunload", () => {
            this.sendPopupData({type: "windowClosed"}, params.get("redirect_uri"));
          });
        }

        if (this.props.application.enableAutoSignin && silentSignin === null) {
          const values = {};
          values["application"] = this.props.application.name;
          this.login(values);
        }
      }
    }
  }

  checkCaptchaStatus(values) {
    AuthBackend.getCaptchaStatus(values)
      .then((res) => {
        if (res.status === "ok") {
          if (res.data) {
            this.setState({
              openCaptchaModal: true,
              values: values,
            });
            return null;
          }
        }
        this.login(values);
      });
  }

  getApplicationLogin() {
    let loginParams;
    if (this.state.type === "cas") {
      loginParams = Util.getCasLoginParameters("admin", this.state.applicationName);
    } else if (this.state.type === "device") {
      loginParams = {userCode: this.state.userCode, type: this.state.type};
    } else {
      loginParams = Util.getOAuthGetParameters();
    }
    AuthBackend.getApplicationLogin(loginParams)
      .then((res) => {
        if (res.status === "ok") {
          const application = res.data;
          this.onUpdateApplication(application);
        } else {
          if (this.state.type === "device") {
            this.setState({
              userCodeStatus: "expired",
            });
          }
          this.onUpdateApplication(null);
          this.setState({
            msg: res.msg,
          });
        }
      });
  }

  getApplication() {
    if (this.state.applicationName === null) {
      return null;
    }

    if (this.state.owner === null || this.state.type === "saml") {
      ApplicationBackend.getApplication("admin", this.state.applicationName)
        .then((res) => {
          if (res.status === "error") {
            this.onUpdateApplication(null);
            this.setState({
              msg: res.msg,
            });
            return;
          }
          this.onUpdateApplication(res.data);
        });
    } else {
      OrganizationBackend.getDefaultApplication("admin", this.state.owner)
        .then((res) => {
          if (res.status === "ok") {
            const application = res.data;
            this.onUpdateApplication(application);
            this.setState({
              applicationName: res.data.name,
            });
          } else {
            this.onUpdateApplication(null);
            Setting.showMessage("error", res.msg);

            this.props.history.push("/404");
          }
        });
    }
  }

  getApplicationObj() {
    return this.props.application;
  }

  getDefaultLoginMethod(application) {
    if (application?.signinMethods?.length > 0) {
      switch (application?.signinMethods[0].name) {
      case "Password": return "password";
      case "Verification code": {
        switch (application?.signinMethods[0].rule) {
        case "All": return "verificationCode"; // All
        case "Email only": return "verificationCodeEmail";
        case "Phone only": return "verificationCodePhone";
        }
        break;
      }
      case "WebAuthn": return "webAuthn";
      case "LDAP": return "ldap";
      case "Face ID": return "faceId";
      case "Device login":
        if (application?.signinMethods[0]?.rule === "Tab") {
          return "device";
        }
        break;
      }
    }

    return "password";
  }

  getCurrentLoginMethod() {
    if (this.state.loginMethod === "password") {
      return "Password";
    } else if (this.state.loginMethod?.includes("verificationCode")) {
      return "Verification code";
    } else if (this.state.loginMethod === "webAuthn") {
      return "WebAuthn";
    } else if (this.state.loginMethod === "ldap") {
      return "LDAP";
    } else if (this.state.loginMethod === "faceId") {
      return "Face ID";
    } else if (this.state.loginMethod === "device") {
      return "Device login";
    } else {
      return "Password";
    }
  }

  getPlaceholder(defaultPlaceholder = null) {
    if (defaultPlaceholder) {
      return defaultPlaceholder;
    }
    switch (this.state.loginMethod) {
    case "verificationCode": return i18next.t("login:Email or phone");
    case "verificationCodeEmail": return i18next.t("general:Email");
    case "verificationCodePhone": return i18next.t("general:Phone");
    case "ldap": return i18next.t("login:LDAP username, Email or phone");
    default: return i18next.t("login:username, Email or phone");
    }
  }

  onUpdateAccount(account) {
    this.props.onUpdateAccount(account);
  }

  onUpdateApplication(application) {
    this.props.onUpdateApplication(application);
    if (application === null) {
      return;
    }
    for (const idx in application.providers) {
      const provider = application.providers[idx];
      if (provider.provider?.category === "Face ID") {
        this.setState({haveFaceIdProvider: true});
        break;
      }
    }
  }

  parseOffset(offset) {
    if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) {
      return "0 auto";
    }
    if (offset === 1) {
      return "0 10%";
    }
    if (offset === 3) {
      return "0 60%";
    }
  }

  populateOauthValues(values) {
    if (this.getApplicationObj()?.organization) {
      values["organization"] = this.getApplicationObj().organization;
    }

    values["signinMethod"] = this.getCurrentLoginMethod();
    const oAuthParams = Util.getOAuthGetParameters();

    values["type"] = oAuthParams?.responseType ?? this.state.type;
    if (this.state.userCode) {
      values["userCode"] = this.state.userCode;
    }

    if (oAuthParams?.samlRequest) {
      values["samlRequest"] = oAuthParams.samlRequest;
      values["type"] = "saml";
      values["relayState"] = oAuthParams.relayState;
    }
  }

  sendPopupData(message, redirectUri) {
    const params = new URLSearchParams(this.props.location.search);
    const popup = params.get("popup");
    const popupType = params.get("popup_type") || "window";
    if (popup !== "1") {
      return;
    }

    if (popupType === "iframe") {
      window.parent.postMessage(message, new URL(redirectUri).origin);
    } else {
      window.opener.postMessage(message, redirectUri);
    }
  }

  postCodeLoginAction(resp) {
    const application = this.getApplicationObj();
    const ths = this;
    const oAuthParams = Util.getOAuthGetParameters();
    const code = resp.data;
    const concatChar = oAuthParams?.redirectUri?.includes("?") ? "&" : "?";
    const noRedirect = oAuthParams.noRedirect;
    const redirectUrl = `${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`;
    if (resp.data === RequiredMfa) {
      this.props.onLoginSuccess(window.location.href);
      return;
    }

    if (resp.data3) {
      sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
      Setting.goToLinkSoft(ths, "/account");
      return;
    }

    // Check if consent is required
    if (resp.data?.required === true) {
      // Consent required, redirect to consent page
      Setting.goToLinkSoft(ths, `/consent/${application.name}?${window.location.search.substring(1)}`);
      return;
    }

    if (Setting.hasPromptPage(application)) {
      AuthBackend.getAccount()
        .then((res) => {
          if (res.status === "ok") {
            const account = res.data;
            account.organization = res.data2;
            this.onUpdateAccount(account);

            if (Setting.isPromptAnswered(account, application)) {
              Setting.goToLink(redirectUrl);
            } else {
              Setting.goToLinkSoft(ths, `/prompt/${application.name}?redirectUri=${oAuthParams.redirectUri}&code=${code}&state=${oAuthParams.state}`);
            }
          } else {
            Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
          }
        });
    } else {
      if (noRedirect === "true") {
        window.close();
        const newWindow = window.open(redirectUrl);
        if (newWindow) {
          setInterval(() => {
            if (!newWindow.closed) {
              newWindow.close();
            }
          }, 1000);
        }
      } else {
        const params = new URLSearchParams(this.props.location.search);
        const popupType = params.get("popup_type") || "window";
        const popup = params.get("popup");
        const isIframePopup = popup === "1" && popupType === "iframe";
        if (!isIframePopup) {
          Setting.goToLink(redirectUrl);
        }
        this.sendPopupData({type: "loginSuccess", data: {code: code, state: oAuthParams.state}}, oAuthParams.redirectUri);
      }
    }
  }

  onFinish(values) {
    this.setState({loginLoading: true});
    if (this.state.loginMethod === "webAuthn") {
      let username = this.state.username;
      if (username === null || username === "") {
        username = values["username"];
      }

      this.signInWithWebAuthn(username, values);
      return;
    }
    if (this.state.loginMethod === "faceId") {
      let username = this.state.username;
      if (username === null || username === "") {
        username = values["username"];
      }
      const application = this.getApplicationObj();
      fetch(`${Setting.ServerUrl}/api/faceid-signin-begin?owner=${application.organization}&name=${username}`, {
        method: "GET",
        credentials: "include",
        headers: {
          "Accept-Language": Setting.getAcceptLanguage(),
        },
      }).then(res => res.json())
        .then((res) => {
          if (res.status === "error") {
            this.setState({
              loginLoading: false,
            });
            Setting.showMessage("error", res.msg);
            return;
          }
          this.setState({
            openFaceRecognitionModal: true,
            values: values,
          });
        });
      return;
    }
    if (this.state.loginMethod === "password" || this.state.loginMethod === "ldap") {
      const organization = this.getApplicationObj()?.organizationObj;
      const [passwordCipher, errorMessage] = Obfuscator.encryptByPasswordObfuscator(organization?.passwordObfuscatorType, organization?.passwordObfuscatorKey, values["password"]);
      if (errorMessage.length > 0) {
        Setting.showMessage("error", errorMessage);
        return;
      } else {
        values["password"] = passwordCipher;
      }
      const captchaRule = Setting.getCaptchaRule(this.getApplicationObj());
      const application = this.getApplicationObj();
      const inlineCaptchaEnabled = Setting.isInlineCaptchaEnabled(application);
      if (!inlineCaptchaEnabled) {
        if (captchaRule === Setting.CaptchaRule.Always) {
          this.setState({
            openCaptchaModal: true,
            values: values,
          });
          return;
        } else if (captchaRule === Setting.CaptchaRule.Dynamic) {
          this.checkCaptchaStatus(values);
          return;
        } else if (captchaRule === Setting.CaptchaRule.InternetOnly) {
          this.checkCaptchaStatus(values);
          return;
        }
      } else {
        values["captchaType"] = this.state?.captchaValues?.captchaType;
        values["captchaToken"] = this.state?.captchaValues?.captchaToken;
        values["clientSecret"] = this.state?.captchaValues?.clientSecret;
      }
    }
    this.login(values);
  }

  login(values) {
    // here we are supposed to determine whether Casdoor is working as an OAuth server or CAS server
    values["language"] = this.state.userLang ?? "";
    const usedCaptcha = this.state.captchaValues !== undefined;
    const inlineCaptchaEnabled = Setting.isInlineCaptchaEnabled(this.getApplicationObj());
    const shouldRefreshCaptcha = usedCaptcha && inlineCaptchaEnabled && !this.state.loginMethod?.includes("verificationCode");
    if (this.state.type === "cas") {
      // CAS
      const casParams = Util.getCasParameters();
      values["signinMethod"] = this.getCurrentLoginMethod();
      values["type"] = this.state.type;
      AuthBackend.loginCas(values, casParams).then((res) => {
        const loginHandler = (res) => {
          let msg = "Logged in successfully. ";
          if (casParams.service === "") {
            // If service was not specified, Casdoor must display a message notifying the client that it has successfully initiated a single sign-on session.
            msg += "Now you can visit apps protected by Casdoor.";
          }
          Setting.showMessage("success", msg);

          if (casParams.service !== "") {
            const st = res.data;
            const newUrl = new URL(casParams.service);
            newUrl.searchParams.append("ticket", st);
            window.location.href = newUrl.toString();
          }
        };

        if (res.status === "ok") {
          Setting.checkLoginMfa(res, values, casParams, loginHandler, this);
        } else {
          Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
          if (shouldRefreshCaptcha) {
            this.refreshInlineCaptcha();
          }
        }
      }).finally(() => {
        this.setState({loginLoading: false});
      });
    } else {
      // OAuth
      const oAuthParams = Util.getOAuthGetParameters();
      this.populateOauthValues(values);
      AuthBackend.login(values, oAuthParams)
        .then((res) => {
          const loginHandler = (res) => {
            const responseType = values["type"];
            const responseTypes = responseType.split(" ");
            const responseMode = oAuthParams?.responseMode || "query";
            if (responseType === "login") {
              if (res.data3) {
                sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
                Setting.goToLinkSoft(this, "/account");
                return;
              }
              Setting.showMessage("success", i18next.t("application:Logged in successfully"));
              this.props.onLoginSuccess();
            } else if (responseType === "code") {
              this.postCodeLoginAction(res);
            } else if (responseType === "device") {
              Setting.showMessage("success", i18next.t("application:Logged in successfully"));
              this.setState({
                userCodeStatus: "success",
              });
            } else if (responseTypes.includes("token") || responseTypes.includes("id_token")) {
              if (res.data3) {
                sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
                Setting.goToLinkSoft(this, "/account");
                return;
              }
              const amendatoryResponseType = responseType === "token" ? "access_token" : responseType;
              const accessToken = res.data;
              if (responseMode === "form_post") {
                const params = {
                  token: responseTypes.includes("token") ? res.data : null,
                  id_token: responseTypes.includes("id_token") ? res.data : null,
                  token_type: "bearer",
                  state: oAuthParams?.state,
                };
                createFormAndSubmit(oAuthParams?.redirectUri, params);
              } else {
                Setting.goToLink(`${oAuthParams.redirectUri}#${amendatoryResponseType}=${accessToken}&state=${oAuthParams.state}&token_type=bearer`);
              }
            } else if (responseType === "saml") {
              if (res.data === RequiredMfa) {
                this.props.onLoginSuccess(window.location.href);
                return;
              }
              if (res.data3) {
                sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
                Setting.goToLinkSoft(this, "/account");
                return;
              }
              if (res.data2.method === "POST") {
                this.setState({
                  samlResponse: res.data,
                  redirectUrl: res.data2.redirectUrl,
                  relayState: oAuthParams.relayState,
                });
              } else {
                const SAMLResponse = res.data;
                const redirectUri = res.data2.redirectUrl;
                Setting.goToLink(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${encodeURIComponent(oAuthParams.relayState)}`);
              }
            }
          };

          if (res.status === "ok") {
            Setting.checkLoginMfa(res, values, oAuthParams, loginHandler, this);
          } else {
            Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
            if (shouldRefreshCaptcha) {
              this.refreshInlineCaptcha();
            }
          }
        }).finally(() => {
          localStorage.setItem("lastLoginOrg", values?.organization || "");
          this.setState({loginLoading: false});
        });
    }
  }

  isProviderVisible(providerItem) {
    if (this.state.mode === "signup") {
      return Setting.isProviderVisibleForSignUp(providerItem);
    } else {
      return Setting.isProviderVisibleForSignIn(providerItem);
    }
  }

  renderOtherFormProvider(application) {
    if (Setting.inIframe()) {
      return null;
    }

    for (const providerConf of application.providers) {
      if (providerConf.provider?.type === "Google" && providerConf.rule === "OneTap" && this.props.preview !== "auto") {
        return (
          <GoogleOneTapLoginVirtualButton application={application} providerConf={providerConf} />
        );
      }
    }

    return null;
  }

  switchLoginOrganization(name) {
    const searchParams = new URLSearchParams(window.location.search);

    const clientId = searchParams.get("client_id");
    if (clientId) {
      const clientIdSplited = clientId.split("-org-");
      searchParams.set("client_id", `${clientIdSplited[0]}-org-${name}`);

      Setting.goToLink(`/login/oauth/authorize?${searchParams.toString()}`);
      return;
    }

    const application = this.getApplicationObj();
    if (window.location.pathname.startsWith("/login/saml/authorize")) {
      Setting.goToLink(`/login/saml/authorize/${name}/${application.name}-org-${name}?${searchParams.toString()}`);
      return;
    }

    if (window.location.pathname.startsWith("/cas")) {
      Setting.goToLink(`/cas/${application.name}-org-${name}/${name}/login?${searchParams.toString()}`);
      return;
    }
    searchParams.set("orgChoiceMode", "None");
    Setting.goToLink(`/login/${name}?${searchParams.toString()}`);
  }

  renderFormItem(application, signinItem) {
    if (!signinItem.visible && signinItem.name !== "Forgot password?") {
      return null;
    }

    const resultItemKey = `${application.organization}_${application.name}_${signinItem.name}`;

    if (signinItem.name === "Logo") {
      return (
        <div key={resultItemKey} className="login-logo-box">
          <div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
          {
            Setting.renderHelmet(application)
          }
          {
            Setting.renderLogo(application)
          }
        </div>
      );
    } else if (signinItem.name === "Back button") {
      return (
        <div key={resultItemKey} className="back-button">
          <div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
          {
            this.renderBackButton()
          }
        </div>
      );
    } else if (signinItem.name === "Languages") {
      const languages = application.organizationObj.languages;
      if (languages.length <= 1) {
        const language = (languages.length === 1) ? languages[0] : "en";
        if (Setting.getLanguage() !== language) {
          Setting.setLanguage(language);
        }
        return null;
      }

      return (
        <div key={resultItemKey} className="login-languages">
          <div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
          <LanguageSelect
            languages={application.organizationObj.languages}
            mode={signinItem.rule}
            onClick={key => {this.setState({userLang: key});}}
          />
        </div>
      );
    } else if (signinItem.name === "Signin methods") {
      return (
        <div key={resultItemKey}>
          <div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
          {this.renderMethodChoiceBox()}
        </div>
      )
      ;
    } else if (signinItem.name === "Username") {
      if (this.state.loginMethod === "wechat") {
        return (<WeChatLoginPanel application={application} loginMethod={this.state.loginMethod} />);
      }

      if (this.state.loginMethod === "device") {
        return (<DeviceLoginPanel application={application} onSuccess={(deviceCode) => this.handleDeviceLoginComplete(deviceCode)} />);
      }

      if (this.state.loginMethod === "verificationCodePhone") {
        return <Form.Item className="signin-phone" required={true}>
          <Input.Group compact>
            <Form.Item
              name="countryCode"
              noStyle
              rules={[
                {
                  required: true,
                  message: i18next.t("signup:Please select your country code!"),
                },
              ]}
            >
              <CountryCodeSelect
                style={{width: "35%"}}
                countryCodes={this.getApplicationObj().organizationObj.countryCodes}
              />
            </Form.Item>
            <Form.Item
              name="username"
              dependencies={["countryCode"]}
              noStyle
              rules={[
                {
                  required: true,
                  message: i18next.t("signup:Please input your phone number!"),
                },
                ({getFieldValue}) => ({
                  validator: (_, value) => {
                    if (!value) {
                      return Promise.resolve();
                    }

                    if (value && !Setting.isValidPhone(value, getFieldValue("countryCode"))) {
                      this.setState({validEmailOrPhone: false});
                      return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
                    }

                    this.setState({validEmailOrPhone: true});
                    return Promise.resolve();
                  },
                }),
              ]}
            >
              <Input
                className="signup-phone-input"
                placeholder={signinItem.placeholder}
                style={{width: "65%", textAlign: "left"}}
                onChange={e => this.setState({username: e.target.value})}
              />
            </Form.Item>
          </Input.Group>
        </Form.Item>;
      }

      return (
        <div key={resultItemKey}>
          <div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
          <Form.Item
            name="username"
            className="login-username"
            label={signinItem.label ? signinItem.label : null}
            rules={[
              {
                required: this.state.loginMethod !== "webAuthn",
                message: () => {
                  switch (this.state.loginMethod) {
                  case "verificationCodeEmail":
                    return i18next.t("login:Please input your Email!");
                  case "verificationCodePhone":
                    return i18next.t("login:Please input your Phone!");
                  case "ldap":
                    return i18next.t("login:Please input your LDAP username!");
                  default:
                    return i18next.t("login:Please input your Email or Phone!");
                  }
                },
              },
              {
                validator: (_, value) => {
                  if (value === "") {
                    return Promise.resolve();
                  }

                  if (this.state.loginMethod === "verificationCode") {
                    if (!Setting.isValidEmail(value) && !Setting.isValidPhone(value)) {
                      this.setState({validEmailOrPhone: false});
                      return Promise.reject(i18next.t("login:The input is not valid Email or phone number!"));
                    }

                    if (Setting.isValidEmail(value)) {
                      this.setState({validEmail: true});
                    } else {
                      this.setState({validEmail: false});
                    }
                  } else if (this.state.loginMethod === "verificationCodeEmail") {
                    if (!Setting.isValidEmail(value)) {
                      this.setState({validEmail: false});
                      this.setState({validEmailOrPhone: false});
                      return Promise.reject(i18next.t("login:The input is not valid Email!"));
                    } else {
                      this.setState({validEmail: true});
                    }
                  } else if (this.state.loginMethod === "verificationCodePhone") {
                    if (!Setting.isValidPhone(value)) {
                      this.setState({validEmailOrPhone: false});
                      return Promise.reject(i18next.t("login:The input is not valid phone number!"));
                    }
                  }

                  this.setState({validEmailOrPhone: true});
                  return Promise.resolve();
                },
              },
            ]}
          >

            <Input
              id="input"
              className="login-username-input"
              prefix={<UserOutlined className="site-form-item-icon" />}
              placeholder={this.getPlaceholder(signinItem.placeholder)}
              onChange={e => {
                this.setState({
                  username: e.target.value,
                });
              }}
            />
          </Form.Item>
        </div>
      );
    } else if (signinItem.name === "Password") {
      return (
        <div key={resultItemKey}>
          <div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
          {this.renderPasswordOrCodeInput(signinItem)}
        </div>
      );
    } else if (signinItem.name === "Verification code") {
      return (
        <div key={resultItemKey}>
          <div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
          {this.renderCodeInput(signinItem)}
        </div>
      );
    } else if (signinItem.name === "Forgot password?") {
      return (
        <div key={resultItemKey}>
          <div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
          <div className="login-forget-password">
            <Form.Item name="autoSignin" valuePropName="checked" noStyle>
              <Checkbox className="login-auto-signin" style={{float: "left"}}>
                {i18next.t("login:Auto sign in")}
              </Checkbox>
            </Form.Item>
            {
              signinItem.visible ? Setting.renderForgetLink(application, signinItem.label ? signinItem.label : i18next.t("login:Forgot password?")) : null
            }
          </div>
        </div>
      );
    } else if (signinItem.name === "Agreement") {
      return AgreementModal.isAgreementRequired(application) ? AgreementModal.renderAgreementFormItem(application, true, {}, this) : null;
    } else if (signinItem.name === "Login button") {
      if (this.state.loginMethod === "wechat" || this.state.loginMethod === "device") {
        return null;
      }
      return (
        <Form.Item key={resultItemKey} className="login-button-box">
          <div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
          <Button
            loading={this.state.loginLoading}
            type="primary"
            htmlType="submit"
            className="login-button"
          >
            {
              this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
                this.state.loginMethod === "faceId" ? i18next.t("login:Sign in with Face ID") :
                  this.state.type === "device" ? i18next.t("login:Approve and sign in") :
                    signinItem.label ? signinItem.label : i18next.t("login:Sign In")
            }
          </Button>
          {this.renderNativeSsoFallbackLink()}
          {
            this.state.type === "device" ? (
              <Button
                style={{marginTop: 12}}
                block
                onClick={() => {
                  const cancelToken = new URLSearchParams(this.props.location.search).get("cancelToken") || "";
                  AuthBackend.cancelDeviceLogin(this.state.userCode, cancelToken)
                    .then((res) => {
                      if (res.status === "ok") {
                        this.setState({userCodeStatus: "canceled"});
                      } else {
                        Setting.showMessage("error", res.msg || i18next.t("general:Failed to cancel"));
                      }
                    })
                    .catch(() => {
                      Setting.showMessage("error", i18next.t("general:Failed to cancel"));
                    });
                }}
              >
                {i18next.t("general:Cancel")}
              </Button>
            ) : null
          }
          {
            this.state.loginMethod === "faceId" ?
              this.state.haveFaceIdProvider ? <Suspense fallback={null}><FaceRecognitionCommonModal visible={this.state.openFaceRecognitionModal} onOk={(FaceIdImage) => {
                const values = this.state.values;
                values["FaceIdImage"] = FaceIdImage;
                this.login(values);
                this.setState({openFaceRecognitionModal: false});
              }} onCancel={() => this.setState({openFaceRecognitionModal: false, loginLoading: false})} /></Suspense> :
                <Suspense fallback={null}>
                  <FaceRecognitionModal
                    visible={this.state.openFaceRecognitionModal}
                    onOk={(faceId) => {
                      const values = this.state.values;
                      values["faceId"] = faceId;

                      this.login(values);
                      this.setState({openFaceRecognitionModal: false});
                    }}
                    onCancel={() => this.setState({openFaceRecognitionModal: false, loginLoading: false})}
                  />
                </Suspense>
              :
              <>
              </>
          }
          {
            application?.signinItems.map(signinItem => signinItem.name === "Captcha" && signinItem.rule === "inline").includes(true) ? null : this.renderCaptchaModal(application, false)
          }
        </Form.Item>
      );
    } else if (signinItem.name === "Providers") {
      const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application);
      if (signinItem.rule === "None" || signinItem.rule === "") {
        signinItem.rule = showForm ? "small" : "big";
      }
      const searchParams = new URLSearchParams(window.location.search);
      const providerHint = searchParams.get("provider_hint");

      return (
        <div key={resultItemKey}>
          <div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
          <Form.Item>
            {
              application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map((providerItem, id) => {
                if (providerHint === providerItem.provider.name) {
                  goToLink(Provider.getAuthUrl(application, providerItem.provider, this.state.mode ?? "signup"));
                  return;
                }
                return (
                  <span key={id} onClick={(e) => {
                    const agreementChecked = this.form.current.getFieldValue("agreement");

                    if (agreementChecked !== undefined && typeof agreementChecked === "boolean" && !agreementChecked) {
                      e.preventDefault();
                      message.error(i18next.t("signup:Please accept the agreement!"));
                    }
                  }}>
                    {
                      ProviderButton.renderProviderLogo(providerItem.provider, application, null, null, signinItem.rule, this.props.location, this.state.mode ?? "signup")
                    }
                  </span>
                );
              })
            }
            {
              this.renderOtherFormProvider(application)
            }
          </Form.Item>
        </div>
      );
    } else if (signinItem.name === "Captcha" && signinItem.rule === "inline") {
      return this.renderCaptchaModal(application, true);
    } else if (signinItem.name.startsWith("Text ") || signinItem?.isCustom) {
      return (
        <div key={resultItemKey} dangerouslySetInnerHTML={{__html: signinItem.customCss}} />
      );
    } else if (signinItem.name === "Signup link") {
      return (
        <div key={resultItemKey} style={{width: "100%"}} className="login-signup-link">
          <div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
          {this.renderFooter(application, signinItem)}
        </div>
      );
    } else if (signinItem.name === "Select organization") {
      return (
        <Form.Item>
          <div key={resultItemKey} style={{width: "100%"}} className="login-organization-select">
            <OrganizationSelect style={{width: "100%"}} initValue={application.organization}
              onSelect={(value) => {
                this.switchLoginOrganization(value);
              }} />
          </div>
        </Form.Item>
      );
    }
  }

  renderForm(application) {
    if (this.state.msg !== null) {
      return Util.renderMessage(this.state.msg);
    }

    if (this.state.mode === "signup" && !application.enableSignUp) {
      return (
        <Result
          status="error"
          title={i18next.t("application:Sign Up Error")}
          subTitle={i18next.t("application:The application does not allow to sign up new account")}
          extra={[
            <Button type="primary" key="signin"
              onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
              {
                i18next.t("login:Sign In")
              }
            </Button>,
          ]}
        >
        </Result>
      );
    }

    if (this.state.userCode && this.state.userCodeStatus === "success") {
      return (
        <Result
          status="success"
          title={i18next.t("application:Logged in successfully")}
        >
        </Result>
      );
    }

    if (this.state.userCodeStatus === "canceled") {
      return (
        <Result
          status="warning"
          title={i18next.t("login:Device login was canceled")}
        >
        </Result>
      );
    }

    const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application) || Setting.isFaceIdEnabled(application);
    if (showForm) {
      let loginWidth = 320;
      if (Setting.getLanguage() === "fr") {
        loginWidth += 20;
      } else if (Setting.getLanguage() === "es") {
        loginWidth += 40;
      } else if (Setting.getLanguage() === "ru") {
        loginWidth += 10;
      }

      return (
        <Form
          name="normal_login"
          initialValues={{
            organization: application.organization,
            application: application.name,
            autoSignin: !application?.signinItems.map(signinItem => signinItem.name === "Forgot password?" && signinItem.rule === "Auto sign in - False")?.includes(true),
            username: this.state.prefilledUsername || (Conf.ShowGithubCorner ? "admin" : ""),
            password: Conf.ShowGithubCorner ? "123" : "",
          }}
          onFinish={(values) => {
            this.onFinish(values);
          }}
          style={{width: `${loginWidth}px`}}
          size="large"
          ref={this.form}
        >
          {this.renderDeviceLoginHeader(application)}
          <Form.Item
            hidden={true}
            name="application"
            rules={[
              {
                required: true,
                message: i18next.t("application:Please input your application!"),
              },
            ]}
          >
          </Form.Item>
          <Form.Item
            hidden={true}
            name="organization"
            rules={[
              {
                required: true,
                message: i18next.t("application:Please input your organization!"),
              },
            ]}
          >
          </Form.Item>

          {
            application.signinItems?.map(signinItem => this.renderFormItem(application, signinItem))
          }
        </Form>
      );
    } else {
      return (
        <div style={{marginTop: "20px"}}>
          <div style={{fontSize: 16, textAlign: "left"}}>
            {i18next.t("login:To access")}&nbsp;
            <a target="_blank" rel="noreferrer" href={application.homepageUrl}>
              <span style={{fontWeight: "bold"}}>
                {application.displayName}
              </span>
            </a>
            :
          </div>
          <br />
          {
            application?.signinItems.map(signinItem => signinItem.name === "Providers" || signinItem.name === "Signup link" ? this.renderFormItem(application, signinItem) : null)
          }
        </div>
      );
    }
  }

  renderCaptchaModal(application, noModal) {
    if (Setting.getCaptchaRule(this.getApplicationObj()) === Setting.CaptchaRule.Never) {
      return null;
    }
    const captchaProviderItems = Setting.getCaptchaProviderItems(application);
    const alwaysProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Always");
    const dynamicProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Dynamic");
    const internetOnlyProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Internet-Only");

    // Select provider based on the active captcha rule, not fixed priority
    const captchaRule = Setting.getCaptchaRule(this.getApplicationObj());
    let provider = null;

    if (captchaRule === Setting.CaptchaRule.Always && alwaysProviderItems.length > 0) {
      provider = alwaysProviderItems[0].provider;
    } else if (captchaRule === Setting.CaptchaRule.Dynamic && dynamicProviderItems.length > 0) {
      provider = dynamicProviderItems[0].provider;
    } else if (captchaRule === Setting.CaptchaRule.InternetOnly && internetOnlyProviderItems.length > 0) {
      provider = internetOnlyProviderItems[0].provider;
    }

    if (!provider) {
      return null;
    }

    return <CaptchaModal
      owner={provider.owner}
      name={provider.name}
      visible={this.state.openCaptchaModal}
      noModal={noModal}
      onUpdateToken={(captchaType, captchaToken, clientSecret) => {
        this.setState({
          captchaValues: {
            captchaType, captchaToken, clientSecret,
          },
        });
      }}
      onOk={(captchaType, captchaToken, clientSecret) => {
        const values = this.state.values;
        values["captchaType"] = captchaType;
        values["captchaToken"] = captchaToken;
        values["clientSecret"] = clientSecret;

        this.login(values);
        this.setState({openCaptchaModal: false});
      }}
      onCancel={() => this.setState({openCaptchaModal: false, loginLoading: false})}
      isCurrentProvider={true}
      innerRef={this.captchaRef}
    />;
  }

  renderFooter(application, signinItem) {
    return (
      <div>
        {
          !application.enableSignUp ? null : (
            signinItem.label ? Setting.renderSignupLink(application, signinItem.label) :
              (
                <React.Fragment>
                  {i18next.t("login:No account?")}&nbsp;
                  {
                    Setting.renderSignupLink(application, i18next.t("login:sign up now"))
                  }
                </React.Fragment>
              )
          )
        }
      </div>
    );
  }

  sendSilentSigninData(data) {
    if (Setting.inIframe()) {
      const message = {tag: "Casdoor", type: "SilentSignin", data: data};
      window.parent.postMessage(message, "*");
    }
  }

  showNativeSsoOverlay(messageText) {
    if (this.nativeSsoOverlayTimer !== null) {
      clearTimeout(this.nativeSsoOverlayTimer);
      this.nativeSsoOverlayTimer = null;
    }

    this.setState({nativeSsoOverlay: messageText});
    this.nativeSsoOverlayTimer = setTimeout(() => {
      this.setState({nativeSsoOverlay: ""});
      this.nativeSsoOverlayTimer = null;
    }, 2200);
  }

  handleNativeSsoFallback(messageText, agent) {
    this.setState({
      nativeSsoActive: false,
      nativeSsoSuppressed: true,
      nativeSsoFallbackVisible: true,
      nativeSsoKnownAgent: agent || this.state.nativeSsoKnownAgent,
    });

    if (messageText) {
      this.showNativeSsoOverlay(messageText);
    }
  }

  retryNativeSso() {
    this.setState((prevState) => ({
      nativeSsoSuppressed: false,
      nativeSsoFallbackVisible: true,
      nativeSsoRestartKey: prevState.nativeSsoRestartKey + 1,
    }));
  }

  handleNativeSsoSuccess(result) {
    const accessToken = result?.accessToken || result?.token?.access_token || "";
    if (accessToken === "") {
      this.handleNativeSsoFallback(i18next.t("login:Invalid Native SSO response"));
      return;
    }

    const oAuthParams = Util.getOAuthGetParameters();
    AuthBackend.completeNativeSso(accessToken, oAuthParams).then((res) => {
      if (res.status === "ok") {
        const responseType = oAuthParams?.responseType || "login";
        const responseTypes = responseType.split(" ");
        const responseMode = oAuthParams?.responseMode || "query";
        if (responseType === "login") {
          if (res.data3) {
            sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
            Setting.goToLinkSoft(this, "/account");
            return;
          }
          Setting.showMessage("success", i18next.t("application:Logged in successfully"));
          this.props.onLoginSuccess();
        } else if (responseType === "code") {
          this.postCodeLoginAction(res);
        } else if (responseType === "device") {
          Setting.showMessage("success", i18next.t("application:Logged in successfully"));
          this.setState({
            userCodeStatus: "success",
          });
        } else if (responseTypes.includes("token") || responseTypes.includes("id_token")) {
          if (res.data3) {
            sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
            Setting.goToLinkSoft(this, "/account");
            return;
          }
          const amendatoryResponseType = responseType === "token" ? "access_token" : responseType;
          const nativeSsoAccessToken = res.data;
          if (responseMode === "form_post") {
            const params = {
              token: responseTypes.includes("token") ? res.data : null,
              id_token: responseTypes.includes("id_token") ? res.data : null,
              token_type: "bearer",
              state: oAuthParams?.state,
            };
            createFormAndSubmit(oAuthParams?.redirectUri, params);
          } else {
            Setting.goToLink(`${oAuthParams.redirectUri}#${amendatoryResponseType}=${nativeSsoAccessToken}&state=${oAuthParams.state}&token_type=bearer`);
          }
        } else {
          this.handleNativeSsoFallback(i18next.t("login:Invalid Native SSO response"));
        }
      } else {
        this.handleNativeSsoFallback(res.msg || i18next.t("login:Native SSO was denied"));
      }
    }).catch((error) => {
      this.handleNativeSsoFallback(error.message || i18next.t("login:Native SSO was denied"));
    });
  }

  renderNativeSsoFallbackLink() {
    if (!this.state.nativeSsoFallbackVisible) {
      return null;
    }

    return (
      <div style={{marginTop: 20, textAlign: "center", fontSize: 16, lineHeight: "22px"}}>
        <a onClick={() => this.retryNativeSso()}>{i18next.t("login:Native SSO")}</a>
      </div>
    );
  }

  renderNativeSsoOverlay() {
    if (this.state.nativeSsoOverlay === "") {
      return null;
    }

    return (
      <div style={{
        position: "fixed",
        top: "50%",
        left: "50%",
        transform: "translate(-50%, -50%)",
        zIndex: 1000,
        width: 210,
        height: 205,
        padding: "0 28px",
        borderRadius: 10,
        background: "rgba(0, 0, 0, 0.68)",
        color: "#fff",
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        justifyContent: "center",
        textAlign: "center",
        fontSize: 20,
        fontWeight: 500,
        lineHeight: 1.25,
        pointerEvents: "none",
      }}>
        <div style={{fontSize: 58, lineHeight: 1, fontWeight: 200, marginBottom: 34}}>
          {"×"}
        </div>
        <div>{this.state.nativeSsoOverlay}</div>
      </div>
    );
  }

  shouldRenderNativeSso(application) {
    return this.state.mode === "signin" && this.state.type !== "device" && application?.clientId;
  }

  handleDeviceLoginComplete(deviceCode) {
    const oAuthParams = Util.getOAuthGetParameters();
    AuthBackend.completeDeviceLogin(deviceCode, oAuthParams).then((res) => {
      if (res.status === "ok") {
        const responseType = oAuthParams?.responseType ?? "login";
        const responseTypes = responseType.split(" ");
        const responseMode = oAuthParams?.responseMode || "query";
        if (responseType === "code") {
          this.postCodeLoginAction(res);
        } else if (responseTypes.includes("token") || responseTypes.includes("id_token")) {
          const amendatoryResponseType = responseType === "token" ? "access_token" : responseType;
          const accessToken = res.data;
          if (responseMode === "form_post") {
            const params = {
              token: responseTypes.includes("token") ? res.data : null,
              id_token: responseTypes.includes("id_token") ? res.data : null,
              token_type: "bearer",
              state: oAuthParams?.state,
            };
            createFormAndSubmit(oAuthParams?.redirectUri, params);
          } else {
            Setting.goToLink(`${oAuthParams.redirectUri}#${amendatoryResponseType}=${accessToken}&state=${oAuthParams.state}&token_type=bearer`);
          }
        } else {
          Setting.showMessage("success", i18next.t("application:Logged in successfully"));
          this.props.onLoginSuccess();
        }
      } else {
        Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
      }
    }).catch((err) => {
      Setting.showMessage("error", err.message);
    });
  }

  renderSignedInBox() {
    if (this.props.account === undefined || this.props.account === null) {
      this.sendSilentSigninData("user-not-logged-in");
      return null;
    }

    const application = this.getApplicationObj();
    if (this.props.account.owner !== application?.organization) {
      return null;
    }

    if (this.props.requiredEnableMfa) {
      return null;
    }

    if (this.state.userCode && this.state.userCodeStatus === "success") {
      return null;
    }

    return (
      <div>
        <div style={{fontSize: 16, textAlign: "left"}}>
          {this.state.type === "device" ? i18next.t("login:Continue with your current account to approve this sign-in") : i18next.t("login:Continue with")}&nbsp;:
        </div>
        <br />
        <div onClick={() => {
          const values = {};
          values["application"] = application.name;
          this.login(values);
        }}>
          <SelfLoginButton account={this.props.account} />
        </div>
        <br />
        <br />
        <div style={{fontSize: 16, textAlign: "left"}}>
          {i18next.t("login:Or sign in with another account")}&nbsp;:
        </div>
      </div>
    );
  }

  renderDeviceLoginHeader(application) {
    if (this.state.type !== "device" || !this.state.userCode) {
      return null;
    }

    return (
      <div style={{textAlign: "center", marginBottom: 16}}>
        <h3 style={{marginBottom: 8}}>{i18next.t("login:Approve sign-in on this device")}</h3>
        <div style={{marginBottom: 4}}>
          <strong>{application.displayName}</strong>
        </div>
        <div style={{color: "#8c8c8c"}}>
          {i18next.t("login:Confirmation code")}: {this.state.userCode}
        </div>
      </div>
    );
  }

  renderDeviceLoginSidePanel(application) {
    if (!application?.signinMethods?.some(signinMethod => signinMethod?.name === "Device login" && signinMethod.rule === "Login page") || this.state.type === "device") {
      return null;
    }

    return (
      <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
        <DeviceLoginPanel application={application} onSuccess={(deviceCode) => this.handleDeviceLoginComplete(deviceCode)} />
      </div>
    );
  }

  signInWithWebAuthn(username, values) {
    const oAuthParams = Util.getOAuthGetParameters();
    this.populateOauthValues(values);
    const application = this.getApplicationObj();
    const usernameParam = `&name=${encodeURIComponent(username)}`;
    return fetch(`${Setting.ServerUrl}/api/webauthn/signin/begin?owner=${application.organization}${username ? usernameParam : ""}`, {
      method: "GET",
      credentials: "include",
    })
      .then(res => res.json())
      .then((credentialRequestOptions) => {
        if ("status" in credentialRequestOptions) {
          return Promise.reject(new Error(credentialRequestOptions.msg));
        }
        credentialRequestOptions.publicKey.challenge = UserWebauthnBackend.webAuthnBufferDecode(credentialRequestOptions.publicKey.challenge);

        if (username) {
          credentialRequestOptions.publicKey.allowCredentials.forEach(function(listItem) {
            listItem.id = UserWebauthnBackend.webAuthnBufferDecode(listItem.id);
          });
        }

        return navigator.credentials.get({
          publicKey: credentialRequestOptions.publicKey,
        });
      })
      .then((assertion) => {
        const authData = assertion.response.authenticatorData;
        const clientDataJSON = assertion.response.clientDataJSON;
        const rawId = assertion.rawId;
        const sig = assertion.response.signature;
        const userHandle = assertion.response.userHandle;
        const resourceQuery = oAuthParams?.resource
          ? `&resource=${encodeURIComponent(oAuthParams.resource)}`
          : "";
        let finishUrl = `${Setting.ServerUrl}/api/webauthn/signin/finish?responseType=${values["type"]}`;
        if (values["type"] === "code") {
          finishUrl = `${Setting.ServerUrl}/api/webauthn/signin/finish?responseType=${values["type"]}&clientId=${oAuthParams.clientId}&scope=${oAuthParams.scope}&redirectUri=${oAuthParams.redirectUri}&nonce=${oAuthParams.nonce}&state=${oAuthParams.state}&codeChallenge=${oAuthParams.codeChallenge}&challengeMethod=${oAuthParams.challengeMethod}${resourceQuery}`;
        }
        return fetch(finishUrl, {
          method: "POST",
          credentials: "include",
          body: JSON.stringify({
            id: assertion.id,
            rawId: UserWebauthnBackend.webAuthnBufferEncode(rawId),
            type: assertion.type,
            response: {
              authenticatorData: UserWebauthnBackend.webAuthnBufferEncode(authData),
              clientDataJSON: UserWebauthnBackend.webAuthnBufferEncode(clientDataJSON),
              signature: UserWebauthnBackend.webAuthnBufferEncode(sig),
              userHandle: UserWebauthnBackend.webAuthnBufferEncode(userHandle),
            },
          }),
        })
          .then(res => res.json()).then((res) => {
            if (res.status === "ok") {
              const responseType = values["type"];
              if (responseType === "code") {
                this.postCodeLoginAction(res);
              } else if (responseType === "token" || responseType === "id_token") {
                const accessToken = res.data;
                Setting.goToLink(`${oAuthParams.redirectUri}#${responseType}=${accessToken}?state=${oAuthParams.state}&token_type=bearer`);
              } else {
                Setting.showMessage("success", i18next.t("login:Successfully logged in with WebAuthn credentials"));
                Setting.goToLink("/");
              }
            } else {
              Setting.showMessage("error", res.msg);
            }
          })
          .catch(error => {
            Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}${error}`);
          });
      }).catch(error => {
        Setting.showMessage("error", `${error.message}`);
      }).finally(() => {
        this.setState({
          loginLoading: false,
        });
      });
  }

  hasVerificationCodeSigninItem(application) {
    const targetApp = application || this.getApplicationObj();
    if (!targetApp || !targetApp.signinItems) {
      return false;
    }
    return targetApp.signinItems.some(item => item.name === "Verification code");
  }

  renderPasswordOrCodeInput(signinItem) {
    const application = this.getApplicationObj();
    if (this.state.loginMethod === "password" || this.state.loginMethod === "ldap") {
      return (
        <Col span={24}>
          <div>
            <Form.Item
              name="password"
              className="login-password"
              label={signinItem.label ? signinItem.label : null}
              rules={[{required: true, message: i18next.t("login:Please input your password!")}]}
            >
              <Input.Password
                className="login-password-input"
                prefix={<LockOutlined className="site-form-item-icon" />}
                type="password"
                placeholder={signinItem.placeholder ? signinItem.placeholder : i18next.t("general:Password")}
                disabled={this.state.loginMethod === "password" ? !Setting.isPasswordEnabled(application) : !Setting.isLdapEnabled(application)}
              />
            </Form.Item>
          </div>
        </Col>
      );
    } else if (this.state.loginMethod?.includes("verificationCode") && !this.hasVerificationCodeSigninItem(application)) {
      return (
        <Col span={24}>
          <div className="login-password">
            <Form.Item
              name="code"
              rules={[{required: true, message: i18next.t("login:Please input your code!")}]}
            >
              <SendCodeInput
                disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone}
                method={"login"}
                onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationName(application)]}
                application={application}
                captchaValue={this.state.captchaValues}
                useInlineCaptcha={Setting.isInlineCaptchaEnabled(application)}
                refreshCaptcha={this.refreshInlineCaptcha}
              />
            </Form.Item>
          </div>
        </Col>
      );
    } else {
      return null;
    }
  }

  renderCodeInput(signinItem) {
    const application = this.getApplicationObj();
    if (this.hasVerificationCodeSigninItem(application) && this.state.loginMethod?.includes("verificationCode")) {
      return (
        <Col span={24}>
          <Form.Item
            name="code"
            label={signinItem.label ? signinItem.label : null}
            rules={[{required: true, message: i18next.t("login:Please input your code!")}]}
            className="verification-code"
          >
            <SendCodeInput
              disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone}
              method={"login"}
              onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationName(application)]}
              application={application}
              captchaValue={this.state.captchaValues}
              useInlineCaptcha={Setting.isInlineCaptchaEnabled(application)}
              refreshCaptcha={this.refreshInlineCaptcha}
            />
          </Form.Item>
        </Col>
      );
    } else {
      return null;
    }
  }

  renderMethodChoiceBox() {
    const application = this.getApplicationObj();
    const items = [];

    const generateItemKey = (name, rule) => {
      return `${name}-${rule}`;
    };

    const itemsMap = new Map([
      [generateItemKey("Password", "All"), {label: i18next.t("general:Password"), key: "password"}],
      [generateItemKey("Password", "Non-LDAP"), {label: i18next.t("general:Password"), key: "password"}],
      [generateItemKey("Verification code", "All"), {label: i18next.t("login:Verification code"), key: "verificationCode"}],
      [generateItemKey("Verification code", "Email only"), {label: i18next.t("login:Verification code"), key: "verificationCodeEmail"}],
      [generateItemKey("Verification code", "Phone only"), {label: i18next.t("login:Verification code"), key: "verificationCodePhone"}],
      [generateItemKey("WebAuthn", "None"), {label: i18next.t("login:WebAuthn"), key: "webAuthn"}],
      [generateItemKey("LDAP", "None"), {label: i18next.t("login:LDAP"), key: "ldap"}],
      [generateItemKey("Face ID", "None"), {label: i18next.t("login:Face ID"), key: "faceId"}],
      [generateItemKey("Device login", "Tab"), {label: i18next.t("login:Device login"), key: "device"}],
      [generateItemKey("WeChat", "Tab"), {label: i18next.t("login:WeChat"), key: "wechat"}],
      [generateItemKey("WeChat", "None"), {label: i18next.t("login:WeChat"), key: "wechat"}],
    ]);

    application?.signinMethods?.forEach((signinMethod) => {
      if (signinMethod.rule === "Hide password") {
        return;
      }

      if (this.state.type === "device" && signinMethod.name === "Device login") {
        return;
      }

      const item = itemsMap.get(generateItemKey(signinMethod.name, signinMethod.rule));
      if (item) {
        let label = signinMethod.name === signinMethod.displayName ? item.label : signinMethod.displayName;

        if (application?.signinMethods?.length >= 4 && label === "Verification code") {
          label = "Code";
        }

        items.push({label: label, key: item.key});
      }
    });

    if (items.length > 1) {
      return (
        <div>
          <Tabs className="signin-methods" items={items} size={"small"} activeKey={this.state.loginMethod} onChange={(key) => {
            this.setState({loginMethod: key});
          }} centered>
          </Tabs>
        </div>
      );
    }
  }

  renderLoginPanel(application) {
    const orgChoiceMode = application.orgChoiceMode;

    if (this.isOrganizationChoiceBoxVisible(orgChoiceMode)) {
      return this.renderOrganizationChoiceBox(orgChoiceMode);
    }

    if (this.state.getVerifyTotp !== undefined) {
      return this.state.getVerifyTotp();
    } else {
      return (
        <React.Fragment>
          {this.renderSignedInBox()}
          {this.renderForm(application)}
        </React.Fragment>
      );
    }
  }

  renderOrganizationChoiceBox(orgChoiceMode) {
    const renderChoiceBox = () => {
      switch (orgChoiceMode) {
      case "None":
        return null;
      case "Select":
        return (
          <div>
            <p style={{fontSize: "large"}}>
              {i18next.t("login:Please select an organization to sign in")}
            </p>
            <OrganizationSelect style={{width: "70%"}}
              onSelect={(value) => {
                Setting.goToLink(`/login/${value}?orgChoiceMode=None`);
              }} />
          </div>
        );
      case "Input":
        return (
          <div>
            <p style={{fontSize: "large"}}>
              {i18next.t("login:Please type an organization to sign in")}
            </p>
            <Form
              name="basic"
              onFinish={(values) => {Setting.goToLink(`/login/${values.organizationName}?orgChoiceMode=None`);}}
            >
              <Form.Item
                name="organizationName"
                rules={[{required: true, message: i18next.t("login:Please input your organization name!")}]}
              >
                <Input style={{width: "70%"}} onPressEnter={(e) => {
                  Setting.goToLink(`/login/${e.target.value}?orgChoiceMode=None`);
                }} />
              </Form.Item>
              <Button type="primary" htmlType="submit">
                {i18next.t("general:Confirm")}
              </Button>
            </Form>
          </div>
        );
      default:
        return null;
      }
    };

    return (
      <div style={{height: 300, minWidth: 320}}>
        {renderChoiceBox()}
      </div>
    );
  }

  isOrganizationChoiceBoxVisible(orgChoiceMode) {
    if (this.state.orgChoiceMode === "None") {
      return false;
    }

    const path = this.props.match?.path;
    if (path === "/login" || path === "/login/:owner") {
      return orgChoiceMode === "Select" || orgChoiceMode === "Input";
    }

    return false;
  }

  renderBackButton() {
    if (this.state.orgChoiceMode === "None" || this.props.preview === "auto") {
      return (
        <Button className="back-inner-button" type="text" size="large" icon={<ArrowLeftOutlined />}
          onClick={() => history.back()}>
        </Button>
      );
    }
  }

  render() {
    if (this.state.userCodeStatus === "expired") {
      return <Result
        style={{width: "100%"}}
        status="error"
        title={`Code ${i18next.t("subscription:Expired")}`}
      >
      </Result>;
    }

    const application = this.getApplicationObj();
    if (application === undefined) {
      return null;
    }
    if (application === null) {
      return Util.renderMessageLarge(this, this.state.msg);
    }

    if (this.state.samlResponse !== "") {
      return <RedirectForm samlResponse={this.state.samlResponse} redirectUrl={this.state.redirectUrl} relayState={this.state.relayState} />;
    }

    if (application.signinHtml !== "") {
      return (
        <div dangerouslySetInnerHTML={{__html: application.signinHtml}} />
      );
    }

    const visibleOAuthProviderItems = (application.providers === null) ? [] : application.providers.filter(providerItem => this.isProviderVisible(providerItem) && providerItem.provider?.category !== "SAML");
    if (this.props.preview !== "auto" && !Setting.isPasswordEnabled(application) && !Setting.isCodeSigninEnabled(application) && !Setting.isWebAuthnEnabled(application) && !Setting.isLdapEnabled(application) && visibleOAuthProviderItems.length === 1) {
      Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, this.state.mode ?? "signup"));
      return (
        <Loading type="page" tip={i18next.t("login:Signing in...")} />
      );
    }

    const wechatSigninMethods = application.signinMethods?.filter(method => method.name === "WeChat" && method.rule === "Login page");
    const sidePanels = [];

    if (wechatSigninMethods?.length > 0) {
      sidePanels.push(
        <div key="wechat-panel">
          <h3 style={{textAlign: "center", width: 320}}>{i18next.t("provider:Please use WeChat to scan the QR code and follow the official account for sign in")}</h3>
          <WeChatLoginPanel application={application} loginMethod={this.state.loginMethod} />
        </div>
      );
    }

    if (application.signinMethods?.some(method => method.name === "Device login" && method.rule === "Login page") && this.state.type !== "device") {
      sidePanels.push(
        <div key="device-panel">
          {this.renderDeviceLoginSidePanel(application)}
        </div>
      );
    }

    const showNativeSso = this.shouldRenderNativeSso(application);

    return (
      <React.Fragment>
        <CustomGithubCorner />
        <div className="login-content" style={{margin: this.props.preview ?? this.parseOffset(application.formOffset)}}>
          {this.renderNativeSsoOverlay()}
          {Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
          {Setting.inIframe() || !Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCssMobile}} />}
          <div className={Setting.isDarkTheme(this.props.themeAlgorithm) ? "login-panel-dark" : "login-panel"}>
            <div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
              <div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />
            </div>
            <div className="login-form">
              <div>
                {showNativeSso && !this.state.nativeSsoSuppressed ? (
                  <NativeSsoPanel
                    key={`native-sso-${this.state.nativeSsoRestartKey}`}
                    application={application}
                    restartKey={this.state.nativeSsoRestartKey}
                    initialAgent={this.state.nativeSsoKnownAgent}
                    onActiveChange={(active) => this.setState({nativeSsoActive: active})}
                    onFallback={(messageText, agent) => this.handleNativeSsoFallback(messageText, agent)}
                    onSuccess={(result) => this.handleNativeSsoSuccess(result)}
                  />
                ) : null}
                {showNativeSso && this.state.nativeSsoActive ? null : this.renderLoginPanel(application)}
              </div>
            </div>
            {sidePanels.length > 0 && !(showNativeSso && this.state.nativeSsoActive) ? (
              <div style={{display: "flex", justifyContent: "center", alignItems: "center", flexDirection: "column", gap: 24}}>
                {sidePanels}
              </div>
            ) : null}
          </div>
        </div>
      </React.Fragment>
    );
  }
}

export default withRouter(LoginPage);
