import { h, Fragment } from 'preact';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { IntlProvider, Text } from 'preact-i18n';
import { StyleSheet } from 'aphrodite';
import moize from 'moize';
import { noop } from 'duo-toolbox/utils/functions';
import { discardEvent } from 'duo-toolbox/utils/ui';
import { BASE, CONTEXT_CHALLENGE, CONTEXT_FORUM, useStyles } from './index';

const FORUM_FOLLOW_BUTTON_SELECTOR = '._13Bfz button';
const FORUM_NEW_POST_BUTTONS_SELECTOR = '._1KvMS textarea + div button';

/**
 * @type {Function}
 * @returns {string|null} When in the context of a forum discussion, the inline styles applied to the follow button.
 */
const getForumFollowButtonInlineStyles = moize(() => String(
  document.querySelector(FORUM_FOLLOW_BUTTON_SELECTOR)?.getAttribute('style') || ''
));

/**
 * @type {Function}
 * @returns {object|null} When in the context of a forum discussion, the inline styles applied to the new post buttons.
 */
const getForumNewPostButtonsInlineStyles = moize(() => {
  const postButtons = Array.from(document.querySelectorAll(FORUM_NEW_POST_BUTTONS_SELECTOR));

  return (2 !== postButtons.length)
    ? null
    : {
      [COMMIT_BUTTON]: String(postButtons[0].getAttribute('style') || ''),
      [ROLLBACK_BUTTON]: String(postButtons[1].getAttribute('style') || ''),
    };
});

const UserReference =
  ({
     context = CONTEXT_CHALLENGE,
     reference = '',
     isEditable = true,
     onUpdate = noop,
   }) => {
    const editInput = useRef();
    const [ isEditing, setIsEditing ] = useState(false);

    const commitEdit = useCallback(event => {
      discardEvent(event);

      if (editInput.current) {
        const newReference = String(editInput.current.value || '').trim();

        if (('' !== newReference) && (newReference !== reference)) {
          onUpdate(newReference);
        }
      }

      setIsEditing(false);
    }, [ reference, onUpdate, setIsEditing ]);

    const rollbackEdit = useCallback(event => {
      discardEvent(event);
      setIsEditing(false);
    }, [ setIsEditing ]);

    const onEditKeyDown = useCallback(event => {
      if ('Enter' === event.key) {
        commitEdit(event);
      } else if ('Escape' === event.key) {
        rollbackEdit(event);
      }
    }, [ commitEdit, rollbackEdit ]);

    // Focuses the input when we just have switched to edit mode.
    useEffect(() => {
      if (editInput.current) {
        setTimeout(() => {
          if (document.activeElement !== editInput.current.focused) {
            const length = editInput.current.value.length;
            editInput.current.focus();
            // Place the cursor at the end of the text.
            editInput.current.setSelectionRange(length + 1, length + 1);
          }
        });
      }
    }, [ isEditing, editInput ]);

    const [ Wrapper, Title, Value, EditWrapper ] = (CONTEXT_CHALLENGE === context)
      ? [ 'div', 'h3', 'p', 'p' ]
      : [ 'h2', 'span', 'span', Fragment ];

    const getElementClassNames = useStyles(CLASS_NAMES, STYLE_SHEETS, [ context ]);

    let buttonInlineStyles = {};
    let additionalButtonClass = null;

    if (CONTEXT_FORUM === context) {
      buttonInlineStyles = getForumNewPostButtonsInlineStyles();

      if (null === buttonInlineStyles) {
        const inlineStyles = getForumFollowButtonInlineStyles();

        buttonInlineStyles = {
          [COMMIT_BUTTON]: inlineStyles,
          [ROLLBACK_BUTTON]: inlineStyles,
        };

        additionalButtonClass = FALLBACK_BUTTON;
      }
    }

    const valueKeys = [
      VALUE,
      isEditable && EDITABLE_VALUE,
      ('' === reference) && EMPTY_VALUE,
    ].filter(Boolean);

    return (
      <IntlProvider scope="user_reference">
        <Wrapper className={getElementClassNames(WRAPPER)}>
          <Title className={getElementClassNames(TITLE)}>
            <Text id="your_reference">Your reference:</Text>
          </Title>
          {!isEditing
            ? ( // Not editing.
              <Value onClick={() => isEditable && setIsEditing(true)} className={getElementClassNames(valueKeys)}>
                {('' !== reference) ? reference : <Text id="none">None yet</Text>}
              </Value>
            ) : ( // Editing.
              <EditWrapper>
                <textarea
                  ref={editInput}
                  defaultValue={reference}
                  dir="auto"
                  onKeyDown={onEditKeyDown}
                  className={getElementClassNames(EDIT_FIELD)}
                />

                <button
                  onClick={commitEdit}
                  style={buttonInlineStyles[COMMIT_BUTTON] || ''}
                  className={getElementClassNames([ BUTTON, COMMIT_BUTTON, additionalButtonClass ])}
                >
                  <Text id="update">Update</Text>
                </button>

                <span className={getElementClassNames(BUTTON_SPACER)}>
                  <button
                    onClick={rollbackEdit}
                    style={buttonInlineStyles[ROLLBACK_BUTTON] || ''}
                    className={getElementClassNames([ BUTTON, ROLLBACK_BUTTON, additionalButtonClass ])}
                  >
                    <Text id="cancel">Cancel</Text>
                  </button>
                </span>
              </EditWrapper>
            )}
        </Wrapper>
      </IntlProvider>
    );
  };

export default UserReference;

const WRAPPER = 'wrapper';
const TITLE = 'title';
const VALUE = 'value';
const EMPTY_VALUE = 'empty_value';
const EDITABLE_VALUE = 'editable_value';
const EDIT_FIELD = 'edit_field';
const BUTTON = 'button';
const COMMIT_BUTTON = 'commit_button';
const ROLLBACK_BUTTON = 'rollback_button';
const FALLBACK_BUTTON = 'fallback_button';
const BUTTON_SPACER = 'button_spacer';

const CLASS_NAMES = {
  [CONTEXT_CHALLENGE]: {
    [EDIT_FIELD]: [
      // Copied from the text answer field.
      '_2EMUT',
      '_1QDX9',
      'st_Fn',
      '_2ti2i',
      'sXpqy',
    ],
    [BUTTON]: [
      // Copied from the special letter buttons provided for some languages (such as French).
      // The class responsible for the small and square dimensions is ignored here.
      '_3iVqs',
      '_3HHNB',
      '_2A7uO',
      '_2gwtT',
      '_1nlVc',
      '_2fOC9',
      't5wFJ',
      '_3dtSu',
      '_25Cnc',
      '_3yAjN',
      '_3Ev3S',
      '_1figt',
    ],
    // Found in the "app" stylesheet. Adds the main link color.
    // Use a class located after the one responsible for the color and background of the button.
    [COMMIT_BUTTON]: [ '_2__FI' ],
  },
  [CONTEXT_FORUM]: {
    // Copied from the (heading) wrapper of the "Translation:" subtitle and the translation value.
    [WRAPPER]: [ '_2qRu2' ],
    // Copied from the "Translation:" subtitle.
    [TITLE]: [ '_1gXMJ' ],
    // Copied from the post text field.
    [EDIT_FIELD]: [ '_1Ch3x', '_2yvtl', 'gFN2J' ],
    // The class names applied to both post buttons.
    [BUTTON]: [ '_2NzLI', 'QHkFc' ],
    // The class names specific to the "Post" button.
    [COMMIT_BUTTON]: [ '_1qPrY', '_2pnz9' ],
    // The class names specific to the "Cancel" button.
    [ROLLBACK_BUTTON]: [ '_3kaGF', '_1O1Bz' ],
    // One of the class name from the "Cancel" button which adds 3D-like border widths.
    [FALLBACK_BUTTON]: [ '_1O1Bz' ],
    // Copied from the (spacing) wrapper of the "Cancel" button.
    [BUTTON_SPACER]: [ '_3cCqs' ],
  },
};

const STYLE_SHEETS = {
  [BASE]: StyleSheet.create({
    [EMPTY_VALUE]: {
      fontStyle: 'italic',
    },
    [EDITABLE_VALUE]: {
      cursor: 'text',
    },
  }),
  [CONTEXT_CHALLENGE]: StyleSheet.create({
    [VALUE]: {
      fontWeight: 'normal',
      marginTop: '10px',
    },
    [EDIT_FIELD]: {
      marginBottom: '10px',
    },
    [COMMIT_BUTTON]: {
      ':after': {
        borderColor: 'currentColor',
      },
    },
    [BUTTON_SPACER]: {
      marginLeft: '10px',
    },
  }),
};