import React from "react";
import PropTypes from "prop-types";
import ReactTagsInput from "react-tagsinput";
import Icon from "react-oui-icons";
import classNames from "classnames";

import Token from "../Token";
import { brandBlueDark } from "../../tokens/forimport/index.es";

/**
 * @typedef {Object} TokenWrapper
 * @property {String} name - The text display for the token.
 * @property {String=} style - The style to use for displaying via <Token>
 *            Indicates how to style the token.
 */

/**
 * Key codes which should be interpreted as an
 * indication to add the current text as a new token.
 */
const ADD_KEYS = [
    /** TAB   */
    {
        keyCode: 9,
        match: "\t",
    },
    /** ENTER */
    {
        keyCode: 13,
        match: "\n",
    },
    /** COMMA */
    {
        keyCode: 188,
        match: ",",
    },
];

/**
 * Tokens Input Component
 * @param {Object} props - Properties passed to component
 * @returns {ReactElement}
 */
export const TokensInput = ({
    addOnBlur,
    addOnPaste,
    className,
    extraAddKeys,
    hasSearchIcon,
    isDisabled,
    maxTags,
    onChange,
    onInputBlur,
    onInputChange,
    onInputFocus,
    placeholder,
    tokens,
    ...props
}) => {
    /**
     * Wrap the layout in a flexed <div>
     * https://github.com/olahol/react-tagsinput#renderlayout
     * @param {Array<ReactElement>} tokenComponents - Token components
     * @param {ReactElement} inputComponent - Input component
     * @returns {ReactElement}
     */
    function renderLayout(tokenComponents, inputComponent) {
        return (
            <div className="flex flex-1 flex-wrap flex-align--center" style={{ resize: "both" }}>
                {hasSearchIcon && (
                    <span className="flex flex-align--center push-half--sides">
                        <Icon name="search" fill={brandBlueDark} />
                    </span>
                )}
                {tokenComponents}
                {inputComponent}
            </div>
        );
    }

    /**
     * Render an OUI Token to display each token.
     * https://github.com/olahol/react-tagsinput#rendertag
     *
     * @param {Object} renderOptions - Values to render token
     * @returns {ReactElement}
     */
    // eslint-disable-next-line react/prop-types
    function renderToken({ tag, key, onRemove }) {
        function onDismiss() {
            onRemove(key);
        }
        const { name, style, backgroundColor } = tag;
        return (
            <Token
                key={key}
                hasSnugWrap={true}
                isDismissible={!isDisabled}
                onDismiss={onDismiss}
                name={name}
                style={style}
                backgroundColor={backgroundColor}
                showWell={false}
                testSection={`token-${name}`}
            />
        );
    }

    /**
     * Handler for when a string is pasted into the input.
     * Splits the string on all the addKeys and extraAddKeys.
     * https://github.com/olahol/react-tagsinput#pastesplit
     *
     * @param {string} str - The pasted string.
     * @returns {Array<string>}
     */
    function pasteSplit(str) {
        return (
            ADD_KEYS.map(k => k.match)
                .concat(extraAddKeys)

                // Split the string by all our addKeys by joining with a
                // \n, which we will use as the final split operator.
                .reduce((acc, value) => acc.split(value).join("\n"), str)
                .split("\n")
                .filter(k => !!k)
        );
    }

    /**
     * When the list of tokens changes, convert any string tokens
     * to object form and ensure there are no duplicates.
     * @param {Array.<TokenWrapper|String>} allTokens - All tokens
     */
    function __onChange(allTokens) {
        const updatedTokens = allTokens
            .reduce((acc, token) => {
                // A newly typed token will be in string form
                if (typeof token === "string") {
                    token = { name: token };
                }
                if (!acc.find(item => item.name === token.name)) {
                    acc.push(token);
                }
                return acc;
            }, [])
            .filter(token => !!token.name);

        onChange(updatedTokens);
    }

    const addKeys = ADD_KEYS.map(k => k.keyCode).concat(extraAddKeys);
    const isNumberOfTokensMoreThanOrEqualToMaxTags = tokens.length >= maxTags && maxTags !== -1;
    const minWidth = isNumberOfTokensMoreThanOrEqualToMaxTags ? "" : "min-width--150";

    return (
        <div className={classNames("oui-text-input oui-pill-input text--left flush height--auto", className)} {...props}>
            <ReactTagsInput
                addKeys={addKeys}
                addOnBlur={addOnBlur}
                addOnPaste={addOnPaste}
                inputProps={{
                    className: `flex flex--1 ${minWidth} no-border soft-half--ends soft--sides border-radius push-half--left`,
                    onBlur: onInputBlur,
                    onChange: onInputChange,
                    onFocus: onInputFocus,
                    placeholder: isNumberOfTokensMoreThanOrEqualToMaxTags ? "" : placeholder,
                    readOnly: isNumberOfTokensMoreThanOrEqualToMaxTags,
                    disabled: isDisabled,
                }}
                maxTags={maxTags}
                onChange={__onChange}
                onlyUnique={true}
                pasteSplit={pasteSplit}
                renderTag={renderToken}
                renderLayout={renderLayout}
                value={tokens}
            />
        </div>
    );
};

TokensInput.propTypes = {
    /**
     * Adds a new tag on input blur
     */
    addOnBlur: PropTypes.bool,

    /**
     * Adds a new tag on input paste
     */
    addOnPaste: PropTypes.bool,
    /** CSS class names. */
    className: PropTypes.string,
    /**
     * Additional keycodes which should be considered
     * an intent to enter the current string as a new Token.
     * See ADD_KEYS above.
     */
    extraAddKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])),

    /**
     * Whether a search icon should be present
     */
    hasSearchIcon: PropTypes.bool,

    /**
     * Whether the input is disabled or not
     */
    isDisabled: PropTypes.bool,

    /**
     * Maximum number of allowed tokens (pass-through to <ReactTagsInput>)
     */
    maxTags: PropTypes.number,

    /**
     * Handler to invoke when the token list changes.
     */
    onChange: PropTypes.func.isRequired,

    /**
     * Handler to invoke when the token input is blurred
     */
    onInputBlur: PropTypes.func,

    /**
     * Handler to invoke when the token input changes
     */
    onInputChange: PropTypes.func,

    /**
     * Handler to invoke when the token input is focused
     */
    onInputFocus: PropTypes.func,

    /**
     * Placeholder text for the input box.
     */
    placeholder: PropTypes.string,

    /**
     * @type {Array.<TokenWrapper>}
     */
    tokens: PropTypes.arrayOf(PropTypes.object).isRequired,
};

TokensInput.defaultProps = {
    addOnBlur: true,

    addOnPaste: true,

    extraAddKeys: [],

    maxTags: -1,

    onInputBlur: () => null,

    onInputChange: () => null,

    onInputFocus: () => null,

    placeholder: "enter tokens",
};

export default TokensInput;
