<script>
import { createAlert } from '~/flash';
import { __ } from '~/locale';
import { mapEnvironmentNames, reportMessageToSentry } from '../utils';
import {
  ADD_MUTATION_ACTION,
  DELETE_MUTATION_ACTION,
  UPDATE_MUTATION_ACTION,
  environmentFetchErrorText,
  genericMutationErrorText,
  variableFetchErrorText,
} from '../constants';
import CiVariableSettings from './ci_variable_settings.vue';

export default {
  components: {
    CiVariableSettings,
  },
  inject: ['endpoint'],
  props: {
    areScopedVariablesAvailable: {
      required: true,
      type: Boolean,
    },
    componentName: {
      required: true,
      type: String,
    },
    entity: {
      required: false,
      type: String,
      default: '',
    },
    fullPath: {
      required: false,
      type: String,
      default: null,
    },
    hideEnvironmentScope: {
      type: Boolean,
      required: false,
      default: false,
    },
    id: {
      required: false,
      type: String,
      default: null,
    },
    mutationData: {
      required: true,
      type: Object,
      validator: (obj) => {
        const hasValidKeys = Object.keys(obj).includes(
          ADD_MUTATION_ACTION,
          UPDATE_MUTATION_ACTION,
          DELETE_MUTATION_ACTION,
        );

        const hasValidValues = Object.values(obj).reduce((acc, val) => {
          return acc && typeof val === 'object';
        }, true);

        return hasValidKeys && hasValidValues;
      },
    },
    refetchAfterMutation: {
      required: false,
      type: Boolean,
      default: false,
    },
    queryData: {
      required: true,
      type: Object,
      validator: (obj) => {
        const { ciVariables, environments } = obj;
        const hasCiVariablesKey = Boolean(ciVariables);
        let hasCorrectEnvData = true;

        const hasCorrectVariablesData =
          typeof ciVariables?.lookup === 'function' && typeof ciVariables.query === 'object';

        if (environments) {
          hasCorrectEnvData =
            typeof environments?.lookup === 'function' && typeof environments.query === 'object';
        }

        return hasCiVariablesKey && hasCorrectVariablesData && hasCorrectEnvData;
      },
    },
  },
  data() {
    return {
      ciVariables: [],
      hasNextPage: false,
      isInitialLoading: true,
      isLoadingMoreItems: false,
      loadingCounter: 0,
      maxVariableLimit: 0,
      pageInfo: {},
    };
  },
  apollo: {
    ciVariables: {
      query() {
        return this.queryData.ciVariables.query;
      },
      variables() {
        return {
          fullPath: this.fullPath || undefined,
        };
      },
      update(data) {
        return this.queryData.ciVariables.lookup(data)?.nodes || [];
      },
      result({ data }) {
        this.maxVariableLimit = this.queryData.ciVariables.lookup(data)?.limit || 0;

        this.pageInfo = this.queryData.ciVariables.lookup(data)?.pageInfo || this.pageInfo;
        this.hasNextPage = this.pageInfo?.hasNextPage || false;

        // Because graphQL has a limit of 100 items,
        // we batch load all the variables by making successive queries
        // to keep the same UX. As a safeguard, we make sure that we cannot go over
        // 20 consecutive API calls, which means 2000 variables loaded maximum.
        if (!this.hasNextPage) {
          this.isLoadingMoreItems = false;
        } else if (this.loadingCounter < 20) {
          this.hasNextPage = false;
          this.fetchMoreVariables();
          this.loadingCounter += 1;
        } else {
          createAlert({ message: this.$options.tooManyCallsError });
          reportMessageToSentry(this.componentName, this.$options.tooManyCallsError, {});
        }
      },
      error() {
        this.isLoadingMoreItems = false;
        this.hasNextPage = false;
        createAlert({ message: variableFetchErrorText });
      },
      watchLoading(flag) {
        if (!flag) {
          this.isInitialLoading = false;
        }
      },
    },
    environments: {
      query() {
        return this.queryData?.environments?.query || {};
      },
      skip() {
        return !this.queryData?.environments?.query;
      },
      variables() {
        return {
          fullPath: this.fullPath,
        };
      },
      update(data) {
        return mapEnvironmentNames(this.queryData.environments.lookup(data)?.nodes);
      },
      error() {
        createAlert({ message: environmentFetchErrorText });
      },
    },
  },
  computed: {
    isLoading() {
      return (
        (this.$apollo.queries.ciVariables.loading && this.isInitialLoading) ||
        this.$apollo.queries.environments.loading ||
        this.isLoadingMoreItems
      );
    },
  },
  methods: {
    addVariable(variable) {
      this.variableMutation(ADD_MUTATION_ACTION, variable);
    },
    deleteVariable(variable) {
      this.variableMutation(DELETE_MUTATION_ACTION, variable);
    },
    fetchMoreVariables() {
      this.isLoadingMoreItems = true;

      this.$apollo.queries.ciVariables.fetchMore({
        variables: {
          after: this.pageInfo.endCursor,
        },
      });
    },
    updateVariable(variable) {
      this.variableMutation(UPDATE_MUTATION_ACTION, variable);
    },
    async variableMutation(mutationAction, variable) {
      try {
        const currentMutation = this.mutationData[mutationAction];

        const { data } = await this.$apollo.mutate({
          mutation: currentMutation,
          variables: {
            endpoint: this.endpoint,
            fullPath: this.fullPath || undefined,
            id: this.id || undefined,
            variable,
          },
        });

        if (data.ciVariableMutation?.errors?.length) {
          const { errors } = data.ciVariableMutation;
          createAlert({ message: errors[0] });
        } else if (this.refetchAfterMutation) {
          // The writing to cache for admin variable is not working
          // because there is no ID in the cache at the top level.
          // We therefore need to manually refetch.
          this.$apollo.queries.ciVariables.refetch();
        }
      } catch (e) {
        createAlert({ message: genericMutationErrorText });
      }
    },
  },
  i18n: {
    tooManyCallsError: __('Maximum number of variables loaded (2000)'),
  },
};
</script>

<template>
  <ci-variable-settings
    :are-scoped-variables-available="areScopedVariablesAvailable"
    :entity="entity"
    :hide-environment-scope="hideEnvironmentScope"
    :is-loading="isLoading"
    :variables="ciVariables"
    :max-variable-limit="maxVariableLimit"
    :environments="environments"
    @add-variable="addVariable"
    @delete-variable="deleteVariable"
    @update-variable="updateVariable"
  />
</template>
