/*---------------------------------------------------------------------------------------------
 *  Licensed under the MIT License.
 *
 *  @Author: fantasticsoul
 *--------------------------------------------------------------------------------------------*/
import type { AnyObject, DraftMeta } from '../inner-types';
import {
  ARRAY,
  CAREFUL_FNKEYS,
  CHANGE_ARR_ORDER_METHODS,
  MAP,
  OP_DEL,
  OP_SET,
  SET,
  SHOULD_REASSIGN_ARR_METHODS,
  SHOULD_REASSIGN_MAP_METHODS,
  SHOULD_REASSIGN_SET_METHODS,
} from '../support/consts';
import { isFn, isPrimitive } from '../support/util';
import { getUnProxyValue } from './helper';
import { getDraftMetaByCtx, markModified } from './meta';
import { delKeyPath } from './path-util';

function mayMarkModified(options: { calledBy: string; parentMeta: DraftMeta; op: string; parentType: string; key: string | number }) {
  const { calledBy, parentMeta, op, parentType } = options;
  // 对于由 set 陷阱触发的 handleDataNode 调用，需要替换掉爷爷数据节点 key 指向的 value
  if (
    ['deleteProperty', 'set'].includes(calledBy) ||
    (calledBy === 'get' &&
      ((parentType === SET && SHOULD_REASSIGN_SET_METHODS.includes(op)) || // 针对 Set.add
        (parentType === ARRAY && SHOULD_REASSIGN_ARR_METHODS.includes(op)) || // 针对 Array 一系列的改变操作
        (parentType === MAP && SHOULD_REASSIGN_MAP_METHODS.includes(op)))) // 针对 Map 一系列的改变操作
  ) {
    markModified(parentMeta);
  }
}

function getValPathKey(parentMeta: DraftMeta, key: string) {
  const pathCopy = parentMeta.keyPath.slice();
  pathCopy.push(key);
  const valPathKey = pathCopy.join('|');
  return valPathKey;
}

export function handleDataNode(parentDataNode: any, copyCtx: { parentMeta: DraftMeta } & AnyObject) {
  const { op, key, value: mayProxyValue, calledBy, parentType, parentMeta, apiCtx, isValueDraft, mayNewNode } = copyCtx;

  /**
   * 防止 value 本身就是一个 Proxy
   * var draft_a1_b = draft.a1.b;
   * draft.a2 = draft_a1_b;
   */
  const value = getUnProxyValue(mayProxyValue, apiCtx);

  /**
   * 链路断裂，此对象未被代理
   * // draft = { a: { b: { c: 1 } }};
   * const newData = { n1: { n2: 2 } };
   * draft.a = newData;
   * draft.a.n1.n2 = 888; // 此时 n2_DataNode 是未代理对象
   */
  if (!parentMeta) {
    parentDataNode[key] = value;
    return;
  }

  const { self, copy: parentCopy } = parentMeta;
  mayMarkModified({ calledBy, parentMeta, op, key, parentType });

  // 是 Map, Set, Array 类型的方法操作或者值获取
  const fnKeys = CAREFUL_FNKEYS[parentType] || [];
  // 是函数调用
  if (isFn(mayProxyValue) && fnKeys.includes(op)) {
    // slice 操作无需使用 copy，返回自身即可
    if ('slice' === op) {
      return self.slice;
    }
    if (CHANGE_ARR_ORDER_METHODS.includes(op)) {
      parentMeta.isArrOrderChanged = true;
    }
    if (parentCopy) {
      // 因为 Map 和 Set 里的对象不能直接操作修改，是通过 set 调用来修改的
      // 所以无需 bind(parentDataNodeMeta.proxyVal)， 否则会以下情况出现，
      // Method Map.prototype.forEach called on incompatible receiver
      // Method Set.prototype.forEach called on incompatible receiver
      // see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Errors/Called_on_incompatible_type
      if (parentType === SET || parentType === MAP) {
        // 注意 forEach 等方法已提前生成了 proxyItems，这里 bind 的目标优先取 proxyItems
        return parentCopy[op].bind(parentCopy);
      }
      return parentCopy[op];
    }
    return self[op].bind(self);
  }

  if (!parentCopy) {
    return value;
  }

  const oldValue = parentCopy[key];
  const tryMarkDel = () => {
    const oldValueMeta = getDraftMetaByCtx(oldValue, apiCtx);
    oldValueMeta && (oldValueMeta.isDel = true);
  };
  const tryMarkUndel = () => {
    const valueMeta = getDraftMetaByCtx(mayProxyValue, apiCtx);
    if (valueMeta && valueMeta.isDel) {
      valueMeta.isDel = false;
      valueMeta.key = key;
      valueMeta.keyPath = parentMeta.keyPath.concat([key]);
      valueMeta.level = parentMeta.level + 1;
      valueMeta.parent = parentMeta.copy;
      valueMeta.parentMeta = parentMeta;
    }
  };

  if (OP_DEL === op) {
    const valueMeta = getDraftMetaByCtx(mayProxyValue, apiCtx);
    // for test/complex/data-node-change case3
    if (valueMeta) {
      const { keyPaths } = valueMeta;
      if (keyPaths.length === 1) {
        valueMeta.isDel = true;
      } else {
        // 存在多个路径指到当前对象时，调用 delKeyPath 删一条路径，并将 keyPath, keyStrPath 重新指向一个存在的路径
        delKeyPath(valueMeta);
      }
    } else {
      // for test/complex/data-node-change (node-change 2)
      tryMarkDel();
    }

    const val = parentCopy[key];
    if (!isPrimitive(val)) {
      apiCtx.newNodeMap.delete(getValPathKey(parentMeta, key));
    }

    delete parentCopy[key];
    return;
  }

  // set 时非原始值都当做新节点记录下来
  if (OP_SET === op && mayNewNode) {
    if (!isValueDraft && !isPrimitive(value)) {
      parentMeta.newNodeStats[key] = true;
      apiCtx.newNodeMap.set(getValPathKey(parentMeta, key), { parent: parentCopy, node: value, key, target: null });
    }
  }

  parentCopy[key] = value;
  // 谨防是 a.b = { ... } ---> a.b = 1 的变异赋值方式
  tryMarkDel();
  tryMarkUndel();
}
