<template>
  <view class="width-100">
    <view class="d-flex a-center" style="width:100%">
      <view @click="openPopup" class="d-flex px2" style="width:100%">
        <view class="input-box flex1">
          <view v-if="inputLabel" class="text-color-3">{{ inputLabel }}</view>
          <view v-else class="text-color-9">{{ placeholder }}</view>
        </view>
        <u-icon name="arrow-right"></u-icon>
      </view>
    </view>
    <u-popup mode="bottom" v-model="showPopup" length="auto" :safeAreaInsetBottom="true" :maskCloseAble="true">
      <view class="ts-popup">
        <view class="ts-header" @touchmove.stop.prevent="">
          <view class="ts-btn ts-btn--cancel" hover-class="ts-opacity" :hover-stay-time="150" @tap="handleCancel">取消
          </view>
          <view class="ts-title">{{ title }}</view>
          <view class="ts-btn ts-btn--confirm" hover-class="ts-opacity" :hover-stay-time="150" @tap="handleConfirm">确认
          </view>
        </view>
        <scroll-view scroll-y class="ts-body">
          <view v-for="(item, index) in visibleNodes" :key="index" class="ts-item"
            :class="{ 'ts-item--active': tempValue === item.node[valueName] }"
            :style="{ paddingLeft: item.depth * 40 + 30 + 'rpx' }" @click="selectNode(item)">
            <view class="ts-item-label">{{ item.node[labelName] }}</view>
            <u-icon v-if="tempValue === item.node[valueName]" name="checkmark" size="28" color="#0082ff"></u-icon>
            <view v-if="hasChildren(item.node)" class="ts-arrow" @click.stop="toggleExpand(item.node)">
              <u-icon :name="isExpanded(item.node) ? 'arrow-down' : 'arrow-right'" size="26" color="#909399"></u-icon>
            </view>
          </view>
          <view v-if="!visibleNodes.length" class="ts-empty">暂无数据</view>
        </scroll-view>
      </view>
    </u-popup>
  </view>
</template>

<script>
import Emitter from '@/uview-ui/libs/util/emitter.js'

/**
 * treeSelect 树形选择表单组件
 * @description 外观与 my-select 一致的表单选择框，点击后从底部弹出树形选择面板：
 *              父节点可通过右侧箭头展开/收起子级，点击任意节点（父/子）即选中，
 *              点击“确认”后写回 v-model，并派发 u-form 校验事件。
 * @property {String Number} value 选中节点的值（v-model）
 * @property {Array} data 树形数据，形如 [{ id, title, children: [...] }]
 * @property {Array} list 树形数据（data 的别名，兼容旧用法）
 * @property {String} labelName 节点显示文本对应的字段名（默认title）
 * @property {String} valueName 节点值对应的字段名（默认id）
 * @property {String} childrenName 子节点数组对应的字段名（默认children）
 * @property {String} placeholder 未选中时的占位文案（默认请选择）
 * @property {String} title 弹出面板顶部标题（默认请选择）
 * @property {Boolean} disabled 是否禁用选择（默认false）
 * @event {Function} input 确认选中后触发（v-model）
 * @event {Function} selected 确认选中后触发，返回 (value, 节点对象)
 * @example <tree-select v-model="form.sbCph" :data="sbPositionData" />
 */
export default {
  name: 'TreeSelect',
  mixins: [Emitter],
  props: {
    // 选中节点的值（v-model）
    value: {
      type: [String, Number],
      default: ''
    },
    // 树形数据
    data: {
      type: Array,
      default() {
        return []
      }
    },
    // 树形数据（data 的别名）
    list: {
      type: Array,
      default() {
        return []
      }
    },
    // 显示文本字段名
    labelName: {
      type: String,
      default: 'title'
    },
    // 值字段名
    valueName: {
      type: String,
      default: 'id'
    },
    // 子节点字段名
    childrenName: {
      type: String,
      default: 'children'
    },
    placeholder: {
      type: String,
      default: '请选择'
    },
    title: {
      type: String,
      default: '请选择'
    },
    disabled: {
      type: [String, Number, Boolean],
      default: false
    }
  },
  data() {
    return {
      showPopup: false,
      inputLabel: '',
      // 弹框内临时选中的值与文本，确认后才写回
      tempValue: '',
      tempLabel: '',
      // 弹框内已展开的节点 value 集合
      expandedKeys: []
    }
  },
  computed: {
    treeData() {
      return this.data && this.data.length ? this.data : this.list
    },
    // 将树按展开状态拍平为可见节点列表：[{ node, depth }]
    visibleNodes() {
      const result = []
      const walk = (nodes, depth) => {
        if (!nodes || !nodes.length) return
        for (let i = 0; i < nodes.length; i++) {
          const node = nodes[i]
          result.push({ node, depth })
          if (this.isExpanded(node) && this.hasChildren(node)) {
            walk(node[this.childrenName], depth + 1)
          }
        }
      }
      walk(this.treeData, 0)
      return result
    }
  },
  watch: {
    value() {
      this.initByValue()
    },
    treeData() {
      this.initByValue()
    }
  },
  created() {
    this.$nextTick(() => {
      this.initByValue()
    })
  },
  methods: {
    openPopup() {
      if (this.disabled) {
        uni.showToast({ title: '不允许选择', icon: 'none' })
        return
      }
      // 回显当前值：临时选中 + 自动展开其所有父级
      this.tempValue = this.value
      this.tempLabel = this.inputLabel
      const path = this.findPath(this.treeData, this.value) || []
      const keys = []
      let nodes = this.treeData
      for (let i = 0; i < path.length - 1; i++) {
        keys.push(nodes[path[i]][this.valueName])
        nodes = nodes[path[i]][this.childrenName]
      }
      this.expandedKeys = keys
      this.showPopup = true
    },
    handleCancel() {
      this.showPopup = false
    },
    handleConfirm() {
      const found = this.findNodeByValue(this.treeData, this.tempValue)
      if (this.tempValue !== '' && this.tempValue !== null && this.tempValue !== undefined && found) {
        this.inputLabel = found[this.labelName]
        if (found[this.valueName] + '' !== this.value + '') {
          this.$emit('input', found[this.valueName])
          this.$emit('selected', found[this.valueName], found)
          this.dispatch('u-form-item', 'on-form-change')
        }
      }
      this.showPopup = false
    },
    // 点击节点：选中；若为父节点则同时展开其子级，方便继续选子选项
    selectNode(item) {
      this.tempValue = item.node[this.valueName]
      this.tempLabel = item.node[this.labelName]
      if (this.hasChildren(item.node) && !this.isExpanded(item.node)) {
        this.expandedKeys = [...this.expandedKeys, item.node[this.valueName]]
      }
    },
    // 点击箭头：仅展开/收起，不改变选中
    toggleExpand(node) {
      const key = node[this.valueName]
      if (this.isExpanded(node)) {
        this.expandedKeys = this.expandedKeys.filter(k => k !== key)
      } else {
        this.expandedKeys = [...this.expandedKeys, key]
      }
    },
    isExpanded(node) {
      return this.expandedKeys.indexOf(node[this.valueName]) !== -1
    },
    hasChildren(item) {
      const children = item[this.childrenName]
      return !!(children && children.length)
    },
    // 递归查找 value 对应节点，返回 { path, node }
    findNode(nodes, path = []) {
      if (this.value === '' || this.value === null || this.value === undefined) {
        return null
      }
      return this.findNodeByValue(nodes, this.value, path)
    },
    // 递归查找 value 对应节点的下标路径
    findPath(nodes, val, path = []) {
      if (!nodes || !nodes.length || val === '' || val === null || val === undefined) {
        return null
      }
      for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i]
        if (node[this.valueName] == val) {
          return [...path, i]
        }
        const children = node[this.childrenName]
        if (children && children.length) {
          const found = this.findPath(children, val, [...path, i])
          if (found) return found
        }
      }
      return null
    },
    // 按给定值递归查找节点
    findNodeByValue(nodes, val, path = []) {
      if (!nodes || !nodes.length || val === '' || val === null || val === undefined) {
        return null
      }
      for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i]
        if (node[this.valueName] == val) {
          return node
        }
        const children = node[this.childrenName]
        if (children && children.length) {
          const found = this.findNodeByValue(children, val, [...path, i])
          if (found) return found
        }
      }
      return null
    },
    // 根据外部传入的 value 回显输入框文本
    initByValue() {
      const found = this.findNode(this.treeData)
      this.inputLabel = found ? found[this.labelName] : ''
    }
  }
}
</script>

<style scoped>
.width-100 {
  width: 100%;
}

.ts-popup {
  background-color: #fff;
}

.ts-header {
  width: 100%;
  height: 90rpx;
  padding: 0 40rpx;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-sizing: border-box;
  font-size: 30rpx;
  background: #fff;
  position: relative;
}

.ts-header::after {
  content: '';
  position: absolute;
  border-bottom: 1rpx solid #eaeef1;
  -webkit-transform: scaleY(0.5);
  transform: scaleY(0.5);
  bottom: 0;
  right: 0;
  left: 0;
}

.ts-title {
  color: #606266;
}

.ts-btn {
  padding: 16rpx;
  box-sizing: border-box;
  text-align: center;
}

.ts-btn--cancel {
  color: #606266;
}

.ts-btn--confirm {
  color: #2979ff;
}

.ts-opacity {
  opacity: 0.5;
}

.ts-body {
  max-height: 600rpx;
  padding-bottom: 20rpx;
}

.ts-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 20rpx;
  padding-bottom: 20rpx;
  padding-right: 30rpx;
  font-size: 28rpx;
  color: #303133;
}

.ts-item--active {
  color: #0082ff;
  font-weight: 600;
}

.ts-item-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ts-arrow {
  padding: 6rpx 10rpx;
}

.ts-empty {
  text-align: center;
  color: #909399;
  font-size: 26rpx;
  padding: 40rpx 0;
}
</style>
