<i18n>
{
  "en": {
    "errors": {
      "save": "Error occured while saving form schema, try again"
    },
    "text": {
      "saveSuccess": "Successfully saved form schema",
      "enhancedSettingsTitle": "Enhanced Settings"
    },
    "buttons": {
      "previewSchema": "Preview",
      "editSchema": "Edit"
    }
  },
  "ru": {
    "errors": {
      "save": "Во время сохранения экранной формы произошла ошибка, попробуйте еще раз"
    },
    "text": {
      "saveSuccess": "Успешно сохранили экранную форму",
      "enhancedSettingsTitle": "Расширенные настройки"
    },
    "buttons": {
      "previewSchema": "Предпросмотр",
      "editSchema": "Редактирование"
    }
  }
}
</i18n>
<template>
  <div>
    <template v-if="!loading">
      <q-card class="no-shadow q-mx-md q-mt-md">
        <div
          class="bg-grey-1 flex items-center justify-between row q-pa-md"
          style="border-radius: 0; padding: 16px"
        >
          <div class="gt-xs">
            <div v-if="service" class="flex">
              <q-btn
                padding="xs"
                class="text-grey-7 q-mr-md"
                color="grey-3"
                icon="arrow_back"
                @click="backPage"
              />
              <div class="text-h6 text-grey-9 col q-pr-md">
                {{ service.shortName }}
              </div>
            </div>
            <div v-else-if="$route.query.backRoute" class="flex">
              <q-btn
                padding="xs"
                class="text-grey-7 q-mr-md"
                color="grey-3"
                icon="arrow_back"
                @click="goBackRoute"
              />
              <div class="text-h6 text-grey-9 col q-pr-md">Назад к межведу</div>
            </div>
          </div>
          <div class="gt-xs">
            <q-toggle
              v-model="rawMode"
              label="JSON"
              class="q-mr-md"
              keep-color
              color="secondary"
              :disable="previewMode || !!liveSchemaErrors.length"
            />
            <q-btn
              @click="mockSchema"
              unelevated
              color="primary"
              :icon="previewMode ? 'edit' : 'visibility'"
              class="q-mr-md"
              :disable="isAttrList || !!liveSchemaErrors.length"
            >
              <q-tooltip :delay="500">
                {{
                  previewMode
                    ? $t('buttons.editSchema')
                    : $t('buttons.previewSchema')
                }}
              </q-tooltip>
            </q-btn>
            <q-btn
              @click="saveSchema"
              unelevated
              :disable="!!liveSchemaErrors.length"
              label="Сохранить схему"
              color="primary"
              icon="save"
            >
              <q-tooltip :delay="500">Сохранить схему</q-tooltip>
            </q-btn>
          </div>
        </div>
        <div class="flex items-center bg-white q-pa-md rounded-borders">
          <div class="title text-h6 text-grey-10">
            {{ schemaTitle }}
          </div>
        </div>
      </q-card>

      <div class="bg-white q-pa-md q-mt-md q-mx-md rounded-borders">
        <template v-if="previewMode && ready">
          <FormGeneratorWrapper
            :model="previewModel"
            mode="edit"
            :schema="schemaClone"
            :padded-groups="true"
          />
        </template>
        <template v-else>
          <MicroSchemaConstructor
            v-if="!rawMode"
            ref="msConstructor"
            :shouldInit="false"
            :showHeader="false"
            :extSchema="schema"
          />
          <div v-else class="raw-mode">
            <transition-group appear name="fade">
              <q-banner
                v-for="(error, idx) of lazySchemaErrors"
                :key="idx + Math.floor(Math.random() * 1000 + 19)"
                inline-actions
                rounded
                class="bg-red-7 text-white q-mb-md"
              >
                <div
                  :key="lineIndex + Math.floor(Math.random() * 1000 + 19)"
                  v-for="(line, lineIndex) of error.message.split('<br>')"
                >
                  {{ line }}
                </div>
              </q-banner>
            </transition-group>
            <v-json-editor
              v-model="schema"
              ref="jsoneditor"
              height="calc(100vh - 51px - 72px - 80px)"
              :options="{
                mode: 'code',
                mainMenuBar: false,
                onValidationError: setSchemaErrors,
                onBlur: setLazySchemaErrors,
              }"
            />

            <h2
              v-if="schema"
              class="justify-center text-h5 q-mt-lg"
              style="margin-top: 32px"
            >
              {{ $t('text.enhancedSettingsTitle') }}
            </h2>
            <EnhancedEditor :schema="schema" @settings="setSettings" />
          </div>
        </template>
      </div>
    </template>

    <q-inner-loading :showing="loading">
      <q-spinner-gears color="primary" size="50px" />
    </q-inner-loading>
  </div>
</template>

<script lang="ts">
// @ts-nocheck
// @ts-ignore
import VJsonEditor from 'v-jsoneditor';
import MicroSchemaConstructor from '@components/schemas/MicroSchemaConstructor';
import FormGeneratorWrapper from '@components/common/node-generator/FormGeneratorWrapper.vue';
// @ts-ignore
import { EnhancedEditor } from '@devsun/eva-extended-settings-ctor/dist/eva-extended-settings-ctor.common';
import { FormApi } from '@openapi/registry-service';
import get from 'lodash/get';
import has from 'lodash/has';
import isEmpty from 'lodash/isEmpty';
import isEqual from 'lodash/isEqual';
import set from 'lodash/set';
import cloneDeep from 'lodash/cloneDeep';
import { notifyError, notifySuccess } from '@utils/notify';

const formApi = new FormApi();

export default {
  name: 'schemas',
  props: ['common', 'model', 'user-task'],
  components: {
    FormGeneratorWrapper,
    VJsonEditor,
    MicroSchemaConstructor,
    EnhancedEditor,
  },
  data() {
    return {
      schema: null,
      lazySchemaErrors: [],
      liveSchemaErrors: [],
      schemaDump: null,
      schemaTitle: '',
      schemaKeys: [],
      unsavedChanges: false,
      previewModel: {},
      previewMode: false,
      rawMode: true,
      schemaStr: '',
      schemaMeta: null,
      propertiesMetaCopy: ['dictionaries', 'extensions'],
      service: null,
    };
  },

  computed: {
    schemaClone() {
      return cloneDeep(this.schema);
    },
    formID() {
      return this.$route.params.id;
    },
    loading() {
      return this.schema === null && !this.schemaStr;
    },
    ready() {
      return !isEmpty(this.previewModel) && !isEmpty(this.schemaClone);
    },
    isAttrList() {
      return (
        this.schema.attrList === undefined || isEmpty(this.schema.attrList)
      );
    },
  },

  async created() {
    this.setSchema();
    this.getService();
  },

  watch: {
    rawMode: {
      handler(rawMode) {
        if (rawMode) {
          const getSchemaFunc = get(
            this.$refs,
            'msConstructor.getCurrentSchema'
          );

          this.schema = { ...this.schemaMeta, ...(getSchemaFunc() || {}) };

          // Удаляем свойства в Meta при переключении в редактор, иначе данные будут не актуальны
          this.propertiesMetaCopy.forEach((property) => {
            delete this.schemaMeta[property];
          });
        } else {
          // Временное решение (Возможно нужно научить коструктор не удалять кастомные поля)
          // Копируем свойства в Meta, потому что они не сохраняются при переключении в конструктор схем
          this.propertiesMetaCopy.forEach((property) => {
            if (this.schema[property] !== undefined) {
              this.schemaMeta[property] = this.schema[property];
            }
          });
        }
      },
    },
    schema: {
      handler(schema) {
        this.unsavedChanges =
          schema !== null && !isEqual(schema, this.schemaDump);
        this.$root.$emit('unsavedChanges', this.unsavedChanges);
      },
    },
  },

  methods: {
    setSettings(settings) {
      this.$set(this.schema, 'extensions', settings);
    },
    setSchemaErrors(errors) {
      this.liveSchemaErrors = errors;
      if (this.lazySchemaErrors.length) {
        this.lazySchemaErrors = this.liveSchemaErrors;
      }
    },
    setLazySchemaErrors() {
      this.lazySchemaErrors = this.liveSchemaErrors;
    },
    removeOldProperty(schema) {
      let removeKey = null;

      const newSchema = cloneDeep(schema);

      for (const key in this.schemaKeys) {
        if (this.schemaKeys[key].includes(removeKey)) continue;

        const hasKey = has(newSchema, this.schemaKeys[key]);

        if (!hasKey) {
          set(newSchema, this.schemaKeys[key], '');
          removeKey = this.schemaKeys[key];
        }
      }
      return newSchema;
    },
    getKeysSchema(schema) {
      let keys = [];
      for (const key in schema) {
        keys.push(key);

        if (Array.isArray(schema[key])) continue;

        if (typeof schema[key] === 'object') {
          const subkeys = this.getKeysSchema(schema[key]);
          keys = keys.concat(
            subkeys.map((subkey) => {
              return `${key}.${subkey}`;
            })
          );
        }
      }
      return keys;
    },
    async getService() {
      this.service = await this.$store.dispatch(
        'registryService/services/getServiceMeta',
        {
          id: this.$route.query.serviceId,
        }
      );
    },
    async setSchema() {
      const { data: schema } = await formApi.getForm(this.formID);

      if (!schema.attrList) schema.attrList = [];
      if (!schema.groupList) schema.groupList = [];

      this.schemaMeta = {
        hidden: schema.hidden,
        modified: schema.modified,
        schema: schema.schema,
        id: schema.id,
        version: schema.version,
        _service_id: schema._service_id,
      };

      this.schemaTitle = schema.label;
      this.schema = schema;
      this.schemaDump = schema;
      this.schemaKeys = this.getKeysSchema(schema);
    },
    async saveSchema() {
      const schemaFromUI = get(this.$refs, 'msConstructor.currentSchema');
      let schema = this.rawMode ? this.schema : schemaFromUI;

      if (!schema) return;

      schema = this.removeOldProperty(schema);

      const { data: result } = await formApi
        .updateForm([{ ...schema, ...this.schemaMeta }])
        .catch((error) => {
          console.error(
            '[schemas index] could not save schema',
            { schema },
            error
          );

          notifyError(this.$t('errors.save'));

          return false;
        });

      if (result) {
        this.unsavedChanges = false;
        this.schemaTitle = this.schema.label;

        notifySuccess(this.$t('text.saveSuccess'), 'done_all');
        return this.setSchema();
      }
    },
    mockSchema() {
      this.previewMode = !this.previewMode;
      this.$store
        .dispatch('utils/components/fetchMockDataForSchema', this.schema)
        .then((data) => {
          this.previewModel = data.model;

          // Если на бфф есть не определенные типы в схеме, то выводим их
          if (Object.keys(data.warning).length > 0) {
            console.log(
              '[FormGenerator][Preview schema] неизвестные типы схемы: ',
              data.warning
            );
          }
        });
    },
    backPage() {
      if (
        !this.unsavedChanges ||
        (this.unsavedChanges &&
          window.confirm(
            'На странице есть несохраненные изменения. Продолжить?'
          ))
      ) {
        this.$router.push({
          name: 'cards',
          params: {
            id: this.service.id,
            path: 'services',
          },
          query: {
            active: 'forms',
            components: this.$store.getters[
              'cardComponents/services/components'
            ],
          },
        });
      }
    },
    goBackRoute() {
      this.$router.push(this.$route.query.backRoute);
    },
  },
};
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.2s ease-in;
}
.fade-enter-to,
.fade-leave {
  opacity: 1;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}
</style>
