<!--
  ~ Licensed to Apache Software Foundation (ASF) under one or more contributor
  ~ license agreements. See the NOTICE file distributed with
  ~ this work for additional information regarding copyright
  ~ ownership. Apache Software Foundation (ASF) licenses this file to you under
  ~ the Apache License, Version 2.0 (the "License"); you may
  ~ not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing,
  ~ software distributed under the License is distributed on an
  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  ~ KIND, either express or implied.  See the License for the
  ~ specific language governing permissions and limitations
  ~ under the License.
-->
<script setup>
  import { reactive, ref, watch, getCurrentInstance, computed } from 'vue';
  import { useRoute } from 'vue-router';
  import { ElMessage } from 'element-plus';
  import { Search, RefreshRight, TrendCharts } from '@element-plus/icons-vue';
  import { getResourceOfAllType, getTableList } from '@/api/index';
  import { jsonToYaml, yamlToJson } from '@/utils/yaml';
  import CodeMirror from '@/components/CodeMirror/index.vue';
  import FormHeader from '../common/FormHeader.vue';
  import { Shortcuts, Last15Minutes } from '../common/data';
  import { CatalogToGroupType } from '../GroupTree/data';
  import TraceTree from '../TraceTree/TraceContent.vue';
  import MeasureAndStreamTable from '../common/MeasureAndStreamTable.vue';

  const route = useRoute();
  const yamlRef = ref();
  // Loading
  const { proxy } = getCurrentInstance();
  const $loadingCreate = getCurrentInstance().appContext.config.globalProperties.$loadingCreate;
  const $loadingClose = proxy.$loadingClose;
  const tagType = {
    TAG_TYPE_UNSPECIFIED: 'null',
    TAG_TYPE_STRING: 'str',
    TAG_TYPE_INT: 'int',
    TAG_TYPE_STRING_ARRAY: 'strArray',
    TAG_TYPE_INT_ARRAY: 'intArray',
    TAG_TYPE_DATA_BINARY: 'binaryData',
  };
  const fieldTypes = {
    FIELD_TYPE_UNSPECIFIED: 'null',
    FIELD_TYPE_STRING: 'str',
    FIELD_TYPE_INT: 'int',
    FIELD_TYPE_FLOAT: 'float',
    FIELD_TYPE_DATA_BINARY: 'binaryData',
  };
  const filterConfig = reactive({
    groups: [],
    name: '',
    offset: null,
    limit: null,
    stages: undefined,
    projection: {
      tagFamilies: [
        {
          name: '',
          tags: [],
        },
      ],
    },
  });
  const filterConfigBaseKeys = ['groups', 'name', 'offset', 'limit', 'stages', 'projection'];
  const data = reactive({
    fields: [],
    tableFields: [],
    handleFields: '',
    group: route.params.group,
    name: route.params.name,
    type: route.params.type,
    tagFamily: 0,
    tagFamilies: [],
    options: [],
    resourceData: null,
    timeValue: null,
    loading: false,
    total: 100,
    tableTags: [],
    tableData: [],
    code: null,
    codeStorage: [],
    byStages: false,
  });
  const showTracesDialog = ref(false);
  const traceData = ref(null);
  const tableHeader = computed(() => {
    return data.tableTags.concat(data.tableFields);
  });
  watch(
    () => data.handleFields,
    () => {
      if (data.handleFields.length > 0) {
        data.tableTags = data.tableTags.map((item) => {
          item.label = `${data.options[data.tagFamily].label}.${item.name}`;
          return item;
        });
      } else {
        data.tableTags = data.tableTags.map((item) => {
          item.label = item.name;
          return item;
        });
      }
    },
  );
  watch(
    () => route,
    () => {
      data.group = route.params.group;
      data.name = route.params.name;
      data.type = route.params.type;
      data.tableData = [];
      data.tableTags = [];
      data.tableFields = [];
      data.fields = [];
      data.handleFields = '';

      if (data.group && data.name && data.type) {
        initCode();
        initData();
      }
    },
    {
      immediate: true,
      deep: true,
    },
  );

  function initCode() {
    let index = data.codeStorage.findIndex((item) => {
      return item.params.group === route.params.group && item.params.name === route.params.name;
    });
    if (index >= 0) {
      data.code = data.codeStorage[index].params.code;
    } else {
      let timeRange = {
        timeRange: {
          begin: new Date(new Date() - Last15Minutes),
          end: new Date(),
        },
      };
      timeRange = jsonToYaml(timeRange).data;
      data.code = `${timeRange}offset: 0
limit: 10
orderBy:
  indexRuleName: ""
  sort: SORT_UNSPECIFIED
`;
    }
  }
  function setCode() {
    const json = yamlToJson(data.code);
    if (data.byStages) {
      json.data.stages = ['hot'];
    } else {
      delete json.data.stages;
    }
    if (!json.data.hasOwnProperty('timeRange')) {
      json.data.timeRange = {
        begin: '',
        end: '',
      };
    }
    json.data.timeRange.begin = data.timeValue ? new Date(data.timeValue[0]) : null;
    json.data.timeRange.end = data.timeValue ? new Date(data.timeValue[1]) : null;
    data.code = jsonToYaml(json.data).data;
  }
  async function initData() {
    $loadingCreate();
    const response = await getResourceOfAllType(data.type, data.group, data.name);
    $loadingClose();
    if (response.error) {
      ElMessage({
        message: `Get ${data.type} failed: ${response.error.message}`,
        type: 'error',
      });
      return;
    }
    data.resourceData = response[data.type];
    data.tableTags = response[data.type].tagFamilies[0].tags.map((item) => {
      item.label = item.name;
      return item;
    });
    data.options = response[data.type].tagFamilies.map((item, index) => {
      return { label: item.name, value: index };
    });
    data.tagFamily = 0;
    data.fields = response[data.type].fields ? response[data.type].fields : [];
    handleCodeData();
  }
  async function handleTracesData(trace) {
    traceData.value = trace;
  }
  async function getTableData() {
    data.tableData = [];
    data.loading = true;
    setTableFilterConfig();
    let paramList = JSON.parse(JSON.stringify(filterConfig));
    if (data.type === CatalogToGroupType.CATALOG_MEASURE) {
      paramList.tagProjection = paramList.projection;
      if (data.handleFields.length > 0) {
        paramList.fieldProjection = {
          names: data.handleFields,
        };
      }
      delete paramList.projection;
    }
    paramList.name = data.resourceData.metadata.name;
    paramList.groups = [data.resourceData.metadata.group];
    const res = await getTableList(paramList, data.type);
    data.loading = false;
    if (res.error) {
      ElMessage({
        message: `Get ${data.type} failed: ${res.error.message}`,
        type: 'error',
      });
      return;
    }
    handleTracesData(res.trace);
    if (data.type === CatalogToGroupType.CATALOG_STREAM) {
      setTableData(res.elements);
    } else {
      setTableData(res.dataPoints);
    }
  }
  function setTableData(elements) {
    const tags = data.resourceData.tagFamilies[data.tagFamily].tags;
    const tableFields = data.tableFields;
    for (const item of elements) {
      const dataItem = {};
      for (const tag of item.tagFamilies[0].tags) {
        const index = tags.findIndex((item) => item.name === tag.key);
        const type = tags[index].type;
        if (tag.value[tagType[type]] === null) {
          dataItem[tag.key] = 'Null';
        } else {
          dataItem[tag.key] = tag.value[tagType[type]]?.value ?? tag.value[tagType[type]];
        }
      }
      if (data.type === CatalogToGroupType.CATALOG_MEASURE && tableFields.length > 0) {
        item.fields.forEach((field) => {
          const name = field.name;
          const fieldType =
            tableFields.filter((tableField) => {
              return tableField.name === name;
            })[0].fieldType || '';
          if (field.value[fieldTypes[fieldType]] === null) {
            dataItem[name] = 'Null';
          } else {
            dataItem[name] = field.value[fieldTypes[fieldType]]?.value ?? field.value[fieldTypes[fieldType]];
          }
        });
      }
      dataItem.timestamp = item.timestamp;
      data.tableData.push(dataItem);
    }
    data.loading = false;
  }
  function setTableFilterConfig() {
    const tagFamily = data.resourceData.tagFamilies[data.tagFamily];
    const tagsList = [];
    tagFamily.tags.forEach((item) => {
      tagsList.push(item.name);
    });
    filterConfig.projection.tagFamilies[0].name = tagFamily.name;
    filterConfig.projection.tagFamilies[0].tags = tagsList;
    filterConfig.stages = data.byStages ? filterConfig.stages : undefined;
  }
  function changeTagFamilies() {
    data.tableTags = data.resourceData.tagFamilies[data.tagFamily].tags.map((item) => {
      item.label = item.name;
      if (data.handleFields.length > 0) {
        item.label = `${data.options[data.tagFamily].label}.${item.name}`;
      }
      return item;
    });
    getTableData();
  }
  function handleCodeData() {
    const json = yamlToJson(data.code).data;
    Object.keys(filterConfig).forEach((key) => {
      if (!filterConfigBaseKeys.includes(key)) {
        delete filterConfig[key];
      }
    });
    filterConfig.offset = json.offset !== undefined ? json.offset : 0;
    filterConfig.limit = json.limit !== undefined ? json.limit : 10;
    delete filterConfig.timeRange;
    if (json.timeRange && !isNaN(Date.parse(json.timeRange.begin)) && !isNaN(Date.parse(json.timeRange.end))) {
      data.timeValue = [json.timeRange.begin, json.timeRange.end];
      filterConfig.timeRange = json.timeRange;
    } else if (json.timeRange.begin || json.timeRange.end) {
      data.timeValue = [];
      ElMessage({
        dangerouslyUseHTMLString: true,
        showClose: true,
        message: 'Warning: Wrong time type',
        type: 'warning',
        duration: 5000,
      });
    } else {
      data.timeValue = [];
    }
    json.orderBy ? (filterConfig.orderBy = json.orderBy) : delete filterConfig.orderBy;

    // Add other fields from json to filterConfig
    Object.keys(json).forEach((key) => {
      if (!['offset', 'limit', 'timeRange', 'orderBy'].includes(key)) {
        filterConfig[key] = json[key];
      }
    });

    getTableData();
  }
  function searchTableData() {
    yamlRef.value
      .checkYaml(data.code)
      .then(() => {
        handleCodeData();
      })
      .catch((err) => {
        ElMessage({
          dangerouslyUseHTMLString: true,
          showClose: true,
          message: err.message,
          type: 'error',
          duration: 5000,
        });
      });
  }

  function refreshTableData() {
    initCode();
    handleCodeData();
  }

  function changeDatePicker() {
    const json = yamlToJson(data.code);
    if (!json.data.hasOwnProperty('timeRange')) {
      json.data.timeRange = {
        begin: '',
        end: '',
      };
    }
    json.data.timeRange.begin = data.timeValue ? data.timeValue[0] : null;
    json.data.timeRange.end = data.timeValue ? data.timeValue[1] : null;
    data.code = jsonToYaml(json.data).data;
  }
  function changeFields() {
    data.tableFields = data.handleFields.map((fieldName) => {
      let item = data.fields.filter((field) => {
        return field.name === fieldName;
      })[0];
      item.label = item.name;
      return item;
    });
    getTableData();
  }
</script>

<template>
  <div>
    <el-card shadow="always">
      <template #header>
        <FormHeader :fields="data" />
      </template>
      <el-row>
        <el-col :span="16">
          <div class="flex align-item-center" style="height: 40px; width: 100%">
            <el-select
              v-model="data.tagFamily"
              @change="changeTagFamilies"
              filterable
              placeholder="Please select"
              style="width: 200px"
            >
              <el-option v-for="item in data.options" :key="item.value" :label="item.label" :value="item.value" />
            </el-select>
            <el-select
              v-if="data.type === CatalogToGroupType.CATALOG_MEASURE"
              v-model="data.handleFields"
              collapse-tags
              style="margin: 0 0 0 10px; flex: 0 0 300px"
              @change="changeFields"
              filterable
              multiple
              placeholder="Please select Fields"
            >
              <el-option v-for="item in data.fields" :key="item.name" :label="item.name" :value="item.name" />
            </el-select>
            <el-date-picker
              @change="changeDatePicker"
              style="margin: 0 10px; flex: 1 1 0"
              v-model="data.timeValue"
              type="datetimerange"
              :shortcuts="Shortcuts"
              range-separator="to"
              start-placeholder="begin"
              end-placeholder="end"
              :align="`right`"
            />
            <el-checkbox
              v-model="data.byStages"
              @change="setCode"
              label="By stages"
              size="large"
              style="margin-right: 10px"
            />
            <el-button :icon="Search" @click="searchTableData" style="flex: 0 0 auto" color="#6E38F7" plain />
          </div>
        </el-col>
        <el-col :span="8">
          <div class="flex align-item-center justify-end" style="height: 30px">
            <el-button :icon="RefreshRight" @click="refreshTableData" plain />
          </div>
        </el-col>
      </el-row>
      <CodeMirror ref="yamlRef" v-model="data.code" mode="yaml" style="height: 200px" :lint="true" :readonly="false" />
    </el-card>
    <el-card shadow="always">
      <div style="margin-bottom: 10px; display: flex; justify-content: flex-end">
        <el-button :icon="TrendCharts" :disabled="!traceData" @click="showTracesDialog = true" plain>
          <span>Debug Trace</span>
        </el-button>
      </div>
      <MeasureAndStreamTable
        :tableData="data.tableData"
        :tableHeader="tableHeader"
        :loading="data.loading"
        :showSelection="true"
        :showIndex="true"
        :showTimestamp="true"
        emptyText="No data yet"
      />
    </el-card>
  </div>
  <el-dialog
    v-model="showTracesDialog"
    width="90%"
    :destroy-on-close="true"
    @closed="showTracesDialog = false"
    class="trace-dialog"
  >
    <div style="max-height: 74vh; overflow-y: auto">
      <TraceTree :trace="traceData" />
    </div>
  </el-dialog>
</template>

<style lang="scss" scoped>
  :deep(.el-card) {
    margin: 15px;
  }
</style>
