<template>
  <div class="TaskButton">
    <q-skeleton type="QBtn" v-if="loading" />
    <template v-else>
      <transition name="TransitionDrop">
        <div
          v-if="dropDownIsOpened || !showToggleButton"
          :class="showToggleButton ? 'TaskButton-Drop' : 'TaskButton-Row'"
        >
          <div
            class="TaskButton-Wrap"
            v-for="button in visibleComponents"
            :key="button.id"
          >
            <component
              v-if="!!button.componentPath"
              :key="getUUID()"
              :label="getLabel(button)"
              :common="common"
              :button-settings="button"
              :model="model"
              :user-task="userTask"
              :task-process="taskProcess"
              :disable="isButtonDisabled(button)"
              :is="components[button.componentPath]"
              v-bind="button.componentProps"
              @submit="submit"
              @close="$emit('close', { button })"
              @done="
                $emit('done', { button });
                $emit('reload');
              "
              @onBeforeAction="
                (buttonCode, data) => button.onBeforeAction(buttonCode, data)
              "
            >
              <q-tooltip v-if="!!button.tooltip">
                {{ button.tooltip }}
              </q-tooltip>
            </component>
            <r-btn
              v-else
              :label="getLabel(button)"
              :disable="isButtonDisabled(button)"
              @click="
                submit({
                  buttonId: button.id,
                  data: getData(button),
                  force: !!button.force,
                })
              "
              v-on="button.actions"
              v-bind="omit(button.props, ['disable', 'label'])"
            />
          </div>
        </div>
      </transition>
      <transition name="TransitionToggle">
        <r-btn
          v-if="showToggleButton"
          round
          class="RButton TaskButton-DropButton"
          :class="
            dropDownIsOpened
              ? 'TaskButton-DropButton--Opened'
              : 'TaskButton-DropButton--Closed'
          "
          key="open"
          @click="toggleDropDown"
        >
          <span class="TaskButton-IconWrapper">
            <q-icon class="TaskButton-Icon TaskButton-OpenedIcon" name="menu" />
            <q-icon
              class="TaskButton-Icon TaskButton-ClosedIcon"
              name="close"
            />
          </span>
        </r-btn>
      </transition>
    </template>
  </div>
</template>

<script lang="ts">
import { Component, Mixins, Prop } from 'vue-property-decorator';
import { v4 as getUUID } from 'uuid';
import isEmpty from 'lodash/isEmpty';
import get from 'lodash/get';
import omit from 'lodash/omit';
import isFunction from 'lodash/isFunction';
import isArray from 'lodash/isArray';
import { UserTask, TaskProcess } from '@openapi/wf';
import { notifyError } from '@utils/notify';
import ComponentImporter from '@/utils/component-importer';
import { Button } from './types';

import RefreshKeyInterceptor from '@/recruits/mixins/refresh-key-interceptor';

export interface SubmitParams {
  buttonId: string;
  data: unknown;
  force: boolean;
}

type Listener = { (task: Task): void };

interface Task {
  title?: string;
  type?: string;
  userTask: UserTask;
  process?: TaskProcess;
}

class SocketMessage<T> {
  id: string;
  traceId?: string;
  createDate?: string;
  till?: string;
  nextTry?: string;
  attempts?: number;
  forUser?: string;
  armID?: string;
  className?: string;
  action?: string;
  body: T;

  constructor(message: MessageEvent<string>) {
    const data = JSON.parse(get(message, 'data', '{}'));
    if (isEmpty(data)) throw new Error('');
    return {
      body: {},
      ...data,
    };
  }
}

@Component({
  methods: {
    getUUID,
    omit,
  },
})
export default class TaskButtons extends Mixins(RefreshKeyInterceptor) {
  getUUID: typeof getUUID;
  omit: typeof omit;

  @Prop({ default: false })
  loading: boolean;

  @Prop({ default: false })
  disableAll: boolean;

  @Prop()
  businessKey?: string;

  @Prop({ default: () => ({}) })
  model: unknown;

  @Prop({ default: () => [] })
  buttons: Button[];

  @Prop({ default: () => ({}) })
  common: Record<string, unknown>;

  listener: { (message: MessageEvent<string>): void } | null = null;

  dropDownIsOpened = false;

  awaitSocketMessageForRefresh = 'default';

  get userTask(): UserTask | null {
    return this.$store.getters['wfActions/userTasks/userTask'];
  }

  get taskProcess(): TaskProcess | null {
    return this.$store.getters['wfActions/userTasks/taskProcess'];
  }

  public disable = false;

  components: Record<string, unknown> = {};

  get buttonsById(): Record<string, Button> {
    return this.buttons.reduce((mem, button) => {
      if (button) mem[button.id] = button;
      return mem;
    }, {} as Record<string, Button>);
  }

  toggleDropDown(): void {
    this.dropDownIsOpened = !this.dropDownIsOpened;
  }

  async submit(
    { buttonId, data, force }: SubmitParams,
    counter = 0
  ): Promise<void> {
    const button = this.buttonsById[buttonId] || {};
    if (!force && button.dialog) {
      button.dialog.value = true;
    } else {
      if (button.props) {
        button.props.loading = true;
      }

      this.disable = true;

      if (typeof data !== 'object') return;
      const requestBody: Record<string, unknown> = {
        id: button.id || buttonId,
        ...(data && { data }),
      };

      const taskId = this.userTask?.taskId;
      if (taskId) {
        try {
          await this.$store.dispatch('wfActions/processes/submitForm', {
            taskId,
            traceId: this.taskProcess?.traceId,
            formModel: requestBody,
          });
          if (isFunction(button.callBack)) button.callBack();
          if (button?.props?.awaitSocketMessageForRefresh) {
            this.awaitSocketMessageForRefresh = button?.props
              ?.awaitSocketMessageForRefresh as string;
          }

          if (button.props) {
            button.props.loading = false;
          }
        } catch (error) {
          if (error instanceof Error) notifyError(error.message);
        }
      } else if (counter < 3) {
        setTimeout(() => {
          this.submit({ buttonId, data, force }, counter + 1);
        }, 700);
      }
    }
    this.disable = false;
  }

  stopListening(): void {
    if (!this.listener) return;
    console.log('✋ Отписались от сообщений');
    this.$websockets.removeListener(this.listener);
  }

  registerListener(cb: Listener): void {
    console.log('🦻 Подписались на сообщения');
    this.listener = (message: MessageEvent<string>) => {
      const traceData = new SocketMessage<Task>(message);
      return cb(traceData.body);
    };
    this.$websockets.addListener(this.listener);
  }

  reloadCondition(message: SocketMessage<Task>): boolean {
    if (message.traceId !== this.taskProcess?.traceId) return false;
    return isArray(get(message, 'body.userTask.outData.actions'));
  }

  fetchButtons(): void {
    const componentsObj = this.components ? this.components : {};

    this.buttons.forEach((button) => {
      const pathComponent = get(button, 'componentPath');

      if (!pathComponent || get(this.components, `${pathComponent}`)) {
        return;
      } // не выполняем загрузку, если нет pathComponent или уже загружено
      componentsObj[pathComponent] = {};
      this.$set(
        componentsObj,
        pathComponent,
        ComponentImporter.registerComponent(
          pathComponent,
          'components/dialogs/'
        )
      );
    });
    this.components = JSON.parse(JSON.stringify(componentsObj));
  }

  isButtonDisabled(button: Button): boolean {
    return this.disableAll || this.disable || button.props?.disable === true;
  }

  getLabel(button: Button): string {
    const label = button.label || button.props?.label || '';
    return String(label);
  }

  getData(button: Button): unknown {
    if (!isFunction(button.getData)) return {};
    return button.getData(this.model);
  }

  testScopes(button: Button): boolean {
    return !isEmpty(button.scopes)
      ? this.$AC.haveOneOfScope(button.scopes)
      : true;
  }

  testComponent(button: Button): boolean {
    return button.componentPath
      ? !isEmpty(this.components[button.componentPath])
      : true;
  }

  get visibleComponents(): Button[] {
    return this.buttons.filter((button) => {
      const byScopes = this.testScopes(button);
      const byComponent = this.testComponent(button);
      const byLoading = !this.loading;

      return button.visible && byScopes && byComponent && byLoading;
    });
  }

  get showToggleButton(): boolean {
    return this.visibleComponents.length > 2;
  }

  mounted(): void {
    this.fetchButtons();
    this.registerListener((message) => {
      // if (message.type && ['task', 'proccess'].includes(message.type)) {
      if (get(message, 'userTask.outData.reload')) {
        console.log('🤙', message);
        // this.$store.commit('wfActions/userTasks/saveTask', message);
        this.$emit('reload');
      }
      // awaitSocketMessageForRefresh бросам через настройку кнопок, решение для рефреша после нажатия на простые кнопки
      // пока чекаем на 'finish'
      if (message.type && this.awaitSocketMessageForRefresh === message.type) {
        this.awaitSocketMessageForRefresh = 'default';
        this.$emit('reload');
      }
    });
  }

  destroyed(): void {
    this.stopListening();
  }
}
</script>

<style lang="scss" scoped>
.TaskButton {
  position: relative;
  display: flex;
  flex-flow: row wrap;
  align-items: flex-end;
  justify-content: flex-end;
  gap: 8px;

  &-Row {
    display: flex;
    flex-flow: row wrap;
    direction: rtl;
    gap: 8px;
  }
  &-Wrap {
    direction: initial;
  }
  &-Drop {
    display: flex;
    flex-flow: column nowrap;
    align-items: flex-end;
    gap: 8px;
    position: absolute;
    top: 40px;
    right: 0;
    z-index: 10;
    ::v-deep button {
      box-shadow: 2px 2px 4px #999;
    }
  }
  &-DropButtonText {
    margin-right: 8px;
  }
  &-IconWrapper {
    position: relative;
    width: 1.715em;
    height: 1.715em;
  }
  &-Icon {
    position: absolute;
    left: 0;
    top: 1px;
    transition: opacity 0.4s, transform 0.4s;
  }
  &-DropButton.TaskButton-DropButton--Closed {
    .TaskButton-OpenedIcon {
      opacity: 1;
      transform: rotate(0deg);
    }
    .TaskButton-ClosedIcon {
      opacity: 0;
      transform: rotate(-180deg);
    }
  }
  &-DropButton.TaskButton-DropButton--Opened {
    .TaskButton-OpenedIcon {
      transform: rotate(180deg);
      opacity: 0;
    }
    .TaskButton-ClosedIcon {
      transform: rotate(0deg);
      opacity: 1;
    }
  }
}

.TransitionDrop-enter-active,
.TransitionDrop-leave-active {
  transition: transform 0.3s ease-in-out, opacity 0.3s linear;
  transform-origin: right top;
}
.TransitionDrop-enter-to,
.TransitionDrop-leave {
  opacity: 1;
  transform: translateY(0);
}
.TransitionDrop-leave-to,
.TransitionDrop-enter {
  opacity: 0;
  transform: translateY(-8px);
}
</style>
