import React from 'react';
import isPlainObject from 'lodash/isPlainObject';
import {RendererEnv} from './env';
import {RendererProps} from './factory';
import {LocaleContext, TranslateFn} from './locale';
import {RootRenderer} from './RootRenderer';
import {SchemaRenderer} from './SchemaRenderer';
import Scoped from './Scoped';
import {IRendererStore} from './store';
import {ThemeContext} from './theme';
import {Schema, SchemaNode} from './types';
import {autobind, isEmpty} from './utils/helper';
import {RootStoreContext} from './WithRootStore';
import {StatusScoped, StatusScopedProps} from './StatusScoped';

export interface RootRenderProps {
  location?: Location;
  theme?: string;
  data?: Record<string, any>;
  locale?: string;
  [propName: string]: any;
}

export interface RootProps extends StatusScopedProps {
  schema: SchemaNode;
  rootStore: IRendererStore;
  env: RendererEnv;
  theme: string;
  pathPrefix?: string;
  locale?: string;
  translate?: TranslateFn;
  [propName: string]: any;
}

export interface RootWrapperProps {
  env: RendererEnv;
  children: React.ReactNode | Array<React.ReactNode>;
  schema: SchemaNode;
  rootStore: IRendererStore;
  theme: string;
  data?: Record<string, any>;
  context?: Record<string, any>;
  [propName: string]: any;
}

const rootWrappers: Array<(props: RootWrapperProps) => React.ReactNode> = [];

export function addRootWrapper(
  fn: (props: RootWrapperProps) => React.ReactNode
) {
  rootWrappers.push(fn);
}

export class Root extends React.Component<RootProps> {
  @autobind
  resolveDefinitions(name: string) {
    const definitions = (this.props.schema as Schema).definitions;
    if (!name || isEmpty(definitions)) {
      return {};
    }
    return definitions && definitions[name];
  }

  render() {
    const {
      schema,
      rootStore,
      env,
      pathPrefix,
      location,
      data,
      context,
      locale,
      translate,
      ...rest
    } = this.props;
    const theme = env.theme;
    let themeName = this.props.theme || 'cxd';

    if (themeName === 'default') {
      themeName = 'cxd';
    }

    return (
      <RootStoreContext.Provider value={rootStore}>
        <ThemeContext.Provider value={themeName}>
          <LocaleContext.Provider value={this.props.locale!}>
            {
              rootWrappers.reduce(
                (props: RootWrapperProps, wrapper) => {
                  return {
                    ...props,
                    children: wrapper(props)
                  };
                },
                {
                  pathPrefix: pathPrefix || '',
                  schema: isPlainObject(schema)
                    ? {
                        type: 'page',
                        ...(schema as any)
                      }
                    : schema,
                  ...rest,
                  render: renderChild,
                  rootStore: rootStore,
                  resolveDefinitions: this.resolveDefinitions,
                  location: location,
                  data,
                  env: env,
                  classnames: theme.classnames,
                  classPrefix: theme.classPrefix,
                  locale: locale,
                  translate: translate,
                  children: (
                    <RootRenderer
                      pathPrefix={pathPrefix || ''}
                      schema={
                        isPlainObject(schema)
                          ? {
                              type: 'page',
                              ...(schema as any)
                            }
                          : schema
                      }
                      {...rest}
                      render={renderChild}
                      rootStore={rootStore}
                      resolveDefinitions={this.resolveDefinitions}
                      location={location}
                      data={data}
                      context={context}
                      env={env}
                      classnames={theme.classnames}
                      classPrefix={theme.classPrefix}
                      locale={locale}
                      translate={translate}
                    />
                  )
                } as RootWrapperProps
              ).children
            }
          </LocaleContext.Provider>
        </ThemeContext.Provider>
      </RootStoreContext.Provider>
    );
  }
}

export interface renderChildProps
  extends Partial<Omit<RendererProps, 'statusStore'>>,
    StatusScopedProps {
  env: RendererEnv;
}
export type ReactElement = React.ReactNode[] | JSX.Element | null | false;

const StatusScopedSchemaRenderer = StatusScoped(SchemaRenderer);

export function renderChildren(
  prefix: string,
  node: SchemaNode,
  props: renderChildProps
): ReactElement {
  if (Array.isArray(node)) {
    var elemKey = props.key || props.propKey || props.id || '';

    return node.map((node, index) =>
      renderChild(`${prefix}/${index}`, node, {
        ...props,
        key: `${elemKey ? `${elemKey}-` : ''}${index}`
      })
    );
  }

  return renderChild(prefix, node, props);
}

export function renderChild(
  prefix: string,
  node: SchemaNode,
  props: renderChildProps
): ReactElement {
  if (Array.isArray(node)) {
    return renderChildren(prefix, node, props);
  }

  const typeofnode = typeof node;

  if (typeofnode === 'undefined' || node === null) {
    return null;
  } else if (React.isValidElement(node)) {
    return node;
  }

  let schema: Schema =
    typeofnode === 'string' || typeofnode === 'number'
      ? {type: 'tpl', tpl: String(node)}
      : (node as Schema);

  const transform = props.propsTransform;

  if (transform) {
    props = {...props};
    delete props.propsTransform;

    props = transform(props);
  }

  if (
    ['dialog', 'drawer'].includes(schema?.type) &&
    !schema?.component &&
    !schema?.children
  ) {
    // 因为状态判断实在 SchemaRenderer 里面判断的
    // 找渲染器也是在那，所以没办法在之前根据渲染器信息来包裹个组件下发 statusStore
    // 所以这里先根据 type 来处理一下
    // 等后续把状态处理再抽一层，可以把此处放到 SchemaRenderer 里面去
    return (
      <StatusScopedSchemaRenderer
        render={renderChild as any}
        {...props}
        schema={schema}
        propKey={schema.key}
        $path={`${prefix ? `${prefix}/` : ''}${(schema && schema.type) || ''}`}
      />
    );
  }

  return (
    <SchemaRenderer
      render={renderChild as any}
      {...props}
      schema={schema}
      propKey={schema.key}
      $path={`${prefix ? `${prefix}/` : ''}${(schema && schema.type) || ''}`}
    />
  );
}

export default StatusScoped(Scoped(Root));
