// @flow
import 'easymde/dist/easymde.min.css';

import './plugins/inline-attachment/inline-attachment';
import './plugins/inline-attachment/codemirror-4.inline-attachment';
import { IMG_CDN_PUBLISH_URL, JSON_RESPONSE_KEYS, UPLOAD_CONFIG } from 'constants/cdn_urls';
import { FF_MAX_CHARS_DEFAULT } from 'constants/form-field';
import { openEditorMenu, stopContextMenu } from 'util/context-menu';
import { lazyImport } from 'util/lazyImport';
import MarkdownPreview from 'component/common/markdown-preview';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import SimpleMDE from 'react-simplemde-editor';
import type { ElementRef } from 'react';
import { InputSimple, BlockWrapWrapper } from './input-simple';
import { InputSelect } from './input-select';
import { CountInfo, QuickAction, Label } from './common';
import { TextareaWrapper } from './slim-input-field';

// prettier-ignore
const TextareaWithSuggestions = lazyImport(() => import('component/textareaWithSuggestions' /* webpackChunkName: "suggestions" */));

type Props = {
  uri?: string,
  affixClass?: string, // class applied to prefix/postfix label
  autoFocus?: boolean,
  blockWrap: boolean,
  charCount?: number,
  children?: React$Node,
  defaultValue?: string | number,
  disabled?: boolean,
  error?: string | boolean,
  helper?: string | React$Node,
  hideSuggestions?: boolean,
  inputButton?: React$Node,
  isLivestream?: boolean,
  label?: any,
  labelOnLeft: boolean,
  max?: number,
  min?: number,
  name: string,
  placeholder?: string | number,
  postfix?: string,
  prefix?: string,
  quickActionLabel?: string,
  range?: number,
  readOnly?: boolean,
  stretch?: boolean,
  textAreaMaxLength?: number,
  type?: string,
  value?: string | number,
  slimInput?: boolean,
  slimInputButtonRef?: any,
  commentSelectorsProps?: any,
  showSelectors?: any,
  submitButtonRef?: any,
  tipModalOpen?: boolean,
  noticeLabel?: any,
  onSlimInputClose?: () => void,
  onChange?: (any) => any,
  setShowSelectors?: ({ tab?: string, open: boolean }) => void,
  quickActionHandler?: (any) => any,
  render?: () => React$Node,
  handleTip?: (isLBC: boolean) => any,
  handleSubmit?: () => any,
};

type State = {
  drawerOpen: boolean,
};

export class FormField extends React.PureComponent<Props, State> {
  static defaultProps = { labelOnLeft: false, blockWrap: true };

  input: { current: ElementRef<any> };

  constructor(props: Props) {
    super(props);
    this.input = React.createRef();

    this.state = {
      drawerOpen: false,
    };
  }

  componentDidMount() {
    const { autoFocus, showSelectors, slimInput } = this.props;
    const input = this.input.current;

    if (input && autoFocus) input.focus();
    if (slimInput && showSelectors && showSelectors.open && input) input.blur();
  }

  render() {
    const {
      uri,
      affixClass,
      autoFocus,
      blockWrap,
      charCount,
      children,
      error,
      helper,
      hideSuggestions,
      inputButton,
      isLivestream,
      label,
      labelOnLeft,
      name,
      postfix,
      prefix,
      quickActionLabel,
      stretch,
      textAreaMaxLength,
      type,
      slimInput,
      slimInputButtonRef,
      commentSelectorsProps,
      showSelectors,
      submitButtonRef,
      tipModalOpen,
      noticeLabel,
      onSlimInputClose,
      quickActionHandler,
      setShowSelectors,
      render,
      handleTip,
      handleSubmit,
      max,
      ...inputProps
    } = this.props;

    const errorMessage = typeof error === 'object' ? error.message : error;

    const wrapperProps = { type, helper };
    const labelProps = { name, label };
    const countInfoProps = { charCount, textAreaMaxLength };
    const quickActionProps = { label: quickActionLabel, quickActionHandler };
    const inputSimpleProps = { name, label, ...inputProps };
    const inputSelectProps = { name, error, label, children, ...inputProps };

    switch (type) {
      case 'radio':
        return (
          <FormFieldWrapper {...wrapperProps}>
            <BlockWrapWrapper blockWrap={blockWrap}>
              <InputSimple {...inputSimpleProps} type="radio" />
            </BlockWrapWrapper>
          </FormFieldWrapper>
        );
      case 'checkbox':
        return (
          <FormFieldWrapper {...wrapperProps}>
            <div className="checkbox">
              <InputSimple {...inputSimpleProps} type="checkbox" />
            </div>
          </FormFieldWrapper>
        );
      case 'range':
        return (
          <FormFieldWrapper {...wrapperProps}>
            <div className="range">
              <InputSimple {...inputSimpleProps} type="range" />
            </div>
          </FormFieldWrapper>
        );
      case 'select':
        return (
          <FormFieldWrapper {...wrapperProps}>
            <InputSelect {...inputSelectProps} />
          </FormFieldWrapper>
        );
      case 'select-tiny':
        return (
          <FormFieldWrapper {...wrapperProps}>
            <InputSelect {...inputSelectProps} className="select--slim" />
          </FormFieldWrapper>
        );
      case 'markdown':
        const handleEvents = { contextmenu: openEditorMenu };

        const getInstance = (editor) => {
          // SimpleMDE max char check
          editor.codemirror.on('beforeChange', (instance, changes) => {
            if (textAreaMaxLength && changes.update) {
              let str = changes.text.join('\n');
              let delta = str.length - (instance.indexFromPos(changes.to) - instance.indexFromPos(changes.from));

              if (delta <= 0) return;

              delta = instance.getValue().length + delta - textAreaMaxLength;
              if (delta > 0) {
                str = str.substr(0, str.length - delta);
                changes.update(changes.from, changes.to, str.split('\n'));
              }
            }
          });

          // "Create Link (Ctrl-K)": highlight URL instead of label:
          editor.codemirror.on('changes', (instance, changes) => {
            try {
              // Grab the last change from the buffered list. I assume the
              // buffered one ('changes', instead of 'change') is more efficient,
              // and that "Create Link" will always end up last in the list.
              const lastChange = changes[changes.length - 1];
              if (lastChange.origin === '+input') {
                // https://github.com/Ionaru/easy-markdown-editor/blob/8fa54c496f98621d5f45f57577ce630bee8c41ee/src/js/easymde.js#L765
                const EASYMDE_URL_PLACEHOLDER = '(https://)';

                // The URL placeholder is always placed last, so just look at the
                // last text in the array to also cover the multi-line case:
                const urlLineText = lastChange.text[lastChange.text.length - 1];

                if (urlLineText.endsWith(EASYMDE_URL_PLACEHOLDER) && urlLineText !== '[]' + EASYMDE_URL_PLACEHOLDER) {
                  const from = lastChange.from;
                  const to = lastChange.to;
                  const isSelectionMultiline = lastChange.text.length > 1;
                  const baseIndex = isSelectionMultiline ? 0 : from.ch;

                  // Everything works fine for the [Ctrl-K] case, but for the
                  // [Button] case, this handler happens before the original
                  // code, thus our change got wiped out.
                  // Add a small delay to handle that case.
                  setTimeout(() => {
                    instance.setSelection(
                      { line: to.line, ch: baseIndex + urlLineText.lastIndexOf('(') + 1 },
                      { line: to.line, ch: baseIndex + urlLineText.lastIndexOf(')') }
                    );
                  }, 25);
                }
              }
            } catch (e) {} // Do nothing (revert to original behavior)
          });

          // Add ability to upload pasted/dragged image (https://github.com/sparksuite/simplemde-markdown-editor/issues/328#issuecomment-227075500)
          window.inlineAttachment.editors.codemirror4.attach(editor.codemirror, {
            uploadUrl: IMG_CDN_PUBLISH_URL,
            uploadFieldName: UPLOAD_CONFIG.BLOB_KEY,
            extraParams: { [UPLOAD_CONFIG.ACTION_KEY]: UPLOAD_CONFIG.ACTION_VAL },
            filenameTag: '{filename}',
            urlText: '![image]({filename})',
            jsonFieldName: JSON_RESPONSE_KEYS.UPLOADED_URL,
            errorText: '![image]("failed to upload file")',
          });
        };

        return (
          <FormFieldWrapper {...wrapperProps}>
            <div className="form-field--SimpleMDE" onContextMenu={stopContextMenu}>
              <fieldset-section>
                <div className="form-field__two-column">
                  <div>
                    <Label {...labelProps} />
                  </div>

                  <QuickAction {...quickActionProps} />
                </div>

                <SimpleMDE
                  {...inputProps}
                  id={name}
                  type="textarea"
                  events={handleEvents}
                  getMdeInstance={getInstance}
                  options={{
                    spellChecker: false,
                    hideIcons: ['heading', 'image', 'fullscreen', 'side-by-side'],
                    status: [
                      {
                        className: 'editor-statusbar__upload-hint',
                        defaultValue: (el) => {
                          el.innerHTML = __('Attach images by pasting or drag-and-drop.');
                        },
                      },
                      'lines',
                      'words',
                      'cursor',
                    ],
                    previewRender(plainText) {
                      const preview = <MarkdownPreview content={plainText} noDataStore />;
                      return ReactDOMServer.renderToString(preview);
                    },
                  }}
                />

                <CountInfo {...countInfoProps} />
              </fieldset-section>
            </div>
          </FormFieldWrapper>
        );
      case 'textarea':
        const closeSelector =
          setShowSelectors && showSelectors
            ? () => setShowSelectors({ tab: showSelectors.tab || undefined, open: false })
            : () => {};

        return (
          <FormFieldWrapper {...wrapperProps}>
            <fieldset-section>
              <TextareaWrapper
                isDrawerOpen={Boolean(this.state.drawerOpen)}
                toggleDrawer={() => this.setState({ drawerOpen: !this.state.drawerOpen })}
                closeSelector={closeSelector}
                commentSelectorsProps={commentSelectorsProps}
                showSelectors={Boolean(showSelectors && showSelectors.open)}
                slimInput={slimInput}
                slimInputButtonRef={slimInputButtonRef}
                onSlimInputClose={onSlimInputClose}
                tipModalOpen={tipModalOpen}
              >
                {(!slimInput || this.state.drawerOpen) && label && (
                  <div className="form-field__two-column">
                    <Label {...labelProps} />

                    <QuickAction {...quickActionProps} />

                    <CountInfo {...countInfoProps} />
                  </div>
                )}

                {noticeLabel}

                {hideSuggestions ? (
                  <textarea
                    type={type}
                    id={name}
                    maxLength={textAreaMaxLength || FF_MAX_CHARS_DEFAULT}
                    ref={this.input}
                    {...inputProps}
                  />
                ) : (
                  <React.Suspense fallback={null}>
                    <TextareaWithSuggestions
                      spellCheck
                      uri={uri}
                      type={type}
                      id={name}
                      maxLength={textAreaMaxLength || FF_MAX_CHARS_DEFAULT}
                      inputRef={this.input}
                      isLivestream={isLivestream}
                      toggleSelectors={
                        setShowSelectors && showSelectors
                          ? () => {
                              const input = this.input.current;
                              if (!showSelectors.open && input) input.blur();
                              setShowSelectors({ tab: showSelectors.tab || undefined, open: !showSelectors.open });
                            }
                          : undefined
                      }
                      handleTip={handleTip}
                      handleSubmit={() => {
                        if (handleSubmit) handleSubmit();
                        if (slimInput) this.setState({ drawerOpen: false });
                        closeSelector();
                      }}
                      claimIsMine={commentSelectorsProps && commentSelectorsProps.claimIsMine}
                      {...inputProps}
                      slimInput={slimInput}
                      handlePreventClick={
                        !this.state.drawerOpen ? () => this.setState({ drawerOpen: true }) : undefined
                      }
                      autoFocus={this.state.drawerOpen && (!showSelectors || !showSelectors.open)}
                      submitButtonRef={submitButtonRef}
                    />
                  </React.Suspense>
                )}
              </TextareaWrapper>
            </fieldset-section>
          </FormFieldWrapper>
        );
      default:
        const inputElementProps = { type, name, maxLength: max, ref: this.input, ...inputProps };

        return (
          <FormFieldWrapper {...wrapperProps}>
            <fieldset-section>
              {(label || errorMessage) && (
                <div>
                  <Label {...labelProps} errorMessage={errorMessage} />
                  {inputElementProps.maxLength && inputElementProps.value && (
                    <label
                      className={
                        Number(inputElementProps.maxLength) - String(inputElementProps.value).length > 0
                          ? 'input-max-counter'
                          : 'input-max-counter-error'
                      }
                    >
                      {Number(inputElementProps.maxLength) - String(inputElementProps.value).length}
                    </label>
                  )}
                </div>
              )}

              {prefix && <label htmlFor={name}>{prefix}</label>}

              {inputButton ? (
                <input-submit>
                  <input {...inputElementProps} />
                  {inputButton}
                </input-submit>
              ) : (
                <>
                  <input {...inputElementProps} />
                </>
              )}
            </fieldset-section>
          </FormFieldWrapper>
        );
    }
  }
}

export default FormField;

type WrapperProps = {
  type?: string,
  children?: any,
  helper?: any,
};

const FormFieldWrapper = (wrapperProps: WrapperProps) => {
  const { type, children, helper } = wrapperProps;

  return (
    <>
      {type && children}
      {helper && <div className="form-field__help">{helper}</div>}
    </>
  );
};
