Source: data/data.js

import { forEach, isArray, isObject, pathJoin } from '../utils'
import { addWatch, triggerWatch } from './watchs'
import { createTemplates } from './template'

/**
 * 设置Proxy
 * @private
 * @param   {Object|Array}  target - 对象类型值,target = {} || []
 * @param   {Object|Array}  path   - 对象路径,用于组装监听路径和触发监听
 * @returns {Proxy}                - 返回代理对象
 */
const setProxy = (target, path) => {
  const handler = {
    get: (target, prop) => target[prop],
    set: (target, prop, newVal) => {
      let oldVal = target[prop]

      // 无数据变更
      if (JSON.stringify(newVal) === JSON.stringify(oldVal)) {
        return true
      }

      const watchPath = pathJoin(path, prop)

      // 触发watch
      triggerWatch(watchPath, newVal, oldVal)

      // 对象处理
      if (isObject(newVal)) {
        if (!isObject(oldVal)) {
          target[prop] = setProxy({}, watchPath)
        }

        forEach(target[prop], (val, k) => {
          if (!Object.prototype.hasOwnProperty.call(newVal, k)) {
            target[prop][k] = undefined
            delete target[prop][k]
          }
        })

        forEach(newVal, (val, k) => target[prop][k] = val)
      }

      // 数组处理
      else if (isArray(newVal)) {
        if (!isArray(oldVal)) {
          target[prop] = setProxy([], watchPath)
        }

        for(let i = newVal.length; i < target[prop].length; i ++) {
          target[prop][i] = undefined
          delete target[prop][i]
        }

        forEach(newVal, (val, k) => target[prop][k] = val)
      }

      // 其它类型
      else {
        target[prop] = newVal
      }

      return true
    }
  }
  return new Proxy(target, handler)
}

/**
 * 创建可监听对象
 * @function
 * @param   {Object} data  - 源对象
 * @param   {Object} watch - watch = { path1: fun1, ..., pathN: funN };<br>
 *                           path = 源对象路径; <br>
 *                           fun = (newVal, [oldVal]) => {};
 * @returns {Object}       - 返回可监听对象
 * @example
 * import digi, { createData } from 'digi'
 * import refs, { allotId } from 'digi-refs'
 *
 * digi.plugins([refs])
 *
 * // 创建监听数据
 * const data = createData({ a: 123 }, { watch: {
 *   a: (newVal, oldVal) => {
 *     console.log(`watch a => newVal: ${ newVal }, oldVal: ${ oldVal }`)
 *   }
 * }})
 *
 * // 分配标记id
 * const textRefId = allotId()
 *
 * // 添加元素
 * digi({ ref: textRefId, text: data.$tp('a') })
 *
 * console.log(refs[textRefId].outerHTML)
 * // => <div>123</div>
 *
 * data.a = 321
 * // => watch a => newVal: 321, oldVal: 123
 *
 * console.log(refs[textRefId].outerHTML)
 * // => <div>321</div>
 */
export const createData = (data, { watch } = {}) => {
  // 记录惟一值
  const id = ++ createData.id

  // 可监听对象
  const newData = setProxy({}, id)
  forEach(data, (value, key) => newData[key] = value)

  // 添加监听
  forEach(watch, (handler, path) => addWatch(pathJoin(id, path), handler, false))

  // 生成渲染模板
  Object.defineProperty(newData, '$tp', {
    value: (path, ...filters) => createTemplates(pathJoin(id, path), ...filters)
  })

  return newData
}

/**
 * 累计id
 * @private
 * @property {Number} createData.id 最后一个id值
 */
Object.defineProperty(createData, 'id', { value: 0, writable: true })