<script setup lang="ts">
import {
  ref,
  toRefs,
  computed,
  useAttrs,
  onMounted,
  onUnmounted,
  withDefaults,
} from 'vue';

import {
  AuthFormFields,
  AuthenticatorMachineOptions,
  AuthenticatorRoute,
  SocialProvider,
  authenticatorTextUtil,
  configureComponent,
} from '@aws-amplify/ui';

import { useAuth, useAuthenticator } from '../composables/useAuth';
import { UseAuthenticator } from '../types';
import { VERSION } from '../version';

import SignIn from './sign-in.vue';
import SignUp from './sign-up.vue';
import ConfirmSignUp from './confirm-sign-up.vue';
import ConfirmSignIn from './confirm-sign-in.vue';
import SetupTotp from './setup-totp.vue';
import ForceNewPassword from './force-new-password.vue';
import ResetPassword from './reset-password.vue';
import ConfirmResetPassword from './confirm-reset-password.vue';
import VerifyUser from './verify-user.vue';
import ConfirmVerifyUser from './confirm-verify-user.vue';

interface AuthenticatorProps {
  hideSignUp?: boolean;
  initialState?: AuthenticatorMachineOptions['initialState'];
  loginMechanisms?: AuthenticatorMachineOptions['loginMechanisms'];
  services?: AuthenticatorMachineOptions['services'];
  signUpAttributes?: AuthenticatorMachineOptions['signUpAttributes'];
  variation?: 'default' | 'modal';
  socialProviders?: SocialProvider[];
  formFields?: AuthFormFields;
}

const attrs = useAttrs();

const props = withDefaults(defineProps<AuthenticatorProps>(), {
  variation: 'default',
});

const {
  initialState,
  loginMechanisms,
  variation,
  services,
  signUpAttributes,
  socialProviders,
  hideSignUp,
  formFields,
} = toRefs(props);

/** @deprecated Component events are deprecated and not maintained. */
const emit = defineEmits([
  'signInSubmit',
  'confirmSignUpSubmit',
  'resetPasswordSubmit',
  'confirmResetPasswordSubmit',
  'confirmSignInSubmit',
  'mSetupTOTPSubmit',
  'forceNewPasswordSubmit',
  'signUpSubmit',
  'verifyUserSubmit',
  'confirmVerifyUserSubmit',
]);

let unsubscribeMachine: () => void;

const hasInitialized = ref(false);

const { service, send, state } = useAuth();

/**
 * Subscribes to state machine changes and sends INIT event
 * once machine reaches 'setup' state.
 */
unsubscribeMachine = service.subscribe((newState) => {
  if (newState.matches('setup') && !hasInitialized.value) {
    send({
      type: 'INIT',
      data: {
        /**
         * There's a type inference bug with prop refs that incorrectly assume
         * they can be undefined. Adding `!` until this is resolved from Vue's end.
         *
         * https://github.com/vuejs/core/issues/6420
         */
        initialState: initialState!.value,
        loginMechanisms: loginMechanisms!.value,
        socialProviders: socialProviders!.value,
        signUpAttributes: signUpAttributes!.value,
        services: services!.value,
        formFields: formFields!.value,
      },
    });
    hasInitialized.value = true;
  }
}).unsubscribe;

// `facade` is manually typed to `UseAuthenticator` for temporary type safety.
const facade: UseAuthenticator = useAuthenticator();
const { route, signOut, toSignIn, toSignUp, user } = toRefs(facade);

onMounted(() => {
  configureComponent({
    packageName: '@aws-amplify/ui-vue',
    version: VERSION,
  });
});

onUnmounted(() => {
  unsubscribeMachine();
});

const signInComponent = ref();
const signUpComponent = ref();
const confirmSignUpComponent = ref();
const confirmSignInComponent = ref();
const confirmSetupTOTPComponent = ref();
const forceNewPasswordComponent = ref();
const resetPasswordComponent = ref();
const confirmResetPasswordComponent = ref();
const verifyUserComponent = ref();
const confirmVerifyUserComponent = ref();

// text util
const { getSignInTabText, getSignUpTabText } = authenticatorTextUtil;

// computed
const signInLabel = computed(() => getSignInTabText());
const createAccountLabel = computed(() => getSignUpTabText());

// methods
const onSignInSubmitI = (e: Event) => {
  // TODO(BREAKING): remove unused emit
  // istanbul ignore next
  if (attrs?.onSignInSubmit) {
    emit('signInSubmit', e);
  } else {
    // TODO(BREAKING): remove unused event binding
    // istanbul ignore next
    signInComponent.value?.submit(e);
  }
};

const onConfirmSignUpSubmitI = (e: Event) => {
  // TODO(BREAKING): remove unused emit
  // istanbul ignore next
  if (attrs?.onConfirmSignUpSubmit) {
    emit('confirmSignUpSubmit', e);
  } else {
    // TODO(BREAKING): remove unused event binding
    // istanbul ignore next
    confirmSignUpComponent.value.submit(e);
  }
};

const onResetPasswordSubmitI = (e: Event) => {
  // TODO(BREAKING): remove unused emit
  // istanbul ignore next
  if (attrs?.onResetPasswordSubmit) {
    emit('resetPasswordSubmit', e);
  } else {
    // TODO(BREAKING): remove unused event binding
    // istanbul ignore next
    resetPasswordComponent.value.submit(e);
  }
};

const onConfirmResetPasswordSubmitI = (e: Event) => {
  // TODO(BREAKING): remove unused emit
  // istanbul ignore next
  if (attrs?.onConfirmResetPasswordSubmit) {
    emit('confirmResetPasswordSubmit', e);
  } else {
    // TODO(BREAKING): remove unused event binding
    // istanbul ignore next
    confirmResetPasswordComponent.value.submit(e);
  }
};

const onConfirmSignInSubmitI = (e: Event) => {
  // TODO(BREAKING): remove unused emit
  // istanbul ignore next
  if (attrs?.onConfirmSignInSubmit) {
    emit('confirmSignInSubmit', e);
  } else {
    // TODO(BREAKING): remove unused event binding
    // istanbul ignore next
    confirmSignInComponent.value.submit(e);
  }
};

const onConfirmSetupTOTPSubmitI = (e: Event) => {
  // TODO(BREAKING): remove unused emit
  // istanbul ignore next
  if (attrs?.onForceNewPasswordSubmit) {
    emit('mSetupTOTPSubmit', e);
  } else {
    // TODO(BREAKING): remove unused event binding
    // istanbul ignore next
    confirmSetupTOTPComponent.value.submit(e);
  }
};

const onForceNewPasswordSubmitI = (e: Event) => {
  // TODO(BREAKING): remove unused emit
  // istanbul ignore next
  if (attrs?.onForceNewPasswordSubmit) {
    emit('forceNewPasswordSubmit', e);
  } else {
    // TODO(BREAKING): remove unused event binding
    // istanbul ignore next
    forceNewPasswordComponent.value.submit(e);
  }
};

const onSignUpSubmitI = (e: Event) => {
  // TODO(BREAKING): remove unused emit
  // istanbul ignore next
  if (attrs?.onSignUpSubmit) {
    emit('signUpSubmit', e);
  } else {
    // TODO(BREAKING): remove unused event binding
    // istanbul ignore next
    signUpComponent.value.submit();
  }
};

const onVerifyUserSubmitI = (e: Event) => {
  // TODO(BREAKING): remove unused emit
  // istanbul ignore next
  if (attrs?.onVerifyUserSubmit) {
    emit('verifyUserSubmit', e);
  } else {
    // TODO(BREAKING): remove unused event binding
    // istanbul ignore next
    verifyUserComponent.value.submit(e);
  }
};

const onConfirmVerifyUserSubmitI = (e: Event) => {
  // TODO(BREAKING): remove unused emit
  // istanbul ignore next
  if (attrs?.onConfirmVerifyUserSubmit) {
    emit('confirmVerifyUserSubmit', e);
  } else {
    // TODO(BREAKING): remove unused event binding
    // istanbul ignore next
    confirmVerifyUserComponent.value.submit(e);
  }
};

const hasTabs = computed(() => {
  return route.value === 'signIn' || route.value === 'signUp';
});

const hasRouteComponent = computed(() => {
  const routesWithComponent: AuthenticatorRoute[] = [
    'confirmResetPassword',
    'confirmSignIn',
    'confirmSignUp',
    'confirmVerifyUser',
    'forceNewPassword',
    'resetPassword',
    'setupTOTP',
    'signIn',
    'signUp',
    'verifyUser',
  ];
  return routesWithComponent.includes(route.value);
});
</script>

<template>
  <div
    v-bind="$attrs"
    data-amplify-authenticator
    :data-variation="variation"
    v-if="hasRouteComponent"
  >
    <div data-amplify-container>
      <slot name="header"> </slot>
      <div
        data-amplify-router
        :data-amplify-router-content="hasTabs ? undefined : ''"
      >
        <base-two-tabs v-if="hasTabs && !hideSignUp">
          <base-two-tab-item
            :active="route === 'signIn'"
            :id="44472"
            :label="signInLabel"
            @click="toSignIn"
          />
          <base-two-tab-item
            :active="route === 'signUp'"
            :id="44471"
            :label="createAccountLabel"
            @click="toSignUp"
          />
        </base-two-tabs>
        <div v-if="hasTabs" data-amplify-router-content>
          <sign-in
            v-if="route === 'signIn'"
            @sign-in-submit="onSignInSubmitI"
            ref="signInComponent"
          >
            <template #signInSlotI>
              <slot name="sign-in"></slot>
            </template>

            <template
              #form="{ info, onSignInSubmit, onForgotPasswordClicked, onInput }"
            >
              <slot
                name="sign-in-form"
                :info="info"
                :onInput="onInput"
                :onSignInSubmit="onSignInSubmit"
                :onForgotPasswordClicked="onForgotPasswordClicked"
              ></slot>
            </template>

            <template #header>
              <slot name="sign-in-header"></slot>
            </template>

            <template #footer>
              <slot name="sign-in-footer"> </slot>
            </template>
          </sign-in>
          <sign-up
            v-if="route === 'signUp' && !hideSignUp"
            @sign-up-submit="onSignUpSubmitI"
            ref="signUpComponent"
          >
            <template #signUpSlotI>
              <slot name="sign-up"></slot>
            </template>
            <template #header>
              <slot name="sign-up-header"></slot>
            </template>
            <template #signup-fields="{ info }">
              <slot name="sign-up-fields" :info="info"></slot>
            </template>

            <template #footer>
              <slot name="sign-up-footer"> </slot>
            </template>
          </sign-up>
        </div>

        <confirm-sign-up
          v-if="route === 'confirmSignUp'"
          @confirm-sign-up-submit="onConfirmSignUpSubmitI"
          ref="confirmSignUpComponent"
        >
          <template #confirmSignUpSlotI>
            <slot name="confirm-sign-up"></slot>
          </template>
          <template #header>
            <slot name="confirm-sign-up-header"></slot>
          </template>
          <template #footer="{ onConfirmSignUpSubmit, onLostCodeClicked }">
            <slot
              name="confirm-sign-up-footer"
              :onConfirmSignUpSubmit="onConfirmSignUpSubmit"
              :onLostCodeClicked="onLostCodeClicked"
            >
            </slot>
          </template>
        </confirm-sign-up>

        <reset-password
          v-if="route === 'resetPassword'"
          @reset-password-submit="onResetPasswordSubmitI"
          ref="resetPasswordComponent"
        >
          <template #resetPasswordSlotI>
            <slot name="reset-password"></slot>
          </template>
          <template #header>
            <slot name="reset-password-header"></slot>
          </template>
          <template #footer="{ onResetPasswordSubmit, onBackToSignInClicked }">
            <slot
              name="reset-password-footer"
              :onResetPasswordSubmit="onResetPasswordSubmit"
              :onBackToSignInClicked="onBackToSignInClicked"
            >
            </slot>
          </template>
        </reset-password>

        <confirm-reset-password
          v-if="route === 'confirmResetPassword'"
          @confirm-reset-password-submit="onConfirmResetPasswordSubmitI"
          ref="confirmResetPasswordComponent"
        >
          <template #confirmResetPasswordSlotI>
            <slot name="confirm-reset-password"></slot>
          </template>
          <template #header>
            <slot name="confirm-reset-password-header"></slot>
          </template>
          <template
            #footer="{ onConfirmResetPasswordSubmit, onLostYourCodeClicked }"
          >
            <slot
              name="confirm-reset-password-footer"
              :onConfirmResetPasswordSubmit="onConfirmResetPasswordSubmit"
              :onLostYourCodeClicked="onLostYourCodeClicked"
            >
            </slot>
          </template>
        </confirm-reset-password>

        <confirm-sign-in
          v-if="route === 'confirmSignIn'"
          @confirm-sign-in-submit="onConfirmSignInSubmitI"
          ref="confirmSignInComponent"
        >
          <template #confirmSignInSlotI>
            <slot name="confirm-sign-in"></slot>
          </template>
          <template #header>
            <slot name="confirm-sign-in-header"></slot>
          </template>
          <template #footer="{ onConfirmSignInSubmit, onBackToSignInClicked }">
            <slot
              name="confirm-sign-in-footer"
              :onConfirmSignInSubmit="onConfirmSignInSubmit"
              :onBackToSignInClicked="onBackToSignInClicked"
            >
            </slot>
          </template>
        </confirm-sign-in>

        <setup-totp
          v-if="route === 'setupTOTP'"
          @confirm-setup-totp-submit="onConfirmSetupTOTPSubmitI"
          ref="confirmSetupTOTPComponent"
        >
          <template #confirmSetupTOTPI>
            <slot name="setup-totp"></slot>
          </template>
          <template #header>
            <slot name="setup-totp-header"></slot>
          </template>
          <template #footer="{ onSetupTOTPSubmit, onBackToSignInClicked }">
            <slot
              name="setup-totp-footer"
              :onSetupTOTPSubmit="onSetupTOTPSubmit"
              :onBackToSignInClicked="onBackToSignInClicked"
            >
            </slot>
          </template>
        </setup-totp>

        <force-new-password
          v-if="route === 'forceNewPassword'"
          @force-new-password-submit="onForceNewPasswordSubmitI"
          ref="forceNewPasswordComponent"
        >
          <template #forceNewPasswordI>
            <slot name="force-new-password"></slot>
          </template>
          <template #header>
            <slot name="force-new-password-header"></slot>
          </template>
          <template #force-new-password-form-fields>
            <slot name="force-new-password-form-fields"></slot>
          </template>
          <template
            #footer="{ onHaveAccountClicked, onForceNewPasswordSubmit }"
          >
            <slot
              name="force-new-password-footer"
              :onForceNewPasswordSubmit="onForceNewPasswordSubmit"
              :onBackToSignInClicked="onHaveAccountClicked"
            >
            </slot>
          </template>
        </force-new-password>

        <verify-user
          v-if="route === 'verifyUser'"
          @verify-user-submit="onVerifyUserSubmitI"
          ref="verifyUserComponent"
        >
          <template #verifyUserSlotI>
            <slot name="verify-user"></slot>
          </template>
          <template #header>
            <slot name="verify-user-header"></slot>
          </template>
          <template #footer="{ onVerifyUserSubmit, onSkipClicked }">
            <slot
              name="verify-user-footer"
              :onVerifyUserSubmit="onVerifyUserSubmit"
              :onSkipClicked="onSkipClicked"
            >
            </slot>
          </template>
        </verify-user>

        <confirm-verify-user
          v-if="route === 'confirmVerifyUser'"
          @confirm-verify-user-submit="onConfirmVerifyUserSubmitI"
          ref="confirmVerifyUserComponent"
        >
          <template #confirmVerifyUserSlotI>
            <slot name="confirm-verify-user"></slot>
          </template>
          <template #header>
            <slot name="confirm-verify-user-header"></slot>
          </template>
          <template #footer="{ onConfirmVerifyUserSubmit, onSkipClicked }">
            <slot
              name="confirm-verify-user-footer"
              :onConfirmVerifyUserSubmit="onConfirmVerifyUserSubmit"
              :onSkipClicked="onSkipClicked"
            >
            </slot>
          </template>
        </confirm-verify-user>
      </div>
      <slot name="footer"></slot>
    </div>
  </div>
  <!-- cast slot props back to any for backwards compatibility -->
  <slot
    v-if="route === 'authenticated'"
    :user="(user as any)"
    :state="(state as any)"
    :signOut="(signOut as any)"
    :send="(send as any)"
  ></slot>
</template>
