import * as React from 'react';
import { observable, autorun, untracked } from 'mobx';
import { remove, set as lodashSet, get as lodashGet, isEqual } from 'lodash-es';
import { EventProxy } from './eventProxy';
import { runtime } from './adapter';
import { generateDataContext } from './common';

const WIDGET_API_LIST = [
  'getConfig',
  'findWidgets',
  'getWidgetsByType',
  'extends',
  'addWidgetsListener',
  'removeWidgetsListener',
  'getOwnerWidget',
  'getDom',
  'domRef',
  '_listeners',
  '_getInstanceRef',
  '_scope',
];

export const widgetKeys = [...WIDGET_API_LIST, '_key', 'parent', '_visible', '_parentId', '_waForKey'];

export const WIDGET_API_MAP = WIDGET_API_LIST.reduce((map, key) => {
  map[key] = true;
  return map;
}, {});

export const WidgetsContext = React.createContext({ parent: null });

export function checkVisible({ id, _visible = undefined }) {
  return _visible !== false && _visible !== '';
}

// 实现和小程序一致的 API，以兼容多端
// widget.getDom({ rect: true })
// https://developers.weixin.qq.com/miniprogram/dev/api/wxml/NodesRef.fields.html

interface MPNodesRefFields {
  id?: HTMLElement['id'];
  dataset?: HTMLElement['dataset'];
  // rect
  left?: DOMRect['left'];
  right?: DOMRect['right'];
  top?: DOMRect['top'];
  bottom?: DOMRect['bottom'];

  // size
  width?: DOMRect['width'];
  height?: DOMRect['height'];

  // scrollOffest
  scrollLeft?: HTMLElement['scrollLeft'];
  scrollTop?: HTMLElement['scrollTop'];

  // html attribute， css attribute
  [prop: string]: string | any;
}

export function getDom(element: HTMLElement, options): Promise<MPNodesRefFields> {
  if (!element) {
    console.warn('getDom 接口未传入有效的 element');
    return Promise.resolve({});
  }

  let result: MPNodesRefFields = {};
  if (options.id) result.id = element.id;
  if (options.dataset) result.dataset = element.dataset;
  if (options.rect) {
    const rect = element.getBoundingClientRect();
    Object.assign(result, {
      left: rect.left,
      right: rect.right,
      top: rect.top,
      bottom: rect.bottom,
    });
  }
  if (options.size) {
    const rect = element.getBoundingClientRect();
    Object.assign(result, {
      width: rect.width,
      height: rect.height,
    });
  }
  if (options.scrollOffset) {
    Object.assign(result, {
      scrollLeft: element.scrollLeft,
      scrollTop: element.scrollTop,
    });
  }
  if (options.properties && Array.isArray(options.properties)) {
    options.properties.forEach((propName) => {
      result[propName] = element.getAttribute(propName);
    });
  }
  if (options.computedStyle && Array.isArray(options.computedStyle)) {
    const computedStyle = runtime.getComputedStyle(element);
    options.computedStyle.forEach((propName) => {
      result[propName] = computedStyle[propName];
    });
  }

  return Promise.resolve(result);
}

export function findWidgets(widget, parentType, filterFn, includeInvisibleDescendants) {
  if (!widget) return [];

  let { children = [] } = widget;
  let matched = children.filter(filterFn);

  // 过滤掉不可见后代
  if (!includeInvisibleDescendants) {
    children = children.filter((item) => checkVisible(item));
  }

  children.forEach((child) => {
    // 如果递归过程中发现了自己，则停止递归
    matched = matched.concat(findWidgets(child, parentType, filterFn, includeInvisibleDescendants));
  });

  // 过滤掉不可见的匹配项
  if (!includeInvisibleDescendants) {
    matched = matched.filter((item) => checkVisible(item));
  }

  return matched;
}

let retryQueue = [];
// 递归执行
export function retryDataBinds(tryTime = 10) {
  const fn = retryQueue.shift();
  if (!fn || tryTime <= 0) {
    retryQueue = [];
    return;
  }
  try {
    fn({ silent: tryTime > 1 });
  } catch (e) {
    console.error('retryDataBinds', e);
  }
  retryDataBinds(tryTime - 1);
}
export function createWidgets(
  ctx: { widgets?: any; widgetProps?: any; nodeMap?: any },
  widgetProps,
  dataBinds,
  context,
) {
  console.log('createWidgets=======================');
  const { nodeTree, nodeMap } = createWidgetTree(ctx.nodeMap, ctx.widgetProps, widgetProps, dataBinds);
  const widgets = runFor(ctx.widgets, nodeTree, {}, null, null, context);
  return { widgets, nodeMap };

  /**
   * @param {*} pre widgets
   * @param {*} curForNode
   * @param {*} forItems
   * @param {*} parentLevelWidgets
   * @param {*} parentWidget
   * @returns top level widgets or for dispose
   */
  function runFor(pre, curForNode, forItems, parentLevelWidgets, parentWidget, context) {
    const nodeId = curForNode.id;
    if (!curForNode.value) {
      return createSubTree(curForNode, {});
    }
    const { _waForKey } = curForNode.value;
    const timer = {};
    const dispose = autorun((r) => {
      let forList = [];
      try {
        clearTimeout(timer[nodeId]);
        /**
         * untrace 收集context 阶段不添加observer
         * 消费数据阶段再添加
         */
        const dataContext = untracked(() => generateDataContext(parentWidget));

        const list = dataBinds[nodeId]._waFor(forItems, undefined, dataContext);
        if (!Array.isArray(list)) {
          timer[nodeId] = setTimeout(() => {
            console.warn(nodeId, 'For 循环绑定的数据并不是数组，请检查');
          }, 1000);
        } else {
          forList = list;
        }
      } catch (e) {
        timer[nodeId] = setTimeout(() => {
          console.error(`waFor error ${nodeId}`, e);
        }, 1000);
      }
      // r.trace();

      // 让 forList 进行监听
      forList.forEach(() => {});
      untracked(() => {
        // dispose widgets before reused instead
        // disposeWidgets(parentLevelWidgets[curForNode.id]);

        const exsitMap = forList.reduce((map, item) => {
          if (item?.[_waForKey]) {
            map[item[_waForKey]] = true;
          }
          return map;
        }, {});
        const forWidgets = parentLevelWidgets?.[nodeId] || [];
        const existedWidgetIndex = [];
        const extraWidgetsIndex = [];
        const extraWidgets = [];
        forWidgets.forEach((widget, index) => {
          if (exsitMap[widget._key]) {
            existedWidgetIndex.push(index);
            // need to use uqique key
            exsitMap[widget._key] = undefined;
          } else {
            extraWidgetsIndex.push(index);
          }
        });
        const extraWidgetsIndexMap = extraWidgetsIndex.reduce((map, item) => {
          map[item] = true;
          extraWidgets.push(forWidgets[item]);
          return map;
        }, {});

        // clean nodes of previouse for run
        dfsTree(
          curForNode,
          (node) => {
            const arr = parentLevelWidgets[node.id];
            remove(arr, (_, index) => {
              return extraWidgetsIndexMap[index];
            });
          },
          undefined,
        );

        // 清理根 for 的 autorun, 并递归清理子节点
        extraWidgets.map((w) => {
          disposeWidgets([w]);
          parentWidget && remove(parentWidget.children, ({ id }) => id === nodeId);
        });

        forList.forEach((item, index) => {
          const subForItems = {
            ...forItems,
            [nodeId]: item,
            forIndexes: [...(forItems?.['forIndexes'] || [])].concat(index),
          };
          createSubTree(curForNode, subForItems /* , context*/);
        });

        // 非初始化时遇到需要重新构建 dataBinds
        retryDataBinds();
      });
    });

    return dispose;

    function createSubTree(curForNode, subForItems /* , context*/) {
      const widgets = {};

      // traverse down the tree to set all widgets
      dfsTree(
        curForNode,
        (node, parentNode, cache) => {
          const parentForWidgetArr = parentLevelWidgets?.[node.id] || [];
          const { _waForKey } = node.value;
          const key = subForItems[node.id]?.[_waForKey];
          const index = cache[parentNode?.id]
            ? cache[parentNode.id].index
            : parentForWidgetArr.findIndex((widget) => key && widget._key === key);

          const forExsitWidget = index !== -1 ? parentForWidgetArr[index] : null;

          const preExistWidget = node.exist
            ? Array.isArray(pre?.[node.id])
              ? lodashGet(pre?.[node.id], subForItems.forIndexes)
              : pre?.[node.id]
            : null;

          const existedWidget = forExsitWidget || preExistWidget || null;

          if (forExsitWidget) {
            cache[node.id] = {
              index,
            };
          }

          if (node.forCount === curForNode.forCount) {
            // Leaf node
            let w = existedWidget;

            if (!existedWidget) {
              w = observable({
                ...node.value,
                children: [],
                id: node.id,
                _key: key,
                parent: null,
              });

              if (node === curForNode) {
                w._disposers = [];
              }

              // 更新 scope
              w._scope = {
                id: node.id,
                dataContext: context[node.id] || undefined,
              };

              w._listeners = new EventProxy();
              // 提供一个给 Node 挂载 API 的方式
              untracked(() => {
                if (!w.getConfig) {
                  // 兼容非复合组件调用 getConfig 能力
                  w.getConfig = () => ({});
                }
                w.findWidgets = (type, includeInvisibleDescendants) =>
                  findWidgets(w, w.widgetType, type, includeInvisibleDescendants);
                w.getWidgetsByType = (type, includeInvisibleDescendants) =>
                  w.findWidgets((currentWidget) => currentWidget.widgetType === type, includeInvisibleDescendants);
                w.extends = (name, fnOrData) =>
                  Object.defineProperty(w, name, {
                    value: fnOrData,
                    writable: true,
                  });
                w.addWidgetsListener = (name, listener) => w._listeners.on(name, listener);
                w.removeWidgetsListener = (name, listener) => w._listeners.cancel(name, listener);
                w.getOwnerWidget = () => null; // 默认带上一个 widget
                w._getInstanceRef = () => null; // 默认初始值
                Object.defineProperty(w, '_methods', {
                  get() {
                    const instance = w._getInstanceRef();
                    return instance?.current.methods;
                  },
                });
              });

              const parent = parentNode ? widgets[parentNode.id] : parentWidget;
              if (parent) {
                w.parent = parent;
                // 只有可显示 visible 的才存入 children 里
                if (checkVisible(w)) {
                  parent.children.push(w);
                }
              }
            } else {
              if (!forExsitWidget && preExistWidget) {
                if (node.changed) {
                  for (const key of node.changed) {
                    w[key] = node.value[key];
                  }
                }
                // 仅仅为了受控模式下更新 dataContext
                if (context?.[w.id]) {
                  w._scope.dataContext = context[w.id];
                }
              }
              disposeWidgets([existedWidget], true);
            }

            if (!forExsitWidget) {
              parentForWidgetArr.push?.(w);
            }
            widgets[node.id] = w;

            // Setup data binds
            Object.keys(dataBinds[node.id] || {})
              .sort((a, b) => {
                return a.length - b.length > 0 ? 1 : -1;
              })
              .map((prop) => {
                if (prop === '_waFor') {
                  return;
                }
                let timer = null;
                function getBindData(options = { silent: false }) {
                  let disposeError = false;
                  const dispose = autorun((reaction) => {
                    try {
                      clearTimeout(timer);
                      /**
                       * untrace 收集context 阶段不添加observer
                       * 消费数据阶段再添加
                       */
                      const dataContext = untracked(() => generateDataContext(w));
                      // Computed data bind in the next tick since data bind may read widgets data
                      const value = dataBinds[node.id][prop](subForItems, undefined, dataContext);
                      const paths = prop.split('.').filter((key) => !!key);

                      if (paths.length > 1) {
                        // 一定要 untracked 不然爆栈了
                        untracked(() => {
                          const pre = lodashGet(w, prop);
                          lodashSet(w, prop, value);
                          /**
                           * 深层的对象内部值变化不会触发 obverver，因此浅拷贝对象
                           */
                          if (Array.isArray(w[paths[0]])) {
                            if (!isEqual(pre, value)) {
                              w[paths[0]] = [...w[paths[0]]];
                            }
                          }
                        });
                      } else {
                        // 普通 key 直接赋值
                        w[prop] = value;
                      }
                      disposeError = false;
                    } catch (e) {
                      if (prop === '_visible') {
                        w[prop] = false;
                      } else {
                        retryQueue.push(getBindData);
                        disposeError = true;
                        reaction.dispose();
                      }
                      if (!options?.silent) {
                        timer = setTimeout(() => {
                          console.warn(`Error computing data bind ${w.id}.${prop}`, e);
                          timer = null;
                        }, 1000);
                      }
                    }
                  });
                  if (!disposeError && curForNode.id) {
                    widgets[curForNode.id]._disposers.push(dispose);
                  }
                }
                getBindData();
              });
          } else if (!existedWidget) {
            if (parentLevelWidgets) {
              const len = parentLevelWidgets[node.id].push([]);
              widgets[node.id] = parentLevelWidgets[node.id][len - 1];
            } else {
              widgets[node.id] = observable([]);
            }
          } else {
            widgets[node.id] = existedWidget;
          }
        },
        undefined,
      );

      // run for of next level
      dfsTree(
        curForNode,
        (node, parentNode) => {
          if (node.forCount === curForNode.forCount + 1 && dataBinds[node.id] && dataBinds[node.id]._waFor) {
            widgets[node.id]._disposers = { dataBinds: [] };
            const dispose = runFor(pre, node, subForItems, widgets, node.parent && widgets[node.parent.id], context);
            curForNode.id && widgets[curForNode.id]._disposers.push(dispose);
          }
        },
        undefined,
      );

      return widgets;
    }
  }
}

/**
 * Add parent, children to widget
 */
interface IWidgetNode {
  id: string;
  value: any;
  children: IWidgetNode[];
  parent: null | IWidgetNode;
  forCount: number;
  exist: boolean;
  changed: string[];
}
function createWidgetTree(preNodes, preWidgets, widgets, dataBinds) {
  const virtualRoot = { children: [], forCount: 0 };

  const nodes: Record<string, IWidgetNode> = Object.keys(widgets).reduce((result, id) => {
    const currentValue = widgets[id];
    result[id] = {
      id,
      value: currentValue,
      children: [],
      parent: null,
      forCount: 0,
      exist: false,
      changed: [],
    };
    return result;
  }, {});

  // Create widgets tree API
  Object.keys(nodes).map((id) => {
    const curNode = nodes[id];
    const parent = nodes[widgets[id]._parentId];
    if (!parent) {
      virtualRoot.children.push(curNode);
      return;
    }
    curNode.parent = parent;
    parent.children.push(curNode);
  });

  virtualRoot.children.map(addForCount);

  // dfs, add forCount
  function addForCount(node) {
    if (node.parent) {
      node.forCount = node.parent.forCount;
    }
    if (dataBinds[node.id]?._waFor) {
      node.forCount += 1;
    }
    node.children.map(addForCount);
  }

  dfsTree(
    virtualRoot,
    (node, parent) => {
      const currentValue = node.value;
      const preValue = preWidgets?.[node.id];
      const preNode = preNodes?.[node.id];
      let exist = false;
      const changed = [];
      // 多级for绑定的情况会发生父for不变化，子for更新的情况，此时子for生成会有异常，因此复用只复用一层保证渲染正常
      // https://lcap.cloud.tencent.com/preview/?u=https%3A%2F%2Fci-source-1303824488.cos.ap-shanghai.myqcloud.com%2Flcp%2Fapp-h057yqaw%2Fappdata-1666253755745.json&appId=app-h057yqaw#/nav
      if ((!parent || parent?.exist) && preValue && preNode) {
        if (
          preValue.widgetType === currentValue?.widgetType &&
          preValue._parentId === currentValue._parentId &&
          preNode.forCount === node.forCount
        ) {
          exist = true;
          for (let key in currentValue) {
            if (!WIDGET_API_MAP[key] && !isEqual(preValue[key], currentValue[key])) {
              changed.push(key);
            }
          }
        }
        node.exist = exist;
        node.changed = changed;
      }
    },
    undefined,
  );

  return { nodeTree: virtualRoot, nodeMap: nodes };
}

function dfsTree(node, fn, parent, cache = {}, scope = { dataContext: {} }) {
  if (node.value) {
    fn(node, parent, cache, scope);
  }
  node.children.map((e) => dfsTree(e, fn, node.value ? node : null, cache, scope));
}

/**
 * dispose autorun
 * TODO: 是否要同步清理 parent children 引用，防止内存泄漏
 */
function disposeWidgets(widgets = [], noRecursive = false) {
  widgets.forEach((widget) => {
    /**
     * 解除 parent 依赖
     */
    // widget.parent = null;
    const disposers = widget._disposers;
    if (disposers) {
      disposers.map((dispose) => dispose());
      disposers.splice(0, disposers.length);
    }
    if (!noRecursive) {
      disposeWidgets(widget.children);
      /**
       * 解除children 的引用
       */
      // widget.children = [];
    }
  });
}
