<template>
  <div class="home-view">
    <SiderMenu @on-drag-start="onDragStart" />
    <div id="div3d" v-click-outside="clickOutside3d"></div>
    <div class="siderbar-right">
      <ToolBarPanel v-if="appInited" @openPreviewView="openPreviewView" />
      <!-- <PluginPanel v-if="appInited" /> -->
      <div class="prefab-list-wrap" v-if="sceneLoaded">
        <span class="title">场景预制件</span>
        <Select
          @on-change="selectPrefab"
          :value="uuid"
          style="width: 170px"
          filterable
        >
          <Option v-for="o in objects" :value="o.uuid" :key="o.uuid">
            {{ o.name || o.prefabName }} {{ o.id && `(id=${o.id})` }}
          </Option>
        </Select>
      </div>
      <div class="panel-wrapper">
        <PropertyPanel
          v-if="panelVisible"
          :options="panelOptions"
          @on-change="debounceOnPanelChange"
        />
      </div>
    </div>
    <div v-if="previewDialogVisible" class="prev-modal">
      <iframe src="/preview" title="预览"></iframe>
    </div>
  </div>
</template>

<script>
import { debounce } from 'lodash'
import initScene from '@/mixins/initScene'
import SiderMenu from '@/components/SiderMenu.vue'
import ToolBarPanel from '@/components/ToolBarPanel.vue'
import PluginPanel from '@/components/PluginPanel.vue'
import PropertyPanel from '@/components/PropertyPanel.vue'
import gridImg from '@/assets/grid.jpg'
import DragControl from '@/utils/dragControl'
import storage from '@/utils/storage'
import * as utils from '../utils'
import propertyPanel from '@uino/thing-ui-property-panel'
import locale from '../locale/prefab'

export default {
  name: 'HomeView',
  components: {
    SiderMenu,
    PropertyPanel,
    ToolBarPanel,
    PluginPanel,
  },
  mixins: [initScene],
  data() {
    return {
      draging: false,
      prefabMap: {},
      panelVisible: false,
      panelOptions: null, // 属性面板选项
      panelConfig: null, // 属性面板配置
      focusIn3d: false,
      sceneLoaded: false,
      uuid: '',
      previewDialogVisible: false,
      locale,
    }
  },
  watch: {
    uuid(value, oldValue) {
      if (value === oldValue && value) {
        return
      }

      this.clear(value, oldValue)
      this.updateObjects()

      if (value && value !== oldValue) {
        this.curPrefab = app.query(`[uuid="${value}"]`)[0]
        this.offLocateEvent(this.curPrefab)
        this.clickObject(value)
        this.curPrefabMeta = this.prefabMap[value]
        this.showPanel(this.curPrefab.uuid)
        this.initDragEvent(this.curPrefab)
      }
      this.oldPrefab = this.curPrefab
    },
  },
  methods: {
    updateObjects() {
      let res = []
      if (this.exportData?.objects) {
        res = this.exportData.objects.map((o) => {
          const uuid = this.exportData.resources.uuids[o.uuid]
          return {
            name: o.name,
            id: o.id,
            uuid,
            prefabName: this.prefabMap[uuid]?.prefabName,
          }
        })
      }
      this.objects = res
    },
    clear(value, oldValue) {
      //清除 btn-drag
      document.querySelectorAll('.btn-operate').forEach((item) => item.remove())
      // 清除 drag 控件
      if (this.oldPrefab && this._dragControl && value !== oldValue) {
        try {
          this._dragControl.destroy()
        } catch (e) {
          console.log(e)
        }
      }

      this.hidePanel()
      this.curPrefab = null
      this.panelVisible = false
      this.panelOption = null
      this.panelConfig = null
    },
    selectPrefab(uuid) {
      this.setUUID(uuid)
      // 让相机飞过去
      if (uuid) {
        app.camera.flyTo({
          target: app.query(`[uuid="${uuid}"]`)[0].position, // 只调整位置, 体验好点
          time: 500,
          distance: 80,
        })
      }
    },
    setUUID(uuid) {
      this.uuid = uuid
    },
    clickObject(uuid, callback) {
      const prefab = app.query(`[uuid="${uuid}"]`)[0]
      if (prefab?.children[0]) {
        prefab.children[0].on(
          'click',
          () => {
            callback && callback()
            prefab.children[0].off('click', 'TAG_CLICK')
          },
          'TAG_CLICK'
        )

        prefab.children[0].trigger('click')
      }
    },
    clickOutside3d() {
      if (this.isCreatePrefab) {
        this.focusIn3d = true
        this.isCreatePrefab = false
      } else {
        this.focusIn3d = false
      }
    },
    onPanelChange(value, keys, config, options) {
      if (this.lock || !keys) return
      this.panelConfig = config
      // 单独处理 name id, 需要绑在对象上
      // this.curPrefab.userData._name = config.name // 这里不能改 curPrefab 的 name，会导致属性面板国际化失效
      this.curPrefab.id = config.id
      this.curPrefab.name = config.name

      this.autoSave()
    },
    onDragStart(e) {
      this.curPrefab = null
      this.draging = true
      this.createPrefabObject(e)
    },
    initGround() {
      // 获取场景的高度
      const sceneHeight = app.root.boundingBox.halfSize[1]
      const image = new THING.ImageTexture({
        url: gridImg,
      })
      image.waitForComplete().then(() => {
        new THING.Plane(512, 512, {
          position: [0, -sceneHeight, 0],
          style: {
            image,
            uv: {
              repeat: [10, 10],
            },
          },
        })
      })
    },
    showPanel() {
      if (this.lock) return
      const prefabMeta = this.curPrefabMeta
      if (!prefabMeta) {
        return
      }

      let propsData = propertyPanel.updateConfigToOptions(
        this.genPanelOptions(
          this.getCurPrefabPropsData(),
          prefabMeta.prefabName
        ),
        this.getPanelConfig(this.uuid)
      )

      this.panelOptions = JSON.parse(JSON.stringify(propsData))
      // 显示面板
      this.panelVisible = true
    },
    hidePanel() {
      this.panelVisible = false
    },
    async createPrefabObject(e) {
      console.log(e.usePath)
      const instance = new THING.PrefabObject({
        // name: e.name,
        url: e.usePath,
        parent: window.rootObject,
        // prefabName: e.name,
        autoCompile: false,
      })
      await this.autoSave()

      const uuid = instance.uuid

      const curPrefabMeta = {
        // instance,
        loaded: false, // 预制件是否加载完成
        panelOptions: null,
        name: e.name,
        prefabName: e.name, // 这个名字是固定的，和属性面板国际化有关
      }

      this.prefabMap[uuid] = curPrefabMeta

      this.setUUID(uuid)
      this.isCreatePrefab = true // 用于解决第一次拖拽物体到场景中时，可以 Delete 删除

      instance.waitForComplete().then(
        () => {
          curPrefabMeta.loaded = true

          this.offLocateEvent(instance)
          this.focusPrefab(instance)

          this.autoSave()
        },
        (err) => {
          console.error('创建预制件错误:', err)
          instance.destroy()
        }
      )
    },

    // 获取并组合预制件的 props
    getCurPrefabPropsData() {
      const data = {}

      // 这里获取预制件所有 children 的 components
      const customComponents = []
      this.curPrefab.children.forEach((child) => {
        for (let [key, val] of child.components) {
          val.name = key // 加上名字，val 没有name
          customComponents.push(val)
        }
      })

      // 找出 props 有 public 的项
      const publicProps = {}
      customComponents
        .filter((comp) => utils.isObject(comp.props)) // 过滤有 props 属性的组件, 这里可能选到非预制件组件?
        .forEach((comp) => {
          // 将 public 属性提取为共有
          for (let k in comp.props) {
            const v = comp.props[k]
            if (v.public) {
              publicProps[v.name] = v // prop.name 重复时自动覆盖掉
              Reflect.deleteProperty(v, v.name) // 删除掉 public 属性，因为属性面板不支持 hidden
            }
            if (v.type === 'action') {
              Reflect.deleteProperty(v, v.name) // 删除掉 action
            }
          }
          if (Object.keys(comp.props).length) {
            data[comp.name] = comp.props
          }
          return comp
        })

      // 添加 Public
      if (Object.keys(publicProps).length) {
        data.PublicComponent = Object.values(publicProps)
      }

      return data
    },
    // 将 props 转换成 propertyPanel 所需数据, prefix 用于国际化
    genPanelOptions(props, prefix) {
      let optionStart = {
        id: {
          name: 'TwinID',
          type: 'text',
          default: '',
          placeholder: '请输入 ID',
        },
        name: {
          name: 'name',
          type: 'text',
          default: '',
          placeholder: '请输入 name',
        },
      }
      let optionCenter = {}
      let optionEnd = {}

      // 属性面板不支持 showInPanel false 进行隐藏, 只能删除字段
      // 为了简便, 设置一个不可能的条件, 用于将 action 和 public 隐藏
      const hiddenConditions = {
        conditions: [['._not_exist_key', '$eq', true]],
      }

      for (let key in props) {
        const children = Object.values(props[key])
          .map((item, i) => {
            // FIX: tags方便识别:有国际化使用国际化，没有国际化使用name，没有name
            item.$tags = Object.keys(props[key])[i]
            return item
          })
          .reduce((a, b) => {
            const localName = b.$tags
            a[localName] = {
              ...b,
              type: b.type,
              name:
                this.locale[`${prefix}.${key}.${b.$tags}`] ||
                b.name ||
                `${prefix}.${key}.${b.$tags}`, // 国际化优先级>props里的name
              default: b.value,
            }

            // 1 先提取公共
            if (b.propertyPanel === 'public') {
              optionCenter[localName] = {
                ...a[localName],
                name: `${prefix}.PublicComponent.${localName}`,
              }
            }

            // 2 再设置隐藏
            if (this.isHiddenField(b)) {
              a[localName].showInPanel = hiddenConditions
            }
            return a
          }, {})

        optionEnd[key] = {
          type: 'group',
          fold: false,
          name: `${prefix}.${key}`,
          children,
        }
        const hiddenGroup = Object.values(children).every(
          (c) => c && c.showInPanel === hiddenConditions
        )
        if (hiddenGroup) {
          optionEnd[key].showInPanel = hiddenConditions
        }
      }

      return { ...optionStart, ...optionCenter, ...optionEnd }
    },
    // 是否是隐藏字段
    isHiddenField(field) {
      return (
        ['public', 'hidden'].includes(field.propertyPanel) ||
        field.type === 'action'
      )
    },
    saveSceneInfo() {
      // 保存视角到本地
      storage.setItem('defaultSceneInfo', {
        position: THING.Utils.cloneObject(app.camera.position),
        target: THING.Utils.cloneObject(app.camera.target),
      })
    },
    // 鼠标移动时, 更新预制件位置
    initAppEvent() {
      // 更新预制件位置事件
      app.on('mousemove', (e) => {
        this.saveSceneInfo()
        if (!this.draging || !this.curPrefabMeta) return

        // 更新实例位置
        const { loaded } = this.curPrefabMeta

        if (
          this.curPrefab &&
          loaded &&
          e.pickedPosition &&
          !this.operateBtnMove
        ) {
          this.curPrefab.position = e.pickedPosition
          this.autoSave()
        }
      })

      document.addEventListener('mouseup', () => {
        this.draging = false
      })

      // 防止第一人称 3d 拖拽到页面 ui 后释放，再回到 3d 时 鼠标失控的问题
      document.body.ondragstart = () => false

      // 绑定 Delete 删除物体事件
      app.on('keydown', (e) => {
        if (
          this.focusIn3d &&
          this.curPrefab &&
          (e.code === 'Delete' || e.code === 'Backspace')
        ) {
          this.destroyCurPrefab()
          this.autoSave()
        }
      })

      app.on('click', (e) => {
        this.focusPrefab(e.object?.parent)

        this.focusIn3d = true
      })
    },
    getExportData(root) {
      return app
        .query('*')
        .waitForComplete()
        .then(() => {
          const exporter = new THING.SceneObjectExporter()
          //导出
          return exporter.output(root)
        })
        .catch((e) => {
          console.log(e)
        })
    },
    getPanelConfig(uuid) {
      const { exportData } = this
      if (exportData?.objects) {
        const uuidIndex = exportData.resources.uuids.findIndex(
          (item) => item === uuid
        )
        // 1. 找 PrefabObject
        const { externalComponents = {} } = exportData.objects[uuidIndex] || {}
        // 2. 找 Entity
        const config = {}
        for (let key in externalComponents) {
          // 给 物体组件
          externalComponents[key].forEach((item) => {
            const external = {}
            for (let name in item.external) {
              external[name] = item.external[name].value
            }
            config[item.name] = external
          })
        }

        config.name = this.curPrefab.name // this.curPrefab.userData._name
        config.id = this.curPrefab.id

        return config
      }

      return {}
    },
    updateExportData(data) {
      if (!this.panelConfig) return

      const uuidIndex = data.resources.uuids.findIndex(
        (item) => item === this.uuid
      )
      if (uuidIndex !== -1) {
        const { externalComponents = {} } = data.objects[uuidIndex] || {}
        for (let key in externalComponents) {
          this._setPanelPropValue(externalComponents, key, this.panelConfig)
        }
      }
    },
    // 给预制件 external 属性赋值
    _setPanelPropValue(externalComponents, key, panelConfig) {
      externalComponents[key].forEach((item) => {
        if (!item.external) return
        for (let name in item.external) {
          const prop = item.external[name]
          try {
            prop.value =
              prop.propertyPanel === 'public'
                ? panelConfig[name]
                : panelConfig[item.name][name]
          } catch (e) {
            console.warn(e)
          }
        }
      })
    },
    async autoSave() {
      const data = await this.getExportData(window.rootObject)

      if (data) {
        this.exportData = data
        this.updateExportData(data)
        storage.setItem('prefabList', data)
      }
    },
    // 聚焦预制件
    async focusPrefab(obj) {
      const uuid = obj?.type === 'PrefabObject' ? obj.uuid : null
      this.setUUID(uuid)
    },
    openPreviewView(visible) {
      this.previewDialogVisible = visible
    },
    destroyCurPrefab() {
      Reflect.deleteProperty(this.prefabMap, this.uuid)
      this.setUUID(null)
      this.curPrefab && this.curPrefab.destroy()
      this.curPrefab = null
      this.autoSave()

      this.hidePanel()
    },
    async initDragEvent(prefab) {
      if (!prefab) return
      if (this.oldPrefab && prefab === this.oldPrefab) return
      document.querySelectorAll('.btn-operate').forEach((item) => item.remove())
      await prefab.waitForComplete()
      const object = prefab.children[0]
      await object.waitForComplete()

      this._dragControl = new DragControl(object, this)
      this._dragControl.on('btndown', () => {
        this.focusIn3d = true
        this.operateBtnMove = true
      })
      this._dragControl.on('btnmove', ({ downBtnId }) => {
        this.operateBtnMove = true
        this.draging = true
        if (/translate/.test(downBtnId)) {
          prefab.position = object.position
        }
        this.autoSave()
      })
      this._dragControl.on('btnup', () => {
        this.operateBtnMove = false
        prefab.angles = object.angles
        object.localAngles = [0, 0, 0]
        this.autoSave()
      })
    },
    // 采集页面还是需要自己创建预制件，不能导入。要屏蔽 locate 事件
    loadLocalPrefabData() {
      return new Promise((resolve, reject) => {
        try {
          let prefabList = storage.getItem('prefabList')
          if (!prefabList || !prefabList.objects) {
            return resolve()
          }
          this.exportData = prefabList

          window.importer.parse(
            THING.SceneResourceType.Object,
            prefabList,
            () => {
              prefabList.objects?.forEach((obj) => {
                const uuid = prefabList.resources.uuids[obj.uuid]
                this.initPrefabMeta(uuid)
              })
              // 所有预制件加载完毕，这里定义了一个事件，用于预制件内部监听。
              app.trigger('AllPrefabsLoadedEvent')

              resolve()
            },
            null,
            null,
            {
              autoCompile: false,
            }
          )
        } catch (e) {
          console.warn(e)
          reject(e)
        }
      })
    },
    offLocateEvent(obj) {
      obj?.children.forEach((item) =>
        item.off('click', 'baseAbilityLocateEvent')
      )
    },
    // 给场景中所有预制件初始化
    initPrefabMeta(uuid, index = 0, panelOptions = null) {
      const instance = app.query(`[uuid='${uuid}']`)[index]
      if (!instance) {
        return
      }

      instance.parent = window.rootObject // 指定 parent 否则导出时会被忽略

      const curPrefabMeta = {
        loaded: false, // 预制件是否加载完成
        panelOptions: panelOptions,
        name: instance.name,
        prefabName: instance.bundle.info.name,
      }

      this.prefabMap[instance.uuid] = curPrefabMeta // 添加到 map 中
      this.offLocateEvent(instance)

      return instance
    },
  },
  async mounted() {
    this.debounceOnPanelChange = debounce(this.onPanelChange, 200)
    await this.initScene()

    // 创建一个地面
    this.initGround()

    this.initAppEvent()

    this.updateObjects()
    this.sceneLoaded = true
  },
}
</script>

<style scoped lang="less">
.prev-modal {
  width: 400px;
  height: 350px;
  position: absolute;
  right: 330px;
  top: 10%;
  border: 2px solid #ccc;
  border-radius: 10px;
  iframe {
    width: 100%;
    height: 100%;
    border-radius: 10px;
  }
}
.siderbar-right {
  position: fixed;
  top: 66px;
  right: 0;
  width: 320px;
  background: #1b1c1f;
  user-select: none;
}
.btns {
  margin: 20px;
  position: fixed;
  top: 0;
  right: 0;
  z-index: 2;
  > div {
    margin-left: 20px;
    background: #000 !important;
  }
}
.prefab-list-wrap {
  margin-right: 6px;
  padding: 10px 16px;
  margin-bottom: 10px;
  display: flex;
  align-items: center;
  flex-direction: row;
  justify-content: space-between;
  background: #1b1c1f;
  .title {
    padding-left: 24px;
  }
  li {
    list-style-type: none;
    padding: 5px 10px;
  }
}
.panel-wrapper {
  width: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  max-height: calc(100vh - 150px);
}
</style>
