/**
 * @file scoped.jsx.
 * @author fex
 */

import React from 'react';
import { findDOMNode } from 'react-dom';
import { RendererProps } from '../factory';
import hoistNonReactStatic from 'hoist-non-react-statics';
import { Action } from '../types';
import keycode from 'keycode';
import Overlay from '../components/Overlay';
import PopOver from '../components/PopOver';
import { Icon } from '../components/icons';
import { SchemaCollection, SchemaObject } from '../Schema';

export type SchemaQuickEditObject =
  /**
   * 直接就是个表单项
   */
  | ({
    /**
     * 是否立即保存
     */
    saveImmediately?: boolean;

    /**
     * 接口保存失败后，是否重置组件编辑状态
     */
    resetOnFailed?: boolean;

    /**
     * 是否直接内嵌
     */
    mode?: 'inline';
  } & SchemaObject)

  /**
   * 表单项集合
   */
  | {
    /**
     * 是否立即保存
     */
    saveImmediately?: boolean;

    /**
     * 接口保存失败后，是否重置组件编辑状态
     */
    resetOnFailed?: boolean;

    /**
     * 是否直接内嵌
     */
    mode?: 'inline';

    body: SchemaCollection;
  };

export type SchemaQuickEdit = boolean | SchemaQuickEditObject;

export interface QuickEditConfig {
  saveImmediately?: boolean;
  resetOnFailed?: boolean;
  mode?: 'inline' | 'dialog' | 'popOver' | 'append';
  type?: string;
  body?: any;
  prefix?: string;
  focusable?: boolean;
  popOverClassName?: string;
  [propName: string]: any;
}

export interface QuickEditProps extends RendererProps {
  name?: string;
  label?: string;
  prefix?: string;
  quickEdit: boolean | QuickEditConfig;
  quickEditEnabled?: boolean;
}

export interface QuickEditState {
  isOpened: boolean;
}

let inited: boolean = false;
let currentOpened: any;

export const HocQuickEdit =
  (config: Partial<QuickEditConfig> = {}) =>
    (Component: React.ComponentType<any>): any => {
      class QuickEditComponent extends React.PureComponent<
        QuickEditProps,
        QuickEditState
      > {
        target: HTMLElement | null;
        overlay: HTMLElement | null;
        static ComposedComponent = Component;
        constructor(props: QuickEditProps) {
          super(props);

          this.openQuickEdit = this.openQuickEdit.bind(this);
          this.closeQuickEdit = this.closeQuickEdit.bind(this);
          this.handleAction = this.handleAction.bind(this);
          this.handleSubmit = this.handleSubmit.bind(this);
          this.handleKeyUp = this.handleKeyUp.bind(this);
          this.overlayRef = this.overlayRef.bind(this);
          this.handleWindowKeyPress = this.handleWindowKeyPress.bind(this);
          this.handleWindowKeyDown = this.handleWindowKeyDown.bind(this);
          this.formRef = this.formRef.bind(this);
          this.handleInit = this.handleInit.bind(this);
          this.handleChange = this.handleChange.bind(this);

          this.state = {
            isOpened: false
          };
        }

        componentWillUnmount(): void {
          inited = false
          this.overlay = null
          this.target = null
          document.body.removeEventListener('keypress', this.handleWindowKeyPress);
          document.body.removeEventListener('keydown', this.handleWindowKeyDown);
        }

        componentDidMount() {
          this.target = findDOMNode(this) as HTMLElement;

          if (inited) {
            return;
          }

          inited = true;
          document.body.addEventListener('keypress', this.handleWindowKeyPress);
          document.body.addEventListener('keydown', this.handleWindowKeyDown);
        }

        formRef(ref: any) {
          const { quickEditFormRef, rowIndex, colIndex } = this.props;

          if (quickEditFormRef) {
            while (ref && ref.getWrappedInstance) {
              ref = ref.getWrappedInstance();
            }

            quickEditFormRef(ref, colIndex, rowIndex);
          }
        }

        handleWindowKeyPress(e: Event) {

          const ns = this.props.classPrefix;
          let el: HTMLElement = (e.target as HTMLElement).closest(
            `.${ns}Field--quickEditable`
          ) as HTMLElement;
          if (!el) {
            return;
          }
          const table = el.closest('table');
          if (!table) {
            return;
          }

          if (
            keycode(e) === 'space' &&
            !~['INPUT', 'TEXTAREA'].indexOf(el.tagName)
          ) {
            e.preventDefault();
            e.stopPropagation();
          }
        }

        handleWindowKeyDown(e: Event) {
          const code = keycode(e);

          if (code === 'esc' && currentOpened) {
            currentOpened.closeQuickEdit(true);
          } else if (
            ~['INPUT', 'TEXTAREA'].indexOf((e.target as HTMLElement).tagName) ||
            (e.target as HTMLElement).contentEditable === 'true' ||
            !~['up', 'down', 'left', 'right', 'enter'].indexOf(code)
          ) {
            return;
          }

          e.stopPropagation();
          e.preventDefault();
          const ns = this.props.classPrefix;
          let el: HTMLElement =
            ((e.target as HTMLElement).closest(
              `.${ns}Field--quickEditable`
            ) as HTMLElement) ||
            document.querySelector(`.${ns}Field--quickEditable`);
          if (!el) {
            return;
          }

          let table = el.closest('table');
          if (!table) {
            return;
          }

          let current = table.querySelector(
            `.${ns}Field--quickEditable:focus`
          ) as HTMLTableDataCellElement;

          if (!current) {
            let dom = table.querySelector(
              `.${ns}Field--quickEditable[tabindex]`
            ) as HTMLElement;
            dom && dom.focus();
          } else {
            let prevTr, nextTr, prevTd, nextTd;
            // 获取下一个可以编辑的
            const findNextQuickEditable = (_currentParent: HTMLElement, dir: 'up' | 'down'): any => {
              let curParent
              if (!_currentParent) return _currentParent // 如果没有找到对应的返回自身
              switch (code) {
                case 'up':
                  curParent = _currentParent.previousSibling
                  break
                case 'down':
                  curParent = _currentParent.nextSibling
                  break
              }
              if (!curParent) return _currentParent // 如果没有找到对应的返回自身
              // 如果对应的表格行的对应项有对应的编辑项 返回
              if ((curParent as HTMLElement)?.children?.[current.cellIndex]?.className?.includes(`${ns}Field--quickEditable`))
                return curParent
              else return findNextQuickEditable(curParent as HTMLElement, dir)
            }
            switch (code) {
              case 'up':
                prevTr = findNextQuickEditable(current.parentNode as any, code)
                if (prevTr) {
                  let index = current.cellIndex;
                  if (prevTr.children[index])
                    (prevTr.children[index] as HTMLElement)?.focus();
                }
                break;
              case 'down':
                nextTr = findNextQuickEditable(current.parentNode as any, code)

                if (nextTr) {
                  let index = current.cellIndex;
                  if (nextTr.children[index])
                    (nextTr.children[index] as HTMLElement)?.focus();
                }
                break;
              case 'left':
                prevTd = current.previousElementSibling as HTMLTableCellElement;

                while (prevTd) {
                  if (prevTd.matches(`.${ns}Field--quickEditable[tabindex]`)) {
                    break;
                  }
                  prevTd = prevTd.previousElementSibling;
                }

                if (prevTd) {
                  (prevTd as HTMLElement)?.focus();
                } else if ((current.parentNode as HTMLElement).previousSibling) {
                  let tds = (
                    (current.parentNode as HTMLElement)
                      .previousSibling as HTMLElement
                  ).querySelectorAll(`.${ns}Field--quickEditable[tabindex]`);

                  if (tds.length) {
                    (tds[tds.length - 1] as HTMLElement)?.focus();
                  }
                }
                break;
              case 'right':
                nextTd = current.nextSibling;
                while (nextTd) {
                  if (
                    (nextTd as Element).matches(
                      `.${ns}Field--quickEditable[tabindex]`
                    )
                  ) {
                    break;
                  }

                  nextTd = nextTd.nextSibling;
                }

                if (nextTd) {
                  (nextTd as HTMLElement)?.focus();
                } else if ((current.parentNode as HTMLElement).nextSibling) {
                  nextTd = (
                    (current.parentNode as HTMLElement).nextSibling as HTMLElement
                  ).querySelector(`.${ns}Field--quickEditable[tabindex]`);

                  if (nextTd) {
                    (nextTd as any)?.focus();
                  }
                }
                break;
              case 'enter':
                currentOpened?.closeQuickEdit(); // 如果有其他的先关闭其他的
                (current.querySelector('[class*="quickEditBtn"]') as any)?.click?.()
                break
            }
          }
        }

        // handleClickOutside() {
        //     this.closeQuickEdit();
        // }

        overlayRef(ref: any) {
          this.overlay = ref;
        }

        handleAction(e: any, action: Action, ctx: object) {
          const { onAction } = this.props;

          if (action.actionType === 'cancel' || action.actionType === 'close') {
            this.closeQuickEdit(true);
            return;
          }

          onAction && onAction(e, action, ctx);
        }

        handleSubmit(values: object) {
          const { onQuickChange, quickEdit } = this.props;

          this.closeQuickEdit(true);
          onQuickChange(
            values,
            (quickEdit as QuickEditConfig).saveImmediately,
            false,
            (quickEdit as QuickEditConfig).resetOnFailed
          );

          return false;
        }

        handleInit(values: object) {
          const { onQuickChange } = this.props;
          onQuickChange(values, false, false);
        }

        handleChange(values: object) {
          const { onQuickChange, quickEdit } = this.props;

          onQuickChange(
            values,
            (quickEdit as QuickEditConfig).saveImmediately,
            false,
            (quickEdit as QuickEditConfig).resetOnFailed
          );
        }

        openQuickEdit() {
          currentOpened = this;
          this.setState({
            isOpened: true
          });
        }

        closeQuickEdit(e?: any) {
          if (!this.state.isOpened) {
            return;
          }
          currentOpened = null
          const ns = this.props.classPrefix;
          this.setState(
            {
              isOpened: false
            },
            () => {
              // 如果是手动点击的自动聚焦回去
              if (e) {
                let el = findDOMNode(this) as HTMLElement;
                let table = el.closest('table') as HTMLElement;
                ((table &&
                  table.querySelectorAll(`td.${ns}Field--quickEditable:focus`)
                    .length) ||
                  el) &&
                  el.focus();
              }
            }
          );
        }

        buildSchema() {
          const { quickEdit, name, label, translate: __, defaultOpen, column, data } = this.props;
          let schema;

          const domicile = {
            column, label, data
          }
          if (quickEdit === true) {
            schema = {
              type: 'form',
              title: '',
              autoFocus: true,
              body: [
                {
                  type: 'input-text',
                  name,
                  placeholder: label,
                  label: false,
                  defaultOpen,
                  domicile
                }
              ]
            };
          } else if (quickEdit) {
            if (
              quickEdit.body &&
              !~['combo', 'group', 'panel', 'fieldSet', 'fieldset'].indexOf(
                (quickEdit as any).type
              )
            ) {
              schema = {
                title: '',
                autoFocus: (quickEdit as QuickEditConfig).mode !== 'inline',
                ...quickEdit,
                mode: 'normal',
                type: 'form'
              };
            } else {
              schema = {
                title: '',
                className: quickEdit.formClassName,
                type: 'form',
                autoFocus: (quickEdit as QuickEditConfig).mode !== 'inline',
                mode: 'normal',
                body: [
                  {
                    type: quickEdit.type || 'input-text',
                    name: quickEdit.name || name,
                    ...quickEdit,
                    mode: undefined,
                    defaultOpen,
                    domicile

                  }
                ]
              };
            }
          }

          if (schema) {
            schema = {
              ...schema,
              wrapWithPanel: (quickEdit as QuickEditConfig).mode !== 'inline',
              actions:
                (quickEdit as QuickEditConfig).mode === 'inline'
                  ? []
                  : [
                    {
                      type: 'button',
                      label: __('cancel'),
                      actionType: 'cancel'
                    },

                    {
                      label: __('confirm'),
                      type: 'submit',
                      primary: true
                    }
                  ]
            };
          }

          return schema || 'error';
        }

        handleKeyUp(e: Event) {
          const code = keycode(e);
          if (
            code === 'space' &&
            !~['INPUT', 'TEXTAREA'].indexOf((e.target as HTMLElement).tagName)
          ) {
            e.preventDefault();
            e.stopPropagation();
            this.openQuickEdit();
          }
        }

        renderPopOver() {
          let {
            quickEdit,
            render,
            popOverContainer,
            classPrefix: ns,
            classnames: cx,
            canAccessSuperData
          } = this.props;

          const content = (
            <div
              ref={this.overlayRef}
              className={cx((quickEdit as QuickEditConfig).className)}
            >
              {render('quick-edit-form', this.buildSchema(), {
                value: undefined,
                onSubmit: this.handleSubmit,
                onAction: this.handleAction,
                onChange: null,
                formLazyChange: false,
                ref: this.formRef,
                popOverContainer: () => this.overlay,
                canAccessSuperData,
                formStore: undefined
              })}
            </div>
          );

          popOverContainer = popOverContainer || (() => findDOMNode(this));

          return (
            <Overlay
              container={() => findDOMNode(this)}
              target={() => this.target}
              onHide={() => {
                this.closeQuickEdit(true)
              }}
              placement="left-top right-top left-bottom right-bottom left-top"
              show
            >
              <PopOver
                classPrefix={ns}
                className={cx(
                  `${ns}QuickEdit-popover`,
                  (quickEdit as QuickEditConfig).popOverClassName
                )}
                onHide={() => {
                  this.closeQuickEdit(true)
                }}
                overlay
              >
                {content}
              </PopOver>
            </Overlay>
          );
        }

        render() {
          const {
            onQuickChange,
            quickEdit,
            quickEditEnabled,
            className,
            classnames: cx,
            render,
            noHoc,
            canAccessSuperData,
            disabled,
            prefix
          } = this.props;
          if (
            !quickEdit ||
            !onQuickChange ||
            quickEditEnabled === false ||
            noHoc ||
            disabled
          ) {
            return <Component {...this.props} />;
          }

          if ((quickEdit as QuickEditConfig).mode === 'inline') {
            return (
              <Component {...this.props}>
                {render('inline-form', this.buildSchema(), {
                  value: undefined,
                  wrapperComponent: 'div',
                  className: cx('Form--quickEdit'),
                  ref: this.formRef,
                  prefix: prefix ?? (quickEdit as QuickEditConfig).prefix,
                  simpleMode: true,
                  onInit: this.handleInit,
                  onChange: this.handleChange,
                  formLazyChange: false,
                  quickEditForm: true,
                  canAccessSuperData
                })}
              </Component>
            );
          } else {
            return (
              <Component
                {...this.props}
                className={cx(`Field--quickEditable`, className, {
                  in: this.state.isOpened
                })}
                tabIndex={
                  (quickEdit as QuickEditConfig).focusable === false
                    ? undefined
                    : '0'
                }
                onKeyUp={this.handleKeyUp}
              >
                <div style={{ display: 'flex' }} >
                  <Component {...this.props} wrapperComponent={''} noHoc />
                  <span
                    key="edit-btn"
                    className={cx('Field-quickEditBtn')}
                    onClick={this.openQuickEdit}
                  >
                    <Icon icon="pencil" className="icon" />
                  </span>
                  {this.state.isOpened ? this.renderPopOver() : null}
                </div>
              </Component>
            );
          }
        }
      }

      hoistNonReactStatic(QuickEditComponent, Component);

      return QuickEditComponent;
    };

export default HocQuickEdit;
