/**
 * @file 用来创建一个域，在这个域里面会把里面的运行时实例注册进来，方便组件之间的通信。
 * @author fex
 */

import React from 'react';
import find from 'lodash/find';
import hoistNonReactStatic from 'hoist-non-react-statics';

import { dataMapping } from './utils/tpl-builtin';
import { RendererEnv, RendererProps } from './factory';
import {
  noop,
  autobind,
  qsstringify,
  qsparse,
  createObject,
  findTree,
  TreeItem,
  findTreeList
} from './utils/helper';
import { RendererData, Action } from './types';
import { tools } from './utils/shell/tools';
import { cloneDeep } from 'lodash';


export interface ScopedComponentType extends React.Component<RendererProps> {
  focus?: () => void;
  doAction?: (
    action: Action,
    data: RendererData,
    throwErrors?: boolean
  ) => void;
  receive?: (values: RendererData, subPath?: string) => void;
  reload?: (
    subPath?: string,
    query?: RendererData | null,
    ctx?: RendererData
  ) => void;
  context: any;
}

export interface IScopedContext {
  parent?: AliasIScopedContext;
  children?: AliasIScopedContext[];
  registerComponent: (component: ScopedComponentType) => void;
  unRegisterComponent: (component: ScopedComponentType) => void;
  getComponentByName: (name: string) => ScopedComponentType;
  getComponentById: (id: string) => ScopedComponentType | undefined;
  getComponents: () => Array<ScopedComponentType>;
  reload: (target: string, ctx: RendererData, currentName?: string, isItemAction?: boolean, delegate?: IScopedContext) => void;
  send: (target: string, ctx: RendererData, currentName?: string, withTarget?: string) => boolean;
  close: (target: string) => void;
}
type AliasIScopedContext = IScopedContext;
export const ScopedContext = React.createContext(createScopedTools(''));

const scopedComponents: ScopedComponentType[] = []


// window['getHaveChildrenComponents']

function createScopedTools(
  path?: string,
  parent?: AliasIScopedContext,
  env?: RendererEnv
): IScopedContext {
  const components: ScopedComponentType[] = []
  const self = {
    components,
    parent,
    path,
    children: [],
    registerComponent(component: ScopedComponentType) {
      // 不要把自己注册在自己的 Scoped 上，自己的 Scoped 是给子节点们注册的。
      if (component.props.$path === self.path && parent) {
        return parent.registerComponent(component);
      }
      if (!~self.components.indexOf(component)) {
        self.components.push(component);
        if (component.props.type == 'crud' || component.props.type == 'form' || component.props.type == 'data-display') {
          scopedComponents.push(component)
        }
      }
    },

    unRegisterComponent(component: ScopedComponentType) {
      // 自己本身实际上注册在父级 Scoped 上。
      if (component.props.$path === self.path && parent) {
        return parent.unRegisterComponent(component);
      }

      const idx = self.components.indexOf(component);
      const tempIdx = scopedComponents.indexOf(component)

      // console.log(component, idx, tempIdx)
      if (idx > -1) {
        self.components.splice(idx, 1);
      }
      if (tempIdx > -1) {
        scopedComponents.splice(tempIdx, 1)
      }


      // 取消注册的时候 把自己缓存的components全部清除 防止缓存
      self.children = []
      // 情况components
      // console.log('取消注册', components, ~idx, ~tempIdx)
      // while (components.length) {
      //   components.pop()
      // }
      // else {
      //   const instanceIdx = scopedComponents.findIndex(_component => _component.instacneId === component.instacneId)
      //   if (~instanceIdx) scopedComponents.splice(instanceIdx, 1)
      // }
    },

    getComponentByName(name: string) {
      if (~name.indexOf('.')) {
        const paths = name.split('.');
        const len = paths.length;

        return paths.reduce((scope, name, idx) => {
          if (scope && scope.getComponentByName) {
            const result: ScopedComponentType = scope.getComponentByName(name);
            return result && idx < len - 1 ? result.context : result;
          }

          return null;
        }, this);
      }

      const resolved = find(
        self.components,
        component => {
          return component.props.name === name || component.props.id === name
        }
      );
      return resolved || (parent && parent.getComponentByName(name));
    },

    getComponentFromArray(name: string) {
      return find(
        scopedComponents,
        component => {
          return component.props.name === name || component.props.id === name
        }
      );
    },

    getComponentById(id: string) {
      let root: AliasIScopedContext = this;
      // 找到顶端scoped
      while (root.parent) {
        root = root.parent;
      }

      // 向下查找
      let component = undefined;
      findTree([root], (item: TreeItem) =>
        item.getComponents().find((cmpt: ScopedComponentType) => {
          if (cmpt.props.id === id) {
            component = cmpt;
            return true;
          }
          return false;
        })
      ) as ScopedComponentType | undefined;
      return component;
    },

    getComponents() {
      return components.concat();
    },

    getAllChildrenComponets(root: any) {
      const componentArr: any[] = []
      function getChildComponent(component: any) {
        const components = [...component.context.components || []]
        const selfComponentIdx = components.indexOf(component)
        if (~selfComponentIdx) {
          components.splice(selfComponentIdx, 1)
        }
        // 如果有子节点 先遍历子节点
        componentArr.push(component)
        if (components?.length) {
          components.map((_: any) => {
            // 防止走重复的
            if (componentArr.includes(_)) {
              return
            }
            getChildComponent(_)
          })
        }
      }
      getChildComponent(root)
      return componentArr
    },

    // 增加从触发的位置的context查找上下文的功能
    findSubComponent(name: string, currentName?: string, index = -1, delegate?: IScopedContext) {
      if (!currentName || !name) return false;
      let root: AliasIScopedContext = this;
      if (root?.parent) {
        root = root?.parent
      }
      // 触发的currentComponets
      const currentComponent = this.findTargetComponent(currentName, delegate) as any
      if (currentComponent && currentComponent?.context.parent) {
        let componentList: any = [];
        findTreeList([currentComponent?.context.parent], (item: TreeItem) => {
          return item.getComponents().find((cmpt: ScopedComponentType) => {
            if (cmpt.props.name === name || cmpt.props.id === name) {
              componentList.push(cmpt);
              return true;
            }
            return false;
          })
        }
        ) as ScopedComponentType[];
        return componentList.length > 0 ? componentList : false;
      }
      return false
    },

    // 增加从触发的位置的context查找上下文的功能
    findTargetComponent(name: string, delegate?: IScopedContext) {
      let root: AliasIScopedContext = delegate?.parent || this;
      // 向下查找
      let component = undefined;
      findTree([root], (item: TreeItem) =>
        item.getComponents().find((cmpt: ScopedComponentType) => {
          if (cmpt.props.name === name || cmpt.props.id === name) {
            component = cmpt;
            return true;
          }
          return false;
        })
      ) as ScopedComponentType | undefined;
      return component;
    },

    reload(target: string | Array<string>, ctx: any, currentName?: string, isItemAction?: boolean, delegate?: IScopedContext) {
      const scoped = this;
      let targets =
        typeof target === 'string' ? target.split(/\s*,\s*/) : target;
      targets.forEach((name, index) => {
        const idx2 = name.indexOf('?');
        let query: any = null;

        if (~idx2) {
          const queryObj = qsparse(
            name
              .substring(idx2 + 1)
              .replace(
                /\$\{(.*?)\}/,
                (_, match) => '${' + encodeURIComponent(match) + '}'
              )
          );
          query = dataMapping(queryObj, ctx);
          name = name.substring(0, idx2);
        }

        const idx = name.indexOf('.');
        let subPath = '';

        if (~idx) {
          subPath = name.substring(1 + idx);
          name = name.substring(0, idx);
        }
        if (name === 'window') {
          if (query) {
            const link = location.pathname + '?' + qsstringify(query);
            env ? env.updateLocation(link, true) : location.replace(link);
          } else {
            location.reload();
          }
        } else {
          const component = scoped.findSubComponent(name, currentName, index, delegate) || scoped.getComponentByName(name) || scoped.findTargetComponent(name, delegate) || scoped.getComponentFromArray(name);
          if (tools.isArray(component)) {
            component.forEach(item => {
              item.reload?.(subPath, query, ctx, isItemAction)
            })
          } else {
            component?.reload?.(subPath, query, ctx, isItemAction);
          }
        }
      });
    },

    send(receive: string | Array<string>, values: object, currentName?: string, withTarget?: string): boolean {
      let flag = false
      const scoped = this;
      let receives =
        typeof receive === 'string' ? receive.split(/\s*,\s*/) : receive;
      let withValues: object | undefined = undefined
      if (withTarget) {
        const withComponent = scoped.findSubComponent(withTarget) || scoped.getComponentByName(withTarget) || scoped.findTargetComponent(withTarget) || scoped.getComponentFromArray(withTarget);
        if (withComponent) {
          withValues = withComponent.props.data
        }
      }

      let newValue: any = cloneDeep(values);
      // todo 没找到做提示！
      receives.forEach((name, index) => {
        const askIdx = name.indexOf('?');
        if (~askIdx) {
          const query = name.substring(askIdx + 1);
          const queryObj = qsparse(
            query.replace(
              /\$\{(.*?)\}/,
              (_, match) => '${' + encodeURIComponent(match) + '}'
            )
          );

          name = name.substring(0, askIdx);
          newValue = dataMapping(queryObj, values);
          if (withValues) {
            newValue = { ...newValue, ...withValues }
          }
        } else {
          if (withValues) {
            newValue = { ...newValue, ...withValues }
          }
        }

        const idx = name.indexOf('.');
        let subPath = '';

        if (~idx) {
          subPath = name.substring(1 + idx);
          name = name.substring(0, idx);
        }

        const component = scoped.findSubComponent(name, currentName, index) || scoped.getComponentByName(name) || scoped.findTargetComponent(name) || scoped.getComponentFromArray(name);
        if (component) {
          flag = true
        }
        if (name === 'window' && env && env.updateLocation) {
          const query = {
            ...(location.search ? qsparse(location.search.substring(1)) : {}),
            ...values
          };
          const link = location.pathname + '?' + qsstringify(query);
          env.updateLocation(link, true);
        } else {
          if (tools.isArray(component)) {
            component.forEach(item => {
              item?.receive?.(newValue, subPath);
            })
          } else {
            component?.receive?.(newValue, subPath);
          }
        }
      });
      return flag
    },

    /**
     * 主要是用来关闭指定弹框的
     *
     * @param target 目标 name
     */
    close(target: string | boolean) {
      const scoped = this;

      if (typeof target === 'string') {
        // 过滤已经关掉的，当用户 close 配置多个弹框 name 时会出现这种情况
        target
          .split(/\s*,\s*/)
          .map(name => scoped.getComponentByName(name))
          .filter(component => component && component.props.show)
          .forEach(closeDialog);
      }
    }
  };

  if (!parent) {
    return self;
  }

  !parent.children && (parent.children = []);

  // 把孩子带上
  parent.children!.push(self);

  return self;
}

function closeDialog(component: ScopedComponentType) {
  (component.context as IScopedContext)
    .getComponents()
    .filter(
      item =>
        item &&
        (item.props.type === 'dialog' || item.props.type === 'drawer') &&
        item.props.show
    )
    .forEach(closeDialog);
  component.props.onClose && component.props.onClose();
}

export function HocScoped<
  T extends {
    $path?: string;
    env: RendererEnv;
  }
>(
  ComposedComponent: React.ComponentType<T>
): React.ComponentType<
  T & {
    scopeRef?: (ref: any) => void;
  }
> & {
  ComposedComponent: React.ComponentType<T>;
} {
  type ScopedProps = T & {
    scopeRef?: (ref: any) => void;
  };
  class ScopedComponent extends React.Component<ScopedProps> {
    static displayName = `Scoped(${ComposedComponent.displayName || ComposedComponent.name
      })`;
    static contextType = ScopedContext;
    static ComposedComponent = ComposedComponent;
    ref: any;
    scoped?: IScopedContext;

    constructor(props: ScopedProps, context: IScopedContext) {
      super(props);

      this.scoped = createScopedTools(
        this.props.$path,
        context,
        this.props.env
      );

      const scopeRef = props.scopeRef;
      scopeRef && scopeRef(this.scoped);
    }

    getWrappedInstance() {
      return this.ref;
    }

    @autobind
    childRef(ref: any) {
      while (ref && ref.getWrappedInstance) {
        ref = ref.getWrappedInstance();
      }

      this.ref = ref;
    }

    componentWillUnmount() {
      const scopeRef = this.props.scopeRef;
      scopeRef && scopeRef(null);
      delete this.scoped;
    }

    render() {
      const { scopeRef, ...rest } = this.props;

      return (
        <ScopedContext.Provider value={this.scoped!}>
          <ComposedComponent
            {
            ...(rest as any) /* todo */
            }
            ref={this.childRef}
          />
        </ScopedContext.Provider>
      );
    }
  }

  hoistNonReactStatic(ScopedComponent, ComposedComponent);
  return ScopedComponent;
}

export default HocScoped;
