<script>
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
import { LOAD_FAILURE } from '../../constants';
import { reportToSentry } from '../../utils';
import { ONE_COL_WIDTH, UPSTREAM, LAYER_VIEW, STAGE_VIEW } from './constants';
import LinkedPipeline from './linked_pipeline.vue';
import {
  calculatePipelineLayersInfo,
  getQueryHeaders,
  serializeLoadErrors,
  toggleQueryPollingByVisibility,
  unwrapPipelineData,
  validateConfigPaths,
} from './utils';

export default {
  components: {
    LinkedPipeline,
    PipelineGraph: () => import('./graph_component.vue'),
  },
  props: {
    columnTitle: {
      type: String,
      required: true,
    },
    configPaths: {
      type: Object,
      required: true,
      validator: validateConfigPaths,
    },
    linkedPipelines: {
      type: Array,
      required: true,
    },
    showLinks: {
      type: Boolean,
      required: true,
    },
    type: {
      type: String,
      required: true,
    },
    viewType: {
      type: String,
      required: true,
    },
  },
  data() {
    return {
      currentPipeline: null,
      loadingPipelineId: null,
      pipelineLayers: {},
      pipelineExpanded: false,
    };
  },
  titleClasses: [
    'gl-font-weight-bold',
    'gl-pipeline-job-width',
    'gl-text-truncate',
    'gl-line-height-36',
    'gl-pl-3',
    'gl-mb-5',
  ],
  minWidth: `${ONE_COL_WIDTH}px`,
  computed: {
    columnClass() {
      const positionValues = {
        right: 'gl-ml-6',
        left: 'gl-mr-6',
      };
      return `graph-position-${this.graphPosition} ${positionValues[this.graphPosition]}`;
    },
    computedTitleClasses() {
      const positionalClasses = this.isUpstream
        ? ['gl-w-full', 'gl-text-right', 'gl-linked-pipeline-padding']
        : [];

      return [...this.$options.titleClasses, ...positionalClasses];
    },
    graphPosition() {
      return this.isUpstream ? 'left' : 'right';
    },
    graphViewType() {
      return this.currentPipeline?.usesNeeds ? this.viewType : STAGE_VIEW;
    },
    isUpstream() {
      return this.type === UPSTREAM;
    },
    minWidth() {
      return this.isUpstream ? 0 : this.$options.minWidth;
    },
  },
  methods: {
    getPipelineData(pipeline) {
      const projectPath = pipeline.project.fullPath;

      this.$apollo.addSmartQuery('currentPipeline', {
        query: getPipelineDetails,
        pollInterval: 10000,
        context() {
          return getQueryHeaders(this.configPaths.graphqlResourceEtag);
        },
        variables() {
          return {
            projectPath,
            iid: pipeline.iid,
          };
        },
        update(data) {
          /*
            This check prevents the pipeline from being overwritten
            when a poll times out and the data returned is empty.
            This can be removed once the timeout behavior is updated.
            See: https://gitlab.com/gitlab-org/gitlab/-/issues/323213.
          */

          if (!data?.project?.pipeline) {
            return this.currentPipeline;
          }

          return unwrapPipelineData(projectPath, JSON.parse(JSON.stringify(data)));
        },
        result() {
          this.loadingPipelineId = null;
          this.$emit('scrollContainer');
        },
        error(err) {
          this.$emit('error', { type: LOAD_FAILURE, skipSentry: true });

          reportToSentry(
            'linked_pipelines_column',
            `error type: ${LOAD_FAILURE}, error: ${serializeLoadErrors(err)}`,
          );
        },
      });

      toggleQueryPollingByVisibility(this.$apollo.queries.currentPipeline);
    },
    getPipelineLayers(id) {
      if (this.viewType === LAYER_VIEW && !this.pipelineLayers[id]) {
        this.pipelineLayers[id] = calculatePipelineLayersInfo(
          this.currentPipeline,
          this.$options.name,
          this.configPaths.metricsPath,
        );
      }

      return this.pipelineLayers[id];
    },
    isExpanded(id) {
      return Boolean(this.currentPipeline?.id && id === this.currentPipeline.id);
    },
    isLoadingPipeline(id) {
      return this.loadingPipelineId === id;
    },
    onPipelineClick(pipeline) {
      /* If the clicked pipeline has been expanded already, close it, clear, exit */
      if (this.currentPipeline?.id === pipeline.id) {
        this.pipelineExpanded = false;
        this.currentPipeline = null;
        return;
      }

      /* Set the loading id */
      this.loadingPipelineId = pipeline.id;

      /*
        Expand the pipeline.
        If this was not a toggle close action, and
        it was already showing a different pipeline, then
        this will be a no-op, but that doesn't matter.
      */
      this.pipelineExpanded = true;

      this.getPipelineData(pipeline);
    },
    onDownstreamHovered(jobName) {
      this.$emit('downstreamHovered', jobName);
    },
    onPipelineExpandToggle(jobName, expanded) {
      // Highlighting only applies to downstream pipelines
      if (this.isUpstream) {
        return;
      }

      this.$emit('pipelineExpandToggle', jobName, expanded);
    },
    showContainer(id) {
      return this.isExpanded(id) || this.isLoadingPipeline(id);
    },
  },
};
</script>

<template>
  <div class="gl-display-flex">
    <div :class="columnClass" class="linked-pipelines-column">
      <div data-testid="linked-column-title" :class="computedTitleClasses">
        {{ columnTitle }}
      </div>
      <ul class="gl-pl-0">
        <li
          v-for="pipeline in linkedPipelines"
          :key="pipeline.id"
          class="gl-display-flex gl-mb-4"
          :class="{ 'gl-flex-direction-row-reverse': isUpstream }"
        >
          <linked-pipeline
            class="gl-display-inline-block"
            :is-loading="isLoadingPipeline(pipeline.id)"
            :pipeline="pipeline"
            :column-title="columnTitle"
            :type="type"
            :expanded="isExpanded(pipeline.id)"
            @downstreamHovered="onDownstreamHovered"
            @pipelineClicked="onPipelineClick(pipeline)"
            @pipelineExpandToggle="onPipelineExpandToggle"
          />
          <div
            v-if="showContainer(pipeline.id)"
            :style="{ minWidth }"
            class="gl-display-inline-block"
          >
            <pipeline-graph
              v-if="isExpanded(pipeline.id)"
              :type="type"
              class="gl-inline-block gl-mt-n2"
              :config-paths="configPaths"
              :pipeline="currentPipeline"
              :computed-pipeline-info="getPipelineLayers(pipeline.id)"
              :show-links="showLinks"
              :is-linked-pipeline="true"
              :view-type="graphViewType"
            />
          </div>
        </li>
      </ul>
    </div>
  </div>
</template>
