export type PaginationType = {
  current : number,
  size : number,
  count : number
}

export type LoadMoreType = {
  contentdown : string,
  contentrefresh : string,
  contentnomore : string
}

export type SelectedItemType = {
  name : string,
  value : string,
}

export type GetCommandOptions = {
  collection ?: UTSJSONObject,
  field ?: string,
  orderby ?: string,
  where ?: any,
  pageData ?: string,
  pageCurrent ?: number,
  pageSize ?: number,
  getCount ?: boolean,
  getTree ?: any,
  getTreePath ?: UTSJSONObject,
  startwith ?: string,
  limitlevel ?: number,
  groupby ?: string,
  groupField ?: string,
  distinct ?: boolean,
  pageIndistinct ?: boolean,
  foreignKey ?: string,
  loadtime ?: string,
  manual ?: boolean
}

const DefaultSelectedNode = {
  text: '请选择',
  value: ''
}

export const dataPicker = defineMixin({
  props: {
    localdata: {
      type: Array as PropType<Array<UTSJSONObject>>,
      default: [] as Array<UTSJSONObject>
    },
    collection: {
      type: Object,
      default: ''
    },
    field: {
      type: String,
      default: ''
    },
    orderby: {
      type: String,
      default: ''
    },
    where: {
      type: Object,
      default: ''
    },
    pageData: {
      type: String,
      default: 'add'
    },
    pageCurrent: {
      type: Number,
      default: 1
    },
    pageSize: {
      type: Number,
      default: 20
    },
    getcount: {
      type: Boolean,
      default: false
    },
    gettree: {
      type: Object,
      default: ''
    },
    gettreepath: {
      type: Object,
      default: ''
    },
    startwith: {
      type: String,
      default: ''
    },
    limitlevel: {
      type: Number,
      default: 10
    },
    groupby: {
      type: String,
      default: ''
    },
    groupField: {
      type: String,
      default: ''
    },
    distinct: {
      type: Boolean,
      default: false
    },
    pageIndistinct: {
      type: Boolean,
      default: false
    },
    foreignKey: {
      type: String,
      default: ''
    },
    loadtime: {
      type: String,
      default: 'auto'
    },
    manual: {
      type: Boolean,
      default: false
    },
    preload: {
      type: Boolean,
      default: false
    },
    stepSearh: {
      type: Boolean,
      default: true
    },
    selfField: {
      type: String,
      default: ''
    },
    parentField: {
      type: String,
      default: ''
    },
    multiple: {
      type: Boolean,
      default: false
    },
    value: {
      type: Object,
      default: ''
    },
    modelValue: {
      type: Object,
      default: ''
    },
    defaultProps: {
      type: Object as PropType<UTSJSONObject>,
    }
  },
  data() {
    return {
      loading: false,
      error: null as UniCloudError | null,
      treeData: [] as Array<UTSJSONObject>,
      selectedIndex: 0,
      selectedNodes: [] as Array<UTSJSONObject>,
      selectedPages: [] as Array<UTSJSONObject>[],
      selectedValue: '',
      selectedPaths: [] as Array<UTSJSONObject>,
      pagination: {
        current: 1,
        size: 20,
        count: 0
      } as PaginationType
    }
  },
  computed: {
    mappingTextName() : string {
      // TODO
      return (this.defaultProps != null) ? this.defaultProps!.getString('text', 'text') : 'text'
    },
    mappingValueName() : string {
      // TODO
      return (this.defaultProps != null) ? this.defaultProps!.getString('value', 'value') : 'value'
    },
    currentDataList() : Array<UTSJSONObject> {
      if (this.selectedIndex > this.selectedPages.length - 1) {
        return [] as Array<UTSJSONObject>
      }
      return this.selectedPages[this.selectedIndex]
    },
    isLocalData() : boolean {
      return this.localdata.length > 0
    },
    isCloudData() : boolean {
      return this._checkIsNotNull(this.collection)
    },
    isCloudDataList() : boolean {
      return (this.isCloudData && (this.parentField.length == 0 && this.selfField.length == 0))
    },
    isCloudDataTree() : boolean {
      return (this.isCloudData && this.parentField.length > 0 && this.selfField.length > 0)
    },
    dataValue() : any {
      return this.hasModelValue ? this.modelValue : this.value
    },
    hasCloudTreeData() : boolean {
      return this.treeData.length > 0
    },
    hasModelValue() : boolean {
      if (typeof this.modelValue == 'string') {
        const valueString = this.modelValue as string
        return (valueString.length > 0)
      } else if (Array.isArray(this.modelValue)) {
        const valueArray = this.modelValue as Array<string>
        return (valueArray.length > 0)
      }
      return false
    },
    hasCloudDataValue() : boolean {
      if (typeof this.dataValue == 'string') {
        const valueString = this.dataValue as string
        return (valueString.length > 0)
      }
      return false
    }
  },
  created() {
    this.pagination.current = this.pageCurrent
    this.pagination.size = this.pageSize

    this.$watch(
      () : any => [
        this.pageCurrent,
        this.pageSize,
        this.localdata,
        this.value,
        this.collection,
        this.field,
        this.getcount,
        this.orderby,
        this.where,
        this.groupby,
        this.groupField,
        this.distinct
      ],
      (newValue : Array<any>, oldValue : Array<any>) => {
        this.pagination.size = this.pageSize
        if (newValue[0] !== oldValue[0]) {
          this.pagination.current = this.pageCurrent
        }

        this.onPropsChange()
      }
    )
  },
  methods: {
    onPropsChange() {
      this.selectedIndex = 0
      this.selectedNodes.length = 0
      this.selectedPages.length = 0
      this.selectedPaths.length = 0

      // 加载数据
      this.$nextTick(() => {
        this.loadData()
      })
    },

    onTabSelect(index : number) {
      this.selectedIndex = index
    },

    onNodeClick(nodeData : UTSJSONObject) {
      if (nodeData.getBoolean('disable', false)) {
        return
      }

      const isLeaf = this._checkIsLeafNode(nodeData)

      this._trimSelectedNodes(nodeData)

      this.$emit('nodeclick', nodeData)

      if (this.isLocalData) {
        if (isLeaf || !this._checkHasChildren(nodeData)) {
          this.onFinish()
        }
      } else if (this.isCloudDataList) {
        this.onFinish()
      } else if (this.isCloudDataTree) {
        if (isLeaf) {
          this.onFinish()
        } else if (!this._checkHasChildren(nodeData)) {
          // 尝试请求一次，如果没有返回数据标记为叶子节点
          this.loadCloudDataNode(nodeData)
        }
      }
    },

    getChangeNodes(): Array<UTSJSONObject> {
      const nodes: Array<UTSJSONObject> = []
      this.selectedNodes.forEach((node : UTSJSONObject) => {
        const newNode: UTSJSONObject = {}
        newNode[this.mappingTextName] = node.getString(this.mappingTextName)
        newNode[this.mappingValueName] = node.getString(this.mappingValueName)
        nodes.push(newNode)
      })
      return nodes
    },

    onFinish() { },

    // 加载数据(自动判定环境)
    loadData() {
      if (this.isLocalData) {
        this.loadLocalData()
      } else if (this.isCloudDataList) {
        this.loadCloudDataList()
      } else if (this.isCloudDataTree) {
        this.loadCloudDataTree()
      }
    },

    // 加载本地数据
    loadLocalData() {
      this.treeData = this.localdata
      if (Array.isArray(this.dataValue)) {
        const value = this.dataValue as Array<UTSJSONObject>
        this.selectedPaths = value.slice(0)
        this._pushSelectedTreeNodes(value, this.localdata)
      } else {
        this._pushSelectedNodes(this.localdata)
      }
    },

    // 加载 Cloud 数据 (单列)
    loadCloudDataList() {
      this._loadCloudData(null, (data : Array<UTSJSONObject>) => {
        this.treeData = data
        this._pushSelectedNodes(data)
      })
    },

    // 加载 Cloud 数据 (树形)
    loadCloudDataTree() {
      let commandOptions = {
        field: this._cloudDataPostField(),
        where: this._cloudDataTreeWhere(),
        getTree: true
      } as GetCommandOptions
      if (this._checkIsNotNull(this.gettree)) {
        commandOptions.startwith = `${this.selfField}=='${this.dataValue as string}'`
      }
      this._loadCloudData(commandOptions, (data : Array<UTSJSONObject>) => {
        this.treeData = data
        if (this.selectedPaths.length > 0) {
          this._pushSelectedTreeNodes(this.selectedPaths, data)
        } else {
          this._pushSelectedNodes(data)
        }
      })
    },

    // 加载 Cloud 数据 (节点)
    loadCloudDataNode(nodeData : UTSJSONObject) {
      const commandOptions = {
        field: this._cloudDataPostField(),
        where: this._cloudDataNodeWhere()
      } as GetCommandOptions
      this._loadCloudData(commandOptions, (data : Array<UTSJSONObject>) => {
        nodeData['children'] = data
        if (data.length == 0) {
          nodeData['isleaf'] = true
          this.onFinish()
        } else {
          this._pushSelectedNodes(data)
        }
      })
    },

    // 回显 Cloud Tree Path
    loadCloudDataPath() {
      if (!this.hasCloudDataValue) {
        return
      }

      const command : GetCommandOptions = {}

      // 单列
      if (this.isCloudDataList) {
        // 根据 field's as value标识匹配 where 条件
        let where : Array<string> = [];
        let whereField = this._getForeignKeyByField();
        if (whereField.length > 0) {
          where.push(`${whereField} == '${this.dataValue as string}'`)
        }

        let whereString = where.join(' || ')
        if (this._checkIsNotNull(this.where)) {
          whereString = `(${this.where}) && (${whereString})`
        }

        command.field = this._cloudDataPostField()
        command.where = whereString
      }

      // 树形
      if (this.isCloudDataTree) {
        command.field = this._cloudDataPostField()
        command.getTreePath = {
          startWith: `${this.selfField}=='${this.dataValue as string}'`
        }
      }

      this._loadCloudData(command, (data : Array<UTSJSONObject>) => {
        this._extractTreePath(data, this.selectedPaths)
      })
    },

    _loadCloudData(options ?: GetCommandOptions, callback ?: ((data : Array<UTSJSONObject>) => void)) {
      if (this.loading) {
        return
      }
      this.loading = true

      this.error = null

      this._getCommand(options).then((response : UniCloudDBGetResult) => {
        callback?.(response.data)
      }).catch((err : any | null) => {
        this.error = err as UniCloudError
      }).finally(() => {
        this.loading = false
      })
    },

    _cloudDataPostField() : string {
      let fields = [this.field];
      if (this.parentField.length > 0) {
        fields.push(`${this.parentField} as parent_value`)
      }
      return fields.join(',')
    },

    _cloudDataTreeWhere() : string {
      let result : Array<string> = []
      let selectedNodes = this.selectedNodes.length > 0 ? this.selectedNodes : this.selectedPaths
      let parentField = this.parentField
      if (parentField.length > 0) {
        result.push(`${parentField} == null || ${parentField} == ""`)
      }
      if (selectedNodes.length > 0) {
        for (var i = 0; i < selectedNodes.length - 1; i++) {
          const parentFieldValue = selectedNodes[i].getString('value', '')
          result.push(`${parentField} == '${parentFieldValue}'`)
        }
      }

      let where : Array<string> = []
      if (this._checkIsNotNull(this.where)) {
        where.push(`(${this.where as string})`)
      }

      if (result.length > 0) {
        where.push(`(${result.join(' || ')})`)
      }

      return where.join(' && ')
    },

    _cloudDataNodeWhere() : string {
      const where : Array<string> = []
      if (this.selectedNodes.length > 0) {
        const value = this.selectedNodes[this.selectedNodes.length - 1].getString('value', '')
        where.push(`${this.parentField} == '${value}'`)
      }

      let whereString = where.join(' || ')
      if (this._checkIsNotNull(this.where)) {
        return `(${this.where as string}) && (${whereString})`
      }

      return whereString
    },

    _getWhereByForeignKey() : string {
      let result : Array<string> = []
      let whereField = this._getForeignKeyByField();
      if (whereField.length > 0) {
        result.push(`${whereField} == '${this.dataValue as string}'`)
      }

      if (this._checkIsNotNull(this.where)) {
        return `(${this.where}) && (${result.join(' || ')})`
      }

      return result.join(' || ')
    },

    _getForeignKeyByField() : string {
      const fields = this.field.split(',')
      let whereField = ''
      for (let i = 0; i < fields.length; i++) {
        const items = fields[i].split('as')
        if (items.length < 2) {
          continue
        }
        if (items[1].trim() === 'value') {
          whereField = items[0].trim()
          break
        }
      }
      return whereField
    },

    _getCommand(options ?: GetCommandOptions) : Promise<UniCloudDBGetResult> {
      let db = uniCloud.databaseForJQL()

      let collection = Array.isArray(this.collection) ? db.collection(...(this.collection as Array<any>)) : db.collection(this.collection)

      let filter : UniCloudDBFilter | null = null
      if (this.foreignKey.length > 0) {
        filter = collection.foreignKey(this.foreignKey)
      }

      const where : any = options?.where ?? this.where
      if (typeof where == 'string') {
        const whereString = where as string
        if (whereString.length > 0) {
          filter = (filter != null) ? filter.where(where) : collection.where(where)
        }
      } else {
        filter = (filter != null) ? filter.where(where) : collection.where(where)
      }

      let query : UniCloudDBQuery | null = null
      if (this.field.length > 0) {
        query = (filter != null) ? filter.field(this.field) : collection.field(this.field)
      }
      if (this.groupby.length > 0) {
        if (query != null) {
          query = query.groupBy(this.groupby)
        } else if (filter != null) {
          query = filter.groupBy(this.groupby)
        }
      }
      if (this.groupField.length > 0) {
        if (query != null) {
          query = query.groupField(this.groupField)
        } else if (filter != null) {
          query = filter.groupField(this.groupField)
        }
      }
      if (this.distinct == true) {
        if (query != null) {
          query = query.distinct(this.field)
        } else if (filter != null) {
          query = filter.distinct(this.field)
        }
      }
      if (this.orderby.length > 0) {
        if (query != null) {
          query = query.orderBy(this.orderby)
        } else if (filter != null) {
          query = filter.orderBy(this.orderby)
        }
      }

      const size = this.pagination.size
      const current = this.pagination.current
      if (query != null) {
        query = query.skip(size * (current - 1)).limit(size)
      } else if (filter != null) {
        query = filter.skip(size * (current - 1)).limit(size)
      } else {
        query = collection.skip(size * (current - 1)).limit(size)
      }

      const getOptions = {}
      const treeOptions = {
        limitLevel: this.limitlevel,
        startWith: this.startwith
      }
      if (this.getcount == true) {
        getOptions['getCount'] = this.getcount
      }

      const getTree : any = options?.getTree ?? this.gettree
      if (typeof getTree == 'string') {
        const getTreeString = getTree as string
        if (getTreeString.length > 0) {
          getOptions['getTree'] = treeOptions
        }
      } else if (typeof getTree == 'object') {
        getOptions['getTree'] = treeOptions
      } else {
        getOptions['getTree'] = getTree
      }

      const getTreePath = options?.getTreePath ?? this.gettreepath
      if (typeof getTreePath == 'string') {
        const getTreePathString = getTreePath as string
        if (getTreePathString.length > 0) {
          getOptions['getTreePath'] = getTreePath
        }
      } else {
        getOptions['getTreePath'] = getTreePath
      }

      return query.get(getOptions)
    },

    _checkIsNotNull(value : any) : boolean {
      if (typeof value == 'string') {
        const valueString = value as string
        return (valueString.length > 0)
      } else if (value instanceof UTSJSONObject) {
        return true
      }
      return false
    },

    _checkIsLeafNode(nodeData : UTSJSONObject) : boolean {
      if (this.selectedIndex >= this.limitlevel) {
        return true
      }

      if (nodeData.getBoolean('isleaf', false)) {
        return true
      }

      return false
    },

    _checkHasChildren(nodeData : UTSJSONObject) : boolean {
      const children = nodeData.getArray('children') ?? ([] as Array<any>)
      return children.length > 0
    },

    _pushSelectedNodes(nodes : Array<UTSJSONObject>) {
      this.selectedNodes.push(DefaultSelectedNode)
      this.selectedPages.push(nodes)
      this.selectedIndex = this.selectedPages.length - 1
    },

    _trimSelectedNodes(nodeData : UTSJSONObject) {
      this.selectedNodes.splice(this.selectedIndex)
      this.selectedNodes.push(nodeData)

      if (this.selectedPages.length > 0) {
        this.selectedPages.splice(this.selectedIndex + 1)
      }

      const children = nodeData.getArray<UTSJSONObject>('children') ?? ([] as Array<UTSJSONObject>)
      if (children.length > 0) {
        this.selectedNodes.push(DefaultSelectedNode)
        this.selectedPages.push(children)
      }

      this.selectedIndex = this.selectedPages.length - 1
    },

    _pushSelectedTreeNodes(paths : Array<UTSJSONObject>, nodes : Array<UTSJSONObject>) {
      let children : Array<UTSJSONObject> = nodes
      paths.forEach((node : UTSJSONObject) => {
        const findNode = children.find((item : UTSJSONObject) : boolean => {
          return (item.getString(this.mappingValueName) == node.getString(this.mappingValueName))
        })
        if (findNode != null) {
          this.selectedPages.push(children)
          this.selectedNodes.push(node)
          children = findNode.getArray<UTSJSONObject>('children') ?? ([] as Array<UTSJSONObject>)
        }
      })
      this.selectedIndex = this.selectedPages.length - 1
    },

    _extractTreePath(nodes : Array<UTSJSONObject>, result : Array<UTSJSONObject>) {
      if (nodes.length == 0) {
        return
      }

      const node = nodes[0]
      result.push(node)

      const children = node.getArray<UTSJSONObject>('children')
      if (Array.isArray(children) && children!.length > 0) {
        this._extractTreePath(children, result)
      }
    }
  }
})
