<script>
import isMatch from "lodash/isMatch"
import keyBy from "lodash/keyBy"
import config from "../../managers/config"

config.defaults({
  "Menu.menuTrigger": "hover",
  "Menu.collapseTransition": true,
  "Menu.queryKey": "mm_menu_active",
  "Menu.indent": 20,
})

export default {
  name: "mm-menu",

  components: {
    "mm-menu-collapse-transition": {
      functional: true,
      render(createElement, context) {
        const data = {
          props: {
            mode: "out-in",
          },
          on: {
            beforeEnter(el) {
              el.style.opacity = 0.2
            },

            enter(el) {
              el.classList.add("mm-opacity-transition")
              el.style.opacity = 1
            },

            afterEnter(el) {
              el.classList.remove("mm-opacity-transition")
              el.style.opacity = ""
            },

            beforeLeave(el) {
              if (!el.dataset) {
                el.dataset = {}
              }

              if (el.classList.contains("mm-menu--collapse")) {
                el.classList.remove("mm-menu--collapse")
                el.dataset.oldOverflow = el.style.overflow
                el.dataset.scrollWidth = el.clientWidth
                el.classList.add("mm-menu--collapse")
              } else {
                el.classList.add("mm-menu--collapse")
                el.dataset.oldOverflow = el.style.overflow
                el.dataset.scrollWidth = el.clientWidth
                el.classList.remove("mm-menu--collapse")
              }

              el.style.width = el.scrollWidth + "px"
              el.style.overflow = "hidden"
            },

            leave(el) {
              el.classList.add("horizontal-collapse-transition")
              el.style.width = el.dataset.scrollWidth + "px"
            },
          },
        }
        return createElement("transition", data, context.children)
      },
    },
  },

  model: {
    prop: "defaultActive",
    event: "select",
  },
  provide() {
    return {
      "mm-menu": this,
    }
  },
  props: {
    mode: {
      type: String,
      default: "vertical",
    },
    defaultActive: {
      type: String,
      default: "",
    },
    defaultOpeneds: Array,
    uniqueOpened: Boolean,
    router: Boolean,
    menuTrigger: {
      type: String,
      default: () => config.get("Menu.menuTrigger"),
    },
    collapse: Boolean,
    backgroundColor: String,
    textColor: String,
    activeTextColor: String,
    collapseTransition: {
      type: Boolean,
      default: () => config.get("Menu.collapseTransition"),
    },
    query: {
      type: Boolean,
      default: false,
    },
    queryKey: {
      type: String,
      default: () => config.get("Menu.queryKey"),
    },
    indent: {
      type: [Number, Function],
      default: () => config.get("Menu.indent"),
    },
  },
  data() {
    return {
      activeIndex: this.defaultActive,
      openedMenus: this.defaultOpeneds && !this.collapse ? this.defaultOpeneds.slice(0) : [],
      submenusArr: [],
      itemsArr: [],
    }
  },
  computed: {
    items() {
      return keyBy(this.itemsArr, "index")
    },
    submenus() {
      return keyBy(this.submenusArr, "index")
    },
    hoverBackground() {
      return this.backgroundColor ? this.mixColor(this.backgroundColor, 0.2) : ""
    },
    isMenuPopup() {
      return this.mode === "horizontal" || (this.mode === "vertical" && this.collapse)
    },
  },
  watch: {
    defaultActive: "updateActiveIndex",

    defaultOpeneds(value) {
      if (!this.collapse) {
        this.openedMenus = value
      }
    },

    collapse(value) {
      if (value) {
        this.openedMenus = []
      } else {
        this.openedMenus = this.defaultOpeneds || []
      }

      Object.values(this.submenus).forEach(submenu => {
        submenu.$emit("toggle-collapse", value)
      })
    },
  },
  mounted() {
    this.checkRouterUseable()
    this.$on("item-click", this.handleItemClick)
    this.$on("submenu-click", this.handleSubmenuClick)

    if (this.router || this.query) {
      this.handleRouterAndQuery()
      this.$watch("$route", this.handleRouterAndQuery)
      this.$watch("items", this.handleRouterAndQuery)
    }
  },
  methods: {
    updateActiveIndex(val) {
      const itemAndSubmenus = { ...this.items, ...this.submenus }
      const item =
        itemAndSubmenus[val] ||
        itemAndSubmenus[this.defaultActive]
      if (item) {
        this.activeIndex = item.index
        this.initOpenedMenu()
      } else {
        this.activeIndex = null
      }
    },

    handleRouterAndQuery() {
      const {
        items,
        submenus,
        updateActiveIndex,
        activeIndex,
      } = this

      const menuAndSubmenus = { ...items, ...submenus }
      const activeMenu = menuAndSubmenus[activeIndex]

      if (activeIndex && activeMenu && this.isMenuMatchedRoute(activeMenu)) {
        return
      }

      let matched = false

      for (const index in menuAndSubmenus) {
        const item = menuAndSubmenus[index]
        const { disabled, routeable, indexPath, $options: { name }, selectAble } = item
        if (routeable && this.isMenuMatchedRoute(item) && !disabled && (name === "mm-submenu" ? selectAble : true)) {
          updateActiveIndex(index)
          this.$emit("select", index, indexPath, item)
          matched = true
          break
        }
      }
      if (!matched) {
        this.activeIndex = ""
      }
    },

    isMenuMatchedRoute(menu) {
      const { $route, router, query, queryKey } = this
      const { routeable, resolvedPath, index } = menu

      if (!routeable) {
        return false
      }

      if (router) {
        return resolvedPath.path === $route.path &&
          isMatch($route.query, resolvedPath.query)
      }

      if (query) {
        const activeQuery = $route.query[queryKey]
        return activeQuery === index
      }
    },

    checkRouterUseable() {
      const {
        router,
        query,
        $route,
        $router,
      } = this
      if ((query || router) && (!$route || !$router)) {
        throw new Error("\"Query\" and \"router\" modes can only be used when using \"vue-router\"")
      }
    },

    getColorChannels(color) {
      color = color.replace("#", "")
      if (/^[0-9a-fA-F]{3}$/.test(color)) {
        color = color.split("")
        for (let i = 2; i >= 0; i--) {
          color.splice(i, 0, color[i])
        }
        color = color.join("")
      }
      if (/^[0-9a-fA-F]{6}$/.test(color)) {
        return {
          red: parseInt(color.slice(0, 2), 16),
          green: parseInt(color.slice(2, 4), 16),
          blue: parseInt(color.slice(4, 6), 16),
        }
      } else {
        return {
          red: 255,
          green: 255,
          blue: 255,
        }
      }
    },
    mixColor(color, percent) {
      let { red, green, blue } = this.getColorChannels(color)
      if (percent > 0) {
        // shade given color
        red *= 1 - percent
        green *= 1 - percent
        blue *= 1 - percent
      } else {
        // tint given color
        red += (255 - red) * percent
        green += (255 - green) * percent
        blue += (255 - blue) * percent
      }
      return `rgb(${Math.round(red)}, ${Math.round(green)}, ${Math.round(blue)})`
    },
    addItem(item) {
      if (!this.itemsArr.includes(item)) {
        this.itemsArr.push(item)
      }
    },
    removeItem(item) {
      const index = this.itemsArr.indexOf(item)
      if (~index) {
        this.itemsArr.splice(index, 1)
      }
    },
    addSubmenu(item) {
      if (!this.submenusArr.includes(item)) {
        this.submenusArr.push(item)
      }
    },
    removeSubmenu(item) {
      const index = this.submenusArr.indexOf(item)
      if (~index) {
        this.submenusArr.splice(index, 1)
      }
    },
    openMenu(index, indexPath) {
      let openedMenus = this.openedMenus
      if (openedMenus.indexOf(index) !== -1) {
        return
      }
      // 将不在该菜单路径下的其余菜单收起
      // collapse all menu that are not under current menu item
      if (this.uniqueOpened) {
        this.openedMenus = openedMenus.filter(index => {
          return indexPath.indexOf(index) !== -1
        })
      }
      this.openedMenus.push(index)
    },
    closeMenu(index) {
      const i = this.openedMenus.indexOf(index)
      if (i !== -1) {
        this.openedMenus.splice(i, 1)
      }
    },
    handleSubmenuClick(submenu) {
      const { index, indexPath } = submenu
      let isOpened = this.openedMenus.indexOf(index) !== -1

      if (isOpened) {
        this.closeMenu(index)
        this.$emit("close", index, indexPath)
      } else {
        this.openMenu(index, indexPath)
        this.$emit("open", index, indexPath)
      }
      if (submenu.selectAble) {
        this.handleItemClick(submenu)
      }
    },
    handleItemClick(item) {
      const { index, indexPath } = item
      const oldActiveIndex = this.activeIndex

      this.activeIndex = item.index
      this.$emit("select", index, indexPath, item)

      if (this.mode === "horizontal" || this.collapse) {
        this.openedMenus = []
      }

      const errorHandler = error => {
        this.activeIndex = oldActiveIndex
        if (error) {
          console.error(error)
        }
      }

      if (this.router || this.query) {
        if (this.router) {
          this.routeToItem({ ...item.resolvedPath }, errorHandler)
        } else if (this.query) {
          this.routeToItem({
            query: {
              ...this.$route.query,
              [this.queryKey]: item.index,
            },
          }, errorHandler)
        }
      }
    },
    // 初始化展开菜单
    // initialize opened menu
    initOpenedMenu() {
      const index = this.activeIndex
      const activeItem = this.items[index] || this.submenus[index]
      if (!activeItem || this.mode === "horizontal" || this.collapse) {
        return
      }

      let indexPath = activeItem.indexPath

      // 展开该菜单项的路径上所有子菜单
      // expand all submenus of the menu item
      indexPath.forEach(index => {
        let submenu = this.submenus[index]
        submenu && this.openMenu(index, submenu.indexPath)
      })
    },
    routeToItem(route, onError) {
      try {
        this.$router.push(route, () => {}, onError)
      } catch (e) {
        console.error(e)
      }
    },
    open(index) {
      const { indexPath } = this.submenus[index.toString()]
      indexPath.forEach(i => this.openMenu(i, indexPath))
    },
    close(index) {
      this.closeMenu(index)
    },
  },

  render(h) {
    const component = (
      <ul
        role="menubar"
        key={+this.collapse}
        style={{ backgroundColor: this.backgroundColor || "" }}
        class={{
          "mm-menu--horizontal": this.mode === "horizontal",
          "mm-menu--collapse": this.collapse,
          "mm-menu": true,
        }}
      >
        {this.$slots.default}
      </ul>
    )

    if (this.collapseTransition) {
      return <mm-menu-collapse-transition>{component}</mm-menu-collapse-transition>
    } else {
      return component
    }
  },
}
</script>
