<template>
  <v-fade-transition>
    <v-container fluid class="mt-40" v-if="step == steps.CAPTCHA">
      <div class="ma-3" flat ref="captcha" id="captcha">
      </div>
    </v-container>

    <v-container fluid class="mt-40" v-if="step == steps.ENTER_EMAIL">
      <v-row cols="12" align="center" justify="center">
        <v-col sm="8" align="center">
          <div class="text-left font-weight-light">{{ $t("login.email") }}</div>
          <v-text-field v-model="email" color="black" :rules="emailRules" type="email" maxlength="200"
            background-color="white" v-on:keyup.enter="onEmailEntered(email)" autofocus solo></v-text-field>
          <v-btn :disabled="!emailIsValid" color="black" depressed class="filled-button"
            @click.stop="onEmailEntered(email)">
            {{ $t("login.send_verification") }}
          </v-btn>
        </v-col>
      </v-row>
    </v-container>

    <v-container fluid class="mt-40" v-if="step == steps.TERMS">
      <v-row cols="12" align="center" justify="center">
        <v-col sm="8" align="center">
          <div class="text-left font-weight-light">{{ $t("login.terms") }}</div>
        </v-col>
      </v-row>
      <v-row cols="12" align="center" justify="center">
        <v-col sm="8" align="center">
          <div v-for="(policy) in this.policies" :key="policy.id">
            <v-checkbox class="mt-0" v-model="policy.accepted">
              <template v-slot:label>
                <a target="_blank" :href="policy.url" @click.stop>{{ policy.name }}
                </a>
              </template>
            </v-checkbox>
          </div>
        </v-col>
      </v-row>
      <v-row cols="12" align="center" justify="center">
        <v-col sm="8" align="center">
          <v-btn color="black" :disabled="!this.allPoliciesAccepted" depressed class="filled-button"
            @click.stop="onPoliciesAccepted()">
            {{ $t("login.accept_terms") }}
          </v-btn>
        </v-col>
      </v-row>
    </v-container>

    <v-container fluid class="mt-40" v-if="step == steps.AWAITING_EMAIL_VERIFICATION">
      <v-row cols="12" align="center" justify="center">
        <v-col sm="8" align="center">
          <div>
            <div class="text-left font-weight-light">{{ $t("login.sent_verification", { email: this.email }) }}</div>
            <v-progress-circular style="display: inline-flex" indeterminate color="primary"
              size="20"></v-progress-circular>
          </div>
          <v-btn color="black" depressed class="filled-button" @click.stop="onEmailResend()">
            {{ $t("login.resend_verification") }}
          </v-btn>
        </v-col>
      </v-row>
    </v-container>

    <v-container fluid class="mt-40" v-if="step == steps.ENTER_TOKEN">
      <v-row cols="12" align="center" justify="center">
        <v-col sm="8" align="center">
          <div class="text-left font-weight-light">{{ $t("login.registration_token") }}</div>
          <v-text-field v-model="token" color="black" :rules="tokenRules" type="text" maxlength="64"
            background-color="white" v-on:keyup.enter="onTokenEntered(token)" autofocus solo></v-text-field>
          <v-btn :disabled="!tokenIsValid" color="black" depressed class="filled-button"
            @click.stop="onTokenEntered(token)">
            {{ $t("login.send_token") }}
          </v-btn>
        </v-col>
      </v-row>
    </v-container>

  </v-fade-transition>
</template>

<script>
import util from "../plugins/utils";

const steps = Object.freeze({
  INITIAL: 0,
  CAPTCHA: 1,
  TERMS: 2,
  EXTERNAL_AUTH: 3,
  ENTER_EMAIL: 4,
  AWAITING_EMAIL_VERIFICATION: 5,
  ENTER_TOKEN: 6,
});

export default {
  name: "InteractiveAuth",
  data() {
    return {
      steps,
      step: steps.INITIAL,
      emailRules: [
        v => this.validEmail(v) || this.$t("login.email_not_valid")
      ],
      tokenRules: [
        v => this.validToken(v) || this.$t("login.token_not_valid")
      ],
      policies: null,
      onPoliciesAccepted: () => { },
      onEmailResend: () => { },
      onEmailVerify: () => { },
      email: "",
      emailVerification: "",
      emailVerificationSecret: util.randomUser("tokn"),
      emailVerificationAttempt: 1,
      emailVerificationSid: null,
      emailVerificationPollTimer: null,
      token: "",
    };
  },

  computed: {
    allPoliciesAccepted() {
      return Object.keys(this.policies).every(id => this.policies[id].accepted);
    },
    emailIsValid() {
      return this.validEmail(this.email);
    },
    tokenIsValid() {
      return this.validToken(this.token);
    }
  },

  methods: {
    validEmail(email) {
      if (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(.\w{2,3})+$/.test(email)) {
        return true;
      } else {
        return false;
      }
    },

    validToken(token) {
      // https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/3231-token-authenticated-registration.md
      if (/^[A-Za-z0-9._~-]{1,64}$/.test(token)) {
        return true;
      } else {
        return false;
      }
    },

    registrationFlowHandler(client, authData) {
      const flows = authData.flows;
      if (!flows || flows.length == 0) {
        return Promise.reject(this.$t('login.no_supported_flow'));
      }
      const currentFlow = flows[0];
      if (!currentFlow || !currentFlow.stages || currentFlow.stages.length == 0) {
        return Promise.reject(this.$t('login.no_supported_flow'));
      }

      // TODO - For now hardcoded to flows[0]
      const completedStages = authData.completed || [];
      const remainingStages = currentFlow.stages.filter((stage) => {
        return !completedStages.includes(stage);
      });

      const nextStage = remainingStages[0];

      const submitStageData = (resolve, reject, data) => {
        this.step = steps.CREATING;
        resolve(client.registerRequest({ auth: data }).catch(err => {
          if (err.httpStatus == 401 && err.data) {
            // Start next stage!
            return this.registrationFlowHandler(client, err.data);
          } else {
            reject(err);
          }
        }
        ));
      };

      switch (nextStage) {
        case "m.login.recaptcha":
          this.step = steps.CAPTCHA;
          return new Promise((resolve, reject) => {
            let script = document.createElement("script");
            script.src = "https://www.google.com/recaptcha/api.js?onload=onReCaptchaLoaded&render=explicit";
            window.onReCaptchaLoaded = (ignoredev) => {
              const params = authData.params[nextStage];
              if (!params || !params.public_key) {
                return reject(this.$t('login.no_supported_flow'));
              }
              const site_key = params.public_key;
              window.grecaptcha.render('captcha', {
                'sitekey': site_key,
                'theme': 'light',
                callback: (captcha_response) => {
                  const data = {
                    session: authData.session,
                    type: nextStage,
                    response: captcha_response
                  };
                  submitStageData(resolve, reject, data);
                }
              });
            };
            document.body.append(script);
          });
        case "m.login.terms":
          this.step = steps.TERMS;
          return new Promise((resolve, reject) => {
            const params = authData.params[nextStage];
            if (!params || !params.policies) {
              return reject(this.$t('login.no_supported_flow'));
            }

            let policies = [];
            Object.keys(params.policies).forEach((policyId) => {
              const policy = params.policies[policyId];
              let localized = policy[this.$i18n.locale] || policy["en"] || policy[Object.keys(policy).filter(k => k != "version")[0]];
              if (!localized) {
                // Did not find info for this policy!
                return reject(this.$t('login.no_supported_flow'));
              }
              policies.push({
                id: policyId,
                name: localized.name,
                url: localized.url,
                accepted: false
              });
            });
            this.onPoliciesAccepted = () => {
              // Button should not be enabled if not all are accepted, but double check here...
              if (this.allPoliciesAccepted) {
                const data = {
                  session: authData.session,
                  type: nextStage,
                };
                submitStageData(resolve, reject, data);
              }
            }
            this.policies = policies;
          });

        case "m.login.email.identity":
          this.step = steps.ENTER_EMAIL;
          return new Promise((resolve, reject) => {
            this.onEmailEntered = (email) => {
              this.step = steps.CREATING;
              this.emailVerificationAttempt = 1;
              client.requestRegisterEmailToken(email, this.emailVerificationSecret, this.emailVerificationAttempt, null)
                .then(response => {
                  this.emailVerificationSid = response.sid;
                  this.step = steps.AWAITING_EMAIL_VERIFICATION;
                  this.onEmailResend = () => {
                    this.emailVerificationAttempt += 1;
                    client.requestRegisterEmailToken(email, this.emailVerificationSecret, this.emailVerificationAttempt, null)
                      .then(response => {
                        this.emailVerificationSid = response.sid; // Update the SID (TODO: will it really change?)
                      })
                      .catch(ignorederr => { });
                  },
                    this.onEmailVerify = async () => {
                      const data = {
                        type: nextStage,
                        threepid_creds: {
                          sid: this.emailVerificationSid,
                          client_secret: this.emailVerificationSecret
                        },
                        session: authData.session,
                      };
                      console.log("Polling...");
                      await client.registerRequest({ auth: data })
                        .then((result) => {
                          // Yes, email is verified!
                          clearTimeout(this.emailVerificationPollTimer);
                          this.emailVerificationPollTimer = null;
                          resolve(result);
                        })
                        .catch(ignorederr => {
                          // Continue polling
                        });
                    };
                  this.emailVerificationPollTimer = setInterval(this.onEmailVerify, 5000);
                })
                .catch((ignorederr) => {
                  console.error("ERROR", ignorederr);
                  return reject(this.$t('login.no_supported_flow'))
                });
            }
          });

        case "m.login.registration_token": {
          this.step = steps.CREATING;
          return new Promise((resolve, reject) => {
            if (this.$config.registrationToken) {
              // We have a token in config, use that!
              //
              const data = {
                session: authData.session,
                type: nextStage,
                 token: this.$config.registrationToken
              };
              submitStageData(resolve, reject, data);
            } else {
              this.step = steps.ENTER_TOKEN;
              this.onTokenEntered = (token) => {
                this.step = steps.CREATING;
                const data = {
                  session: authData.session,
                  type: nextStage,
                  token: token
                };
                submitStageData(resolve, reject, data);
              };
            }            
            });
          }
          
        default:
          this.step = steps.EXTERNAL_AUTH;
          return new Promise((resolve, reject) => {
            let fallbackUrl = client.getFallbackAuthUrl(nextStage, authData.session);
            var popupWindow;

            var eventListener = function (ev) {
              // check it's the right message from the right place.
              if (ev.data !== "authDone" || !fallbackUrl.startsWith(ev.origin)) {
                return;
              }

              // close the popup
              popupWindow.close();
              window.removeEventListener("message", eventListener);

              const data = {
                session: authData.session,
              };
              submitStageData(resolve, reject, data);
            };

            window.addEventListener("message", eventListener);
            popupWindow = window.open(fallbackUrl, "_blank", "popup=true");
          });
      }
    }
  }
};
</script>

<style lang="scss">
@import "@/assets/css/chat.scss";
</style>
