<template>
  <white-card
    v-if="infringements.length || editable"
    class="InfringementAppealBlock"
  >
    <div class="InfringementAppealBlock-Top">
      <div class="InfringementAppealBlock-Header">
        {{ header }}
      </div>
      <r-btn
        v-if="editable"
        dense
        action
        no-wrap
        label="ДОБАВИТЬ ЗАПИСЬ"
        icon="pgs-plus"
        @click="createInfringement"
      />
    </div>
    <div class="InfringementAppealBlock-ContentBox">
      <empty-block v-if="!infringements.length" />
      <div
        class="row q-col-gutter-xs"
        v-for="(infringement, i) in infringements"
        :key="infringement.id"
      >
        <div class="col-12 q-my-sm relative-position">
          <data-block bolder>
            <q-form
              class="q-gutter-md"
              greedy
              @submit="saveInfringement(infringement)"
              @reset="reset(infringement.id, i)"
            >
              <div class="row q-col-gutter-xs q-mb-lg">
                <div class="InfringementAppealBlock-RecordHeader">
                  <div class="InfringementAppealBlock-RecordCount">
                    {{ infringement.recordNumber }}
                  </div>
                  <template v-if="editor">
                    <div
                      class="InfringementAppealBlock-BlockButtons"
                      v-if="isFooterVisible(infringement)"
                    >
                      <r-btn
                        dense
                        transparent
                        label="ОТМЕНА"
                        icon="pgs-close"
                        no-wrap
                        type="reset"
                      />
                      <r-btn
                        :disabled="
                          !(
                            isCreatedInfrigementAndNotEmpty(infringement) ||
                            changedInfringementIds.includes(infringement.id)
                          )
                        "
                        dense
                        action
                        label="СОХРАНИТЬ"
                        icon="pgs-save"
                        type="submit"
                        no-wrap
                        v-activity-recorder="activityRecorderOption"
                      />
                    </div>
                    <q-btn
                      v-else
                      label="Редактировать"
                      icon="pgs-edit"
                      class="InfringementAppealBlock-EditButton"
                      @click="$set(editor, infringement.id, true)"
                    />
                  </template>
                </div>
                <label class="input-block col-6">
                  <em>Номер обжалуемого решения</em>
                  <q-input
                    v-model.trim="infringement.appealNumber"
                    :rules="[rules.required, rules.strings]"
                    :disable="isDisable(infringement.id)"
                    lazy-rules
                    dense
                    borderless
                  />
                </label>
                <label class="input-block col-6">
                  <em>Дата обжалуемого решения</em>
                  <x-date-input
                    :value="unformatDate(infringement.appealDate)"
                    :rules="[rules.required, rules.date, dateRangeRule]"
                    :options="datePickerOptions"
                    :raw-value="true"
                    :disable="isDisable(infringement.id)"
                    hide-bottom-space
                    lazy-rules
                    @input="infringement.appealDate = formatDate($event)"
                  />
                </label>
                <label class="input-block col-6">
                  <em>Номер принятого решения</em>
                  <q-input
                    v-model.trim="infringement.resultNumber"
                    :rules="[rules.required, rules.strings]"
                    :disable="isDisable(infringement.id)"
                    lazy-rules
                    dense
                    borderless
                  />
                </label>
                <label class="input-block col-6">
                  <em>Дата принятия решения</em>
                  <x-date-input
                    :value="unformatDate(infringement.resultDate)"
                    :rules="[rules.required, rules.date, dateRangeRule]"
                    :options="datePickerOptions"
                    :raw-value="true"
                    :disable="isDisable(infringement.id)"
                    hide-bottom-space
                    lazy-rules
                    @input="infringement.resultDate = formatDate($event)"
                  />
                </label>
                <label class="input-block col-6">
                  <em>Результат рассмотрения</em>
                  <q-select
                    v-model.trim="infringement.result"
                    emit-value
                    map-options
                    borderless
                    :rules="[rules.required]"
                    :disable="isDisable(infringement.id)"
                    option-value="code"
                    option-label="value"
                    dense
                    :options="appeals"
                    class="r-select"
                  >
                    <template v-slot:selected-item="scope">
                      <div class="ellipsis r-select-text">
                        {{ scope.opt.value }}
                      </div>
                    </template>
                  </q-select>
                </label>
                <label class="input-block col-6">
                  <em>Наименование комиссии, принявшей решение</em>
                  <q-input
                    v-model.trim="infringement.commissionName"
                    :rules="[rules.required, rules.rusStrings]"
                    :disable="isDisable(infringement.id)"
                    lazy-rules
                    dense
                    borderless
                  />
                </label>
                <label class="input-block col-6">
                  <em>Основание для внесения записи</em>
                  <q-input
                    v-model="infringement.reasonForAddEntry"
                    :rules="[rules.required, rules.strings]"
                    :disable="isDisable(infringement.id)"
                    lazy-rules
                    dense
                    borderless
                  />
                </label>
              </div>
            </q-form>
          </data-block>
          <q-inner-loading :showing="loadingIds.includes(infringement.id)">
            <q-spinner-gears size="50px" color="primary"></q-spinner-gears>
          </q-inner-loading>
        </div>
      </div>
    </div>
    <q-dialog v-model="deleteInfringementDialog" no-backdrop-dismiss>
      <q-card>
        <q-card-section class="InfringementDialog-AttentionContent">
          <q-icon
            class="InfringementDialog-AttentionContent__Icon"
            name="pgs-delete"
          />
          <p class="InfringementDialog-AttentionContent__Header">
            Вы уверены, что хотите удалить запись об обжаловании?
          </p>
        </q-card-section>
        <q-card-actions class="InfringementDialog-AttentionButtons">
          <r-btn
            class="InfringementDialog-AttentionButton"
            label="Закрыть"
            v-close-popup
          />
          <r-btn
            class="InfringementDialog-AttentionButton"
            label="Удалить"
            :action="true"
            @click="deleteInfringement"
          />
        </q-card-actions>
      </q-card>
    </q-dialog>
  </white-card>
</template>

<script lang="ts">
import { Component, PropSync, Prop, Vue, Watch } from 'vue-property-decorator';
import { notifyError, notifySuccess } from '@utils/notify';
import dataBlock from '@components/cards/statements/components/dataBlock.vue';
import WhiteCard from '@/recruits/components/common/white-card.vue';
import BlockHeader from '@/recruits/components/common/block-header.vue';
import {
  AppealDocumentServiceApi,
  InfringementAppeal,
} from '@/recruits/openapi/appeal-document';
import RInput from '@/recruits/components/common/r-input.vue';
import isNull from 'lodash/isNull';
import ApiErrors from '@/recruits/components/dialogs/ApiErrors.vue';
import XDateInput from '@/recruits/components/common/x-date-input.vue';
import { VALIDATION_RULES } from '@/recruits/utils/validation-rules';
import moment from 'moment';
import {
  getCurrentDate,
  formatDate,
  unformatDate,
} from '@/utils/field-validation/dates';
import EmptyBlock from '@/recruits/components/recruit-card/infringement/EmptyBlock.vue';

@Component({
  components: {
    EmptyBlock,
    dataBlock,
    WhiteCard,
    BlockHeader,
    RInput,
    XDateInput,
    ApiErrors,
  },
  methods: {
    formatDate,
    unformatDate,
  },
})
export default class InfringementAppealBlock extends Vue {
  @PropSync('infringementsAppeal')
  _infringementsAppeal: InfringementAppeal[];

  @Prop({ required: true }) recruitId: string;
  @Prop() activityRecorderOption: any;
  @Prop({ default: true }) editable: boolean;
  @Prop({ default: '', type: String }) header: string;

  public infringements: InfringementAppeal[] = [];
  public createdCount = 0; // createdCount нужен чтобы был уникальный id для созданных блоков
  public loadingIds: string[] = [];
  public deleteInfringementDialog = false;
  public deleteInfringementId = '';
  public rules = VALIDATION_RULES;
  public dateRangeRule = this.rules.dateRange({
    from: '2023-09-01',
    to: getCurrentDate(),
  });

  editor: Record<string, boolean> = {};

  // recordCount(recordId: string, index: number): string {
  //   return this.isCreatedInfrigement(recordId)
  //     ? 'Создание записи'
  //     : `Запись №${index + 1}`;
  // }

  isDisable(id: InfringementAppeal['id']): boolean {
    return !(
      id &&
      this.editable &&
      (this.isCreatedInfrigement(id) || this.editor[id])
    );
  }

  public datePickerOptions = (dt: string): boolean => {
    const dateValidation = this.dateRangeRule(dt.replace(/\//g, '-'));
    return dateValidation === true;
  };

  AppealDocumentServiceApi = new AppealDocumentServiceApi();

  get appeals(): { code: string; value: string }[] {
    const appealsList = this.$store.getters[
      'ervuDictionaries/punishmentAppealList'
    ];
    return appealsList?.documents ?? appealsList ?? [];
  }

  @Watch('_infringementsAppeal', {
    immediate: true,
  })
  infringementsAppealWatcher(val: InfringementAppeal[]): void {
    // обновляем только при первом открытии(пока нет данных в локальном массиве infringements)
    // т.к. после сохранение нам надо обновить данные в родителе для синхронизации
    // и если были изменение в другом несохраненном блоке, то он затрется
    if (!this.infringements.length) {
      const data = JSON.parse(JSON.stringify(val));
      this.infringements = data;
    }
  }

  // Функция получения index по id
  getInfringementIndex(id: string): number | null {
    let numElem: number | null = null;
    this.infringements.forEach((el, index) => {
      if (el.id === id) {
        numElem = index;
      }
    });
    return numElem;
  }

  public reset(infringementId: string, index: number): void {
    if (this.isCreatedInfrigement(infringementId)) {
      // открытие окна подтверждения удаления
      this.deleteInfringementId = infringementId;
      this.deleteInfringementDialog = true;
    } else {
      this.$set(
        this.infringements,
        index,
        JSON.parse(JSON.stringify(this._infringementsAppeal[index]))
      );
      this.editor[infringementId] = false;
    }
  }

  public isFooterVisible(infringement: InfringementAppeal): boolean {
    return (
      !!infringement.id &&
      (this.isCreatedInfrigement(infringement.id) ||
        this.editor[infringement.id])
    );
  }

  // Функция для определения блок создается или редактируется
  public isCreatedInfrigement(
    infringementId: InfringementAppeal['id']
  ): boolean {
    return !!infringementId && infringementId.includes('created');
  }

  // Функция для определния заполнено ли хоть одно поле в новом блоке
  isCreatedInfrigementAndNotEmpty(infringement: InfringementAppeal): boolean {
    let resultState = false;
    // FIXME временная проверка на наличие id, так как оно точно обязательное,
    // но временно в апи указано как необязательное
    if (this.isCreatedInfrigement(infringement?.id || '')) {
      // проверка что хотя бы одно поле не пустое
      // TODO так же, как и блок проверки изменений - changedInfringementIds стоит переписать,
      // чтобы объеденить две логики в одну
      resultState = !!Object.entries(infringement).filter(
        (entry) =>
          !isNull(entry[1]) &&
          entry[1] !== '' &&
          entry[0] !== 'id' &&
          entry[0] !== 'typeDocument' &&
          entry[0] !== 'recruitId'
      ).length;
    }
    return resultState;
  }

  // Функция создания пустого блока, createdCount нужен чтобы был уникальный id
  createInfringement(): void {
    this.$emit('onBeforeAction', 'complaint_add');
    this.infringements.push({
      appealNumber: '',
      appealDate: '',
      resultNumber: '',
      resultDate: '',
      result: '',
      commissionName: '',
      reasonForAddEntry: '',
      recruitId: this.recruitId,
      id: `created_${this.createdCount}`,
    });
    this.createdCount++;
  }

  // Функция удаления блока
  deleteInfringement(): void {
    // сохранение id
    const infringementId = this.deleteInfringementId;
    this.loadingIds.push(infringementId);

    // закрываем модалку и даем пользователю дальше пользоваться сайтом пока происходит удаление
    this.deleteInfringementDialog = false;
    this.deleteInfringementId = '';

    // процесс удаления
    if (this.isCreatedInfrigement(infringementId)) {
      const infringementIndex = this.getInfringementIndex(infringementId);
      if (!isNull(infringementIndex)) {
        this.infringements.splice(infringementIndex, 1);
      }
      this.loadingIds = this.loadingIds.filter(
        (loadingId) => loadingId !== infringementId
      );
    } else {
      this.AppealDocumentServiceApi.deleteRecruitAppeal(infringementId)
        .then(() => {
          const infringementIndex = this.getInfringementIndex(infringementId);
          if (!isNull(infringementIndex)) {
            this.infringements.splice(infringementIndex, 1);
          }
        })
        .catch(() => {
          notifyError(
            'Не удалось сохранить данные, проверьте корректность ввода данных'
          );
        })
        .finally(() => {
          this.loadingIds = this.loadingIds.filter(
            (loadingId) => loadingId !== infringementId
          );
        });
    }
  }

  // Функция сохранения блока
  async saveInfringement(infringement: InfringementAppeal): Promise<void> {
    this.$emit('onBeforeAction', 'complaint_save');
    // FIXME если нет id то подсовываем пустоты чтобы запустить лоадер, но такого не может быть
    // сейчас ошибка так как не обязательное поле
    this.loadingIds.push(infringement.id || '');
    const infringementToSend: Record<string, string> = {};

    // подготовка данных
    // при отсутствии данных поле на бэк не отправляется, так же при создании поле id не должно отправляться
    Object.entries(infringement).forEach(
      ([infringementKey, infringementValue]) => {
        if (
          infringementValue &&
          !(
            infringementKey === 'id' &&
            this.isCreatedInfrigement(infringementValue)
          )
        ) {
          infringementToSend[infringementKey] = infringementValue;
        }
      }
    );
    // FIXME если нет id то подсовываем created чтобы пошло создание, но отсутствовать id не может
    // сейчас ошибка так как не обязательное поле
    if (this.isCreatedInfrigement(infringement.id || 'created')) {
      const oldInfringementId = infringement.id;
      try {
        const {
          data,
        } = await this.AppealDocumentServiceApi.createRecruitAppeal(
          infringementToSend
        );

        // обновляем id, createDate в корректный с бэка
        infringement.id = data.id;
        infringement.createDate = data.createDate;
        infringement.recordNumber = data.recordNumber;
        notifySuccess('Данные сохранены');
        this.$emit('save');
      } catch (error) {
        notifyError(
          'Не удалось сохранить данные, проверьте корректность ввода данных'
        );
      } finally {
        this.loadingIds = this.loadingIds.filter(
          (loadingId) => loadingId !== oldInfringementId
        );
      }

      // обновляем данные в родители, чтобы корректно работало сравнивание
      this._infringementsAppeal.push(JSON.parse(JSON.stringify(infringement)));
    } else {
      if (infringement.id) this.editor[infringement.id] = false;
      try {
        const {
          data,
        } = await this.AppealDocumentServiceApi.updateRecruitAppeal(
          infringementToSend
        );

        // ищем и обновляем объект из списка в родителе
        this._infringementsAppeal = this._infringementsAppeal.map(
          (infringementAppeal) => {
            if (infringementAppeal.id === data.id) {
              return JSON.parse(JSON.stringify(data));
            } else {
              return infringementAppeal;
            }
          }
        );
        notifySuccess('Данные сохранены');
        this.$emit('save');
      } catch (error) {
        notifyError(
          'Не удалось сохранить данные, проверьте корректность ввода данных'
        );
      } finally {
        this.loadingIds = this.loadingIds.filter(
          (loadingId) => loadingId !== infringement.id
        );
      }
    }
  }

  // computed отслеживающий локальный и родительский массив данных и сравнивающий их
  // бежим по 2 массивам сравнивая их id и существование id - временная проверка пока нет строгого определения наличия id
  // сравниваем два объекта и если они не равны то фиксируем его id
  // TODO переписать в более человеческую-оптимизированную функцию
  get changedInfringementIds(): string[] {
    const changedIds: string[] = [];
    this._infringementsAppeal.forEach((infringementAppeal) => {
      const entriesInfringementAppeal: string[] = [];
      // проверка на пустое поле
      // eslint-disable-next-line array-callback-return
      Object.entries(infringementAppeal).filter((entries) => {
        if (!isNull(entries[1]) && entries[1] !== '') {
          entriesInfringementAppeal.push(JSON.stringify(entries));
        }
      });

      this.infringements.forEach((infringement) => {
        if (
          infringementAppeal.id &&
          infringementAppeal.id === infringement.id
        ) {
          const entriesInfringement: string[] = [];
          // проверка на пустое поле
          // eslint-disable-next-line array-callback-return
          Object.entries(infringement).filter((entries) => {
            if (!isNull(entries[1]) && entries[1] !== '') {
              entriesInfringement.push(JSON.stringify(entries));
            }
          });

          const difference = entriesInfringement
            .filter((x) => !entriesInfringementAppeal.includes(x))
            .concat(
              entriesInfringementAppeal.filter(
                (x) => !entriesInfringement.includes(x)
              )
            );
          if (difference.length) {
            changedIds.push(infringement.id);
          }
        }
      });
    });
    return changedIds;
  }
}
</script>

<style lang="scss" scoped>
.InfringementAppealBlock {
  padding: 12px 16px;
  margin-bottom: 16px;

  &-Top {
    display: flex;
    justify-content: space-between;
    padding-bottom: 12px;
    margin-bottom: 20px;
    border-bottom: 1px solid rgba(1, 72, 116, 0.11);
  }
  &-Header {
    font-family: 'Golos UI', sans-serif;
    font-style: normal;
    font-weight: 500;
    font-size: 20px;
    line-height: 24px;
    letter-spacing: -0.015em;
    color: rgba(2, 12, 18, 0.9);
  }
  &-ContentBox {
    padding: 20px 0;
  }
  &-RecordHeader {
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 100%;
    gap: 12px;
    animation: 0.2s ease-out appear;
  }
  &-RecordCount {
    color: rgba(2, 12, 18, 0.58);
    font-family: 'Golos UI', sans-serif;
    font-size: 10px;
    font-style: normal;
    font-weight: 500;
    line-height: 12px;
    letter-spacing: 0.5px;
    text-transform: uppercase;
  }
  &-BlockButtons {
    display: flex;
    gap: 8px;
  }
  &-EditButton {
    color: #107abc;
    font-family: 'Golos UI', sans-serif;
    font-style: normal;
    font-weight: 500;
    font-size: 14px;
    line-height: 16px;
    border-radius: 24px;
    .pgs-icon {
      font-size: 1.715em !important;
    }
    i:has(+ span) {
      margin-right: 6px;
    }
  }
}
@keyframes appear {
  from {
    max-height: 0;
  }
  to {
    max-height: 150px;
  }
}

.input-block {
  min-height: 84px;
  em {
    font-family: 'Golos Text', sans-serif;
    font-style: normal;
    font-weight: 600;
    font-size: 12px;
    line-height: 16px;
    color: rgba(2, 12, 18, 0.58);
    margin-bottom: 4px;
  }

  ::v-deep .q-field {
    width: 100%;
  }

  ::v-deep .q-select {
    .q-field__bottom {
      bottom: -10px;
    }
  }

  ::v-deep .q-field__control {
    input {
      padding: 0 8px;
    }
    &:focus-within {
      background-color: white;
      box-shadow: inset 0 0 2px rgba(15, 104, 160, 0.87);
      border-color: #1487cf;
    }
    padding: 0 8px !important;
    background-color: #e5edf5;
    border-radius: 4px 4px 0 0;
    background-blend-mode: multiply;
    border-bottom: 1px solid rgba(1, 72, 116, 0.18);
  }
}
.InfringementDialog {
  &-AttentionContent {
    * {
      margin: 0;
      padding: 0;
    }
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 30px 40px;
    &__Icon {
      color: #1491cf;
      font-size: 40px;
      margin-bottom: 10px;
    }
    &__Header {
      font-family: 'Golos Text', sans-serif;
      font-style: normal;
      font-weight: 500;
      font-size: 20px;
      line-height: 24px;
      text-align: center;
      letter-spacing: -0.015em;
      color: #015e98;
      margin-bottom: 24px;
    }
  }
  &-AttentionButtons {
    display: flex;
    justify-content: space-between;
    padding: 12px 20px;
  }
  &-AttentionButton {
    padding: 0 10px;
  }
}
</style>
