<template>
  <div class="v-cascader" v-clickoutside="clickOutSide">
    <v-input
      v-if="!props.multiple"
      v-model="label"
      :clearable="clearable"
      :disabled="disabled"
      :arrowShow="arrowShow"
      :placeholder="placeholder"
      :readonly="!filterable"
      @clear="handleClear"
      @input="handleInput"
      componentType="cascader"
    >
    </v-input>
    <div
      v-if="props.multiple"
      :class="['v-cascader-input', { disabled }]"
      ref="cCascaderInput"
      @mouseenter="handleMouseEnter"
      @mouseleave="handleMouseLeave"
    >
      <div class="c_multiple_box">
        <p class="placeholder" v-if="!labelArr.length && !filterable">
          {{ placeholder }}
        </p>
        <ul class="v-cascader-multiple">
          <template v-if="!collapseTags">
            <li v-for="(item, index) of labelArr" :key="index">
              <span>{{ item.label }}</span>
              <i
                :class="['iconfont icon-guanbi', 'v-clear']"
                @click="handleClearMultiple(item)"
              ></i>
            </li>
          </template>
          <li v-if="collapseTags && labelArr.length">
            <span>{{ labelArr[0].label }}</span>
            <i
              :class="['iconfont icon-guanbi', 'v-clear']"
              @click="handleClearMultiple(labelArr[0])"
            ></i>
          </li>
          <li v-if="collapseTags && labelArr.length > 1">
            <span>+ {{ labelArr.length - 1 }}</span>
          </li>
        </ul>
        <v-input
          v-if="filterable"
          v-model="searchValue"
          :disabled="disabled"
          :placeholder="placeholder"
          :arrowShow="arrowShow"
          :componentType="!props.multiple && 'cascader'"
          @input="handleInput"
          @clear="handleClear"
          :class="[{ c_filterable_input: filterable && props.multiple }]"
        ></v-input>
      </div>
      <i
        v-show="!isClear"
        :class="['iconfont icon-xiajiantou']"
        ref="cCascaderArrow"
      ></i>
      <i
        v-if="isClear"
        @click="handleClearMultiple()"
        :class="['iconfont icon-guanbi', 'v-clear']"
      ></i>
    </div>
    <div class="v-cascader-box" ref="cCascaderBox">
      <v-cascader-option-group
        v-for="(option, index) in proxyOptions"
        v-show="show"
        :key="index"
        :itemFocus="itemFocus"
        :option="option"
      >
      </v-cascader-option-group>
      <v-cascader-search
        v-if="!show"
        :searchOptions="searchOptions"
      ></v-cascader-search>
      <div class="v-popper__arrow" style="left: 35px;"></div>
    </div>
  </div>
</template>

<script>
import vCascaderSearch from "./components/Cascader-search";
import VairCascader from "../../types/cascader";
import directives from "../../directives/clickoutside";
import { ref, provide, watchEffect, watch, onMounted } from "vue";
export default {
  name: "cascader",
  directives: {
    ...directives
  },
  components: {
    vCascaderSearch
  },
  props: VairCascader,
  setup(props, ctx) {
    const label = ref("");
    const oldValue = ref("");
    const searchValue = ref("");
    const isClear = ref(false);
    const arrowShow = ref(false);
    const show = ref(true);
    const cCascaderBox = ref(null);
    const cCascaderInput = ref(null);
    const cCascaderArrow = ref(null);
    const itemFocus = ref([]);
    const labelArr = ref([]);
    const searchOptions = ref([]);
    const proxyOptions = ref([]);
    const activeOptions = ref([]);
    const allCombinations = ref([]);

    onMounted(() => {
      watchEffect(() => {
        cCascaderInput.value &&
          (cCascaderInput.value.style.borderColor = arrowShow.value
            ? "#409eff"
            : "");
        cCascaderArrow.value &&
          (cCascaderArrow.value.style.transform = `rotate(${
            arrowShow.value ? 180 : 0
          }deg)`);
        cCascaderBox.value &&
          (cCascaderBox.value.style.transform = `rotateX(${
            arrowShow.value ? 0 : 90
          }deg)`);
      });

      watchEffect(() => {
        allCombinations.value = [];
        optionsAllCombinations(props.options, [], [], "", undefined, 0);
      });

      watchEffect(() => {
        proxyOptions.value = [[]];
        allCombinations.value.forEach(row => {
          row.level === 0 && proxyOptions.value[0].push(row);
        });
        if (activeOptions.value[0]) {
          const value = activeOptions.value[activeOptions.value.length - 1];
          setProxyOptions(value);
        }
      });

      watchModel();
    });

    watch(
      () => props.modelValue,
      () => {
        watchModel();
      }
    );

    const watchModel = () => {
      activeOptions.value = [];
      if (!Array.isArray(props.modelValue)) {
        throw new TypeError(
          "cascader component must have v-model property, and the binding value must be an array"
        );
      }
      if (props.props.multiple) {
        props.modelValue.forEach(item => Array.isArray(item) && activeOptionsPush(item));
        multiple(activeOptions.value);
      } else {
        activeOptionsPush(props.modelValue);
        selected(activeOptions.value[0]);
      }
    };

    const activeOptionsPush = data => {
      const target = assembleTheSubscript(data);
      allCombinations.value.forEach(item => {
        const player = assembleTheSubscript(item.pathIndex);
        target === player && activeOptions.value.push(item);
      });
    };

    const assembleTheSubscript = data => {
      var str = "";
      data.forEach(item => (str += `${item},`));
      return str;
    };

    const optionsAllCombinations = (
      options,
      pathIndex,
      pathNode,
      pathLabel,
      parent,
      level
    ) => {
      options.forEach(value => {
        const data = {
          id: value.value,
          parent,
          value,
          level,
          pathNode,
          children: [],
          pathIndex: pathIndex.concat(value.value),
          pathLabel: pathLabel + value.label,
          hasChildren:
            Array.isArray(value.children) && value.children.length > 0
        };
        allCombinations.value.push(data);
        const targte = allCombinations.value[allCombinations.value.length - 1];
        targte.pathNode = targte.pathNode.concat(targte);
        if (data.hasChildren) {
          optionsAllCombinations(
            value.children,
            pathIndex.concat(value.value),
            pathNode.concat(
              allCombinations.value[allCombinations.value.length - 1]
            ),
            pathLabel + value.label + " / ",
            data,
            level + 1
          );
        }
        if (parent) {
          parent.children.push(data);
        }
      });
    };

    const setProxyOptions = value => {
      if (value.level) {
        proxyOptions.value[value.level] = value.parent.children;
        value.parent.parent && setProxyOptions(value.parent);
      }
    };

    const clickOutSide = bool => {
      if (!bool) {
        setTimeout(() => {
          if (props.props.multiple) {
            searchValue.value = "";
            show.value = true;
          } else {
            label.value = oldValue.value;
          }
        }, 100);
      }
      if (props.disabled) return;
      setArrowShow(bool && !arrowShow.value);
    };

    const setOptions = option => {
      const { level } = option;
      proxyOptions.value[level + 1] = option.children;
      proxyOptions.value.splice(level + 2);
      itemFocus.value[level] = option;
      itemFocus.value.splice(level + 1);
      setArrowShow(true);
    };

    const selected = option => {
      label.value = option?.pathLabel || "";
      if (!props.showAllLevels) {
        label.value = option?.value?.label || "";
      }
      oldValue.value = label.value;
    };

    const setSelected = (option, type) => {
      if (type === "search") {
        label.value = oldValue.value;
        return;
      }
      ctx.emit("update:modelValue", option.pathIndex);
      ctx.emit("change", option.pathIndex);
    };

    const setMultipleSelected = (option, checked, offon = false) => {
      if (option.hasChildren && !props.props.checkStrictly && !offon) {
        deepOptionChildren(option.children, checked);
      } else {
        const index = activeOptions.value.findIndex(item => item === option);
        if (checked && index === -1) {
          activeOptions.value.push(option);
        } else if (!checked && index !== -1) {
          activeOptions.value.splice(index, 1);
        }
      }
      const pathIndex = multiple();
      ctx.emit("update:modelValue", pathIndex);
      ctx.emit("change", pathIndex);
    };

    const multiple = () => {
      labelArr.value = [];
      const pathIndex = [];
      activeOptions.value.forEach(option => {
        pathIndex.push(option.pathIndex);
        labelArr.value.push({
          value: option,
          label: !props.showAllLevels ? option.value.label : option.pathLabel
        });
      });
      return pathIndex;
    };

    const deepOptionChildren = (option, checked) => {
      option.forEach(item => {
        if (item.hasChildren) {
          deepOptionChildren(item.children, checked);
        } else {
          const index = activeOptions.value.findIndex(row => item === row);
          if (checked && index === -1) {
            activeOptions.value.push(item);
          } else if (!checked && index !== -1) {
            activeOptions.value.splice(index, 1);
          }
        }
      });
    };

    const handleClearMultiple = option => {
      if (props.disabled) return;
      setArrowShow(!arrowShow.value);
      if (option) {
        labelArr.value = labelArr.value.filter(
          item => item.value !== option.value
        );
        const index = activeOptions.value.findIndex(
          item => item === option.value
        );
        index !== -1 && activeOptions.value.splice(index, 1);
        ctx.emit("remove-tag", option.value);
      } else {
        labelArr.value = [];
        activeOptions.value = [];
      }
      const pathIndex = multiple();
      ctx.emit("update:modelValue", pathIndex);
      ctx.emit("change", pathIndex);
    };

    const handleClear = () => {
      ctx.emit("update:modelValue", []);
      ctx.emit("change", []);
      setArrowShow(false);
    };

    const setArrowShow = (bool, check = true) => {
      arrowShow.value = bool;
      if (props.filterable && bool && check && !props.props.multiple) {
        show.value = true;
      }
    };

    const handleMouseEnter = () => {
      if (props.disabled) return;
      isClear.value = labelArr.value.length > 0;
    };

    const handleMouseLeave = () => {
      isClear.value = false;
    };

    const handleInput = value => {
      searchOptions.value = [];
      allCombinations.value.forEach(item => {
        if (item.pathLabel.indexOf(value) !== -1) {
          if (props.props.checkStrictly || !item.hasChildren) {
            searchOptions.value.push(item);
          }
        }
      });
      setArrowShow(true);
      show.value = value === "" ? true : false;
    };

    const getCheckedNodes = () => {
      return activeOptions.value;
    };

    const clearCheckedNodes = () => {
      handleClear();
    };

    provide("setOptions", setOptions);
    provide("Props", props.props);
    provide("activeOptions", activeOptions);
    provide("setSelected", setSelected);
    provide("setArrowShow", setArrowShow);
    provide("setMultipleSelected", setMultipleSelected);

    return {
      show,
      label,
      isClear,
      itemFocus,
      searchValue,
      arrowShow,
      labelArr,
      cCascaderBox,
      clickOutSide,
      proxyOptions,
      handleClear,
      cCascaderArrow,
      cCascaderInput,
      getCheckedNodes,
      handleInput,
      searchOptions,
      handleMouseEnter,
      clearCheckedNodes,
      handleMouseLeave,
      handleClearMultiple
    };
  }
};
</script>

<style lang='less' scoped>
@import url("./index.less");
</style>
