import { __assign, __extends } from "tslib";
import * as React from 'react';
import { classNamesFunction, initializeComponentRef, warnDeprecations, warn } from '../../Utilities';
import { TextField } from '../../TextField';
import { ColorRectangle } from './ColorRectangle/ColorRectangle';
import { ColorSlider } from './ColorSlider/ColorSlider';
import { MAX_COLOR_ALPHA, MAX_COLOR_RGB, MAX_HEX_LENGTH, MAX_RGBA_LENGTH, MIN_HEX_LENGTH, MIN_RGBA_LENGTH, HEX_REGEX, RGBA_REGEX, } from '../../utilities/color/consts';
import { getColorFromString } from '../../utilities/color/getColorFromString';
import { getColorFromRGBA } from '../../utilities/color/getColorFromRGBA';
import { clamp } from '../../utilities/color/clamp';
import { updateA } from '../../utilities/color/updateA';
import { updateT } from '../../utilities/color/updateT';
import { updateH } from '../../utilities/color/updateH';
import { correctRGB } from '../../utilities/color/correctRGB';
import { correctHex } from '../../utilities/color/correctHex';
import { ColorRectangleBase } from './ColorRectangle/ColorRectangle.base';
var getClassNames = classNamesFunction();
var allColorComponents = ['hex', 'r', 'g', 'b', 'a', 't'];
/**
 * {@docCategory ColorPicker}
 */
var ColorPickerBase = /** @class */ (function (_super) {
    __extends(ColorPickerBase, _super);
    function ColorPickerBase(props) {
        var _this = _super.call(this, props) || this;
        _this._onSVChanged = function (ev, color) {
            _this._updateColor(ev, color);
        };
        _this._onHChanged = function (ev, h) {
            _this._updateColor(ev, updateH(_this.state.color, h));
        };
        /** Callback for when the alpha/transparency slider changes */
        _this._onATChanged = function (ev, value) {
            var updater = _this.props.alphaType === 'transparency' ? updateT : updateA;
            _this._updateColor(ev, updater(_this.state.color, Math.round(value)));
        };
        _this._onBlur = function (event) {
            var _a;
            var _b = _this.state, color = _b.color, editingColor = _b.editingColor;
            if (!editingColor) {
                return;
            }
            // If there was an intermediate incorrect value (such as too large or empty), correct it.
            var value = editingColor.value, component = editingColor.component;
            var isHex = component === 'hex';
            var isAlpha = component === 'a';
            var isTransparency = component === 't';
            var minLength = isHex ? MIN_HEX_LENGTH : MIN_RGBA_LENGTH;
            if (value.length >= minLength && (isHex || !isNaN(Number(value)))) {
                // Real value. Clamp to appropriate length (hex) or range (rgba).
                var newColor = void 0;
                if (isHex) {
                    newColor = getColorFromString('#' + correctHex(value));
                }
                else if (isAlpha || isTransparency) {
                    var updater = isAlpha ? updateA : updateT;
                    newColor = updater(color, clamp(Number(value), MAX_COLOR_ALPHA));
                }
                else {
                    newColor = getColorFromRGBA(correctRGB(__assign(__assign({}, color), (_a = {}, _a[component] = Number(value), _a))));
                }
                // Update state and call onChange
                _this._updateColor(event, newColor);
            }
            else {
                // Intermediate value was an empty string or too short (hex only).
                // Just clear the intermediate state and revert to the previous value.
                _this.setState({ editingColor: undefined });
            }
        };
        initializeComponentRef(_this);
        var strings = props.strings; // always defined since it's in defaultProps
        warnDeprecations('ColorPicker', props, {
            hexLabel: 'strings.hex',
            redLabel: 'strings.red',
            greenLabel: 'strings.green',
            blueLabel: 'strings.blue',
            alphaLabel: 'strings.alpha',
            alphaSliderHidden: 'alphaType',
        });
        // eslint-disable-next-line deprecation/deprecation
        if (strings.hue) {
            // warnDeprecations can't handle nested deprecated props
            warn("ColorPicker property 'strings.hue' was used but has been deprecated. Use 'strings.hueAriaLabel' instead.");
        }
        _this.state = {
            color: _getColorFromProps(props) || getColorFromString('#ffffff'),
        };
        _this._textChangeHandlers = {};
        for (var _i = 0, allColorComponents_1 = allColorComponents; _i < allColorComponents_1.length; _i++) {
            var component = allColorComponents_1[_i];
            _this._textChangeHandlers[component] = _this._onTextChange.bind(_this, component);
        }
        var defaultStrings = ColorPickerBase.defaultProps.strings;
        _this._textLabels = {
            /* eslint-disable deprecation/deprecation */
            r: props.redLabel || strings.red || defaultStrings.red,
            g: props.greenLabel || strings.green || defaultStrings.green,
            b: props.blueLabel || strings.blue || defaultStrings.blue,
            a: props.alphaLabel || strings.alpha || defaultStrings.alpha,
            hex: props.hexLabel || strings.hex || defaultStrings.hex,
            t: strings.transparency || defaultStrings.transparency,
        };
        _this._strings = __assign(__assign(__assign({}, defaultStrings), { 
            // these aria labels default to the visible labels
            alphaAriaLabel: _this._textLabels.a, transparencyAriaLabel: _this._textLabels.t }), strings);
        return _this;
    }
    Object.defineProperty(ColorPickerBase.prototype, "color", {
        get: function () {
            return this.state.color;
        },
        enumerable: true,
        configurable: true
    });
    ColorPickerBase.prototype.componentDidUpdate = function (prevProps, prevState) {
        // if props changed (as opposed to a state update), update the color
        if (prevProps !== this.props) {
            var color = _getColorFromProps(this.props);
            if (color) {
                this._updateColor(undefined, color);
            }
        }
    };
    ColorPickerBase.prototype.render = function () {
        var _this = this;
        var props = this.props;
        var strings = this._strings;
        var textLabels = this._textLabels;
        var theme = props.theme, className = props.className, styles = props.styles, alphaType = props.alphaType, 
        // eslint-disable-next-line deprecation/deprecation
        _a = props.alphaSliderHidden, 
        // eslint-disable-next-line deprecation/deprecation
        alphaSliderHidden = _a === void 0 ? alphaType === 'none' : _a;
        var color = this.state.color;
        var useTransparency = alphaType === 'transparency';
        var colorComponents = ['hex', 'r', 'g', 'b', useTransparency ? 't' : 'a'];
        var atValue = useTransparency ? color.t : color.a;
        var atLabel = useTransparency ? textLabels.t : textLabels.a;
        var classNames = getClassNames(styles, {
            theme: theme,
            className: className,
            alphaType: alphaType,
        });
        var selectedColorAriaParts = [textLabels.r, color.r, textLabels.g, color.g, textLabels.b, color.b];
        if (!alphaSliderHidden && typeof atValue === 'number') {
            selectedColorAriaParts.push(atLabel, atValue + "%");
        }
        var ariaLabel = strings.rootAriaLabelFormat.replace('{0}', selectedColorAriaParts.join(' '));
        return (React.createElement("div", { className: classNames.root, role: "group", "aria-label": ariaLabel },
            React.createElement("div", { className: classNames.panel },
                React.createElement(ColorRectangle, { color: color, onChange: this._onSVChanged, ariaLabel: strings.svAriaLabel, ariaDescription: strings.svAriaDescription, ariaValueFormat: strings.svAriaValueFormat, className: classNames.colorRectangle }),
                React.createElement("div", { className: classNames.flexContainer },
                    React.createElement("div", { className: classNames.flexSlider },
                        React.createElement(ColorSlider, { className: "is-hue", type: "hue", 
                            // eslint-disable-next-line deprecation/deprecation
                            ariaLabel: strings.hue || strings.hueAriaLabel, value: color.h, onChange: this._onHChanged }),
                        !alphaSliderHidden && (React.createElement(ColorSlider, { className: "is-alpha", type: alphaType, ariaLabel: useTransparency ? strings.transparencyAriaLabel : strings.alphaAriaLabel, overlayColor: color.hex, value: atValue, onChange: this._onATChanged }))),
                    props.showPreview && (React.createElement("div", { className: classNames.flexPreviewBox },
                        React.createElement("div", { className: classNames.colorSquare + ' is-preview', style: {
                                backgroundColor: color.str,
                            } })))),
                React.createElement("table", { className: classNames.table, role: "group", cellPadding: "0", cellSpacing: "0" },
                    React.createElement("thead", null,
                        React.createElement("tr", { className: classNames.tableHeader },
                            React.createElement("td", { className: classNames.tableHexCell }, textLabels.hex),
                            React.createElement("td", null, textLabels.r),
                            React.createElement("td", null, textLabels.g),
                            React.createElement("td", null, textLabels.b),
                            !alphaSliderHidden && React.createElement("td", { className: classNames.tableAlphaCell }, atLabel))),
                    React.createElement("tbody", null,
                        React.createElement("tr", null, colorComponents.map(function (comp) {
                            if ((comp === 'a' || comp === 't') && alphaSliderHidden) {
                                return null;
                            }
                            return (React.createElement("td", { key: comp },
                                React.createElement(TextField, { className: classNames.input, onChange: _this._textChangeHandlers[comp], onBlur: _this._onBlur, value: _this._getDisplayValue(comp), spellCheck: false, ariaLabel: textLabels[comp], "aria-live": comp !== 'hex' ? 'assertive' : undefined, autoComplete: "off" })));
                        })))))));
    };
    ColorPickerBase.prototype._getDisplayValue = function (component) {
        var _a = this.state, color = _a.color, editingColor = _a.editingColor;
        if (editingColor && editingColor.component === component) {
            return editingColor.value;
        }
        if (component === 'hex') {
            return color[component] || '';
        }
        else if (typeof color[component] === 'number' && !isNaN(color[component])) {
            return String(color[component]);
        }
        return '';
    };
    ColorPickerBase.prototype._onTextChange = function (component, event, newValue) {
        var _a;
        var color = this.state.color;
        var isHex = component === 'hex';
        var isAlpha = component === 'a';
        var isTransparency = component === 't';
        newValue = (newValue || '').substr(0, isHex ? MAX_HEX_LENGTH : MAX_RGBA_LENGTH);
        // Ignore what the user typed if it contains invalid characters
        var validCharsRegex = isHex ? HEX_REGEX : RGBA_REGEX;
        if (!validCharsRegex.test(newValue)) {
            return;
        }
        // Determine if the entry is valid (different methods for hex, alpha, and RGB)
        var isValid;
        if (newValue === '') {
            // Empty string is obviously not valid
            isValid = false;
        }
        else if (isHex) {
            // Technically hex values of length 3 are also valid, but committing the value here would
            // cause it to be automatically converted to a value of length 6, which may not be what the
            // user wanted if they're not finished typing. (Values of length 3 will be committed on blur.)
            isValid = newValue.length === MAX_HEX_LENGTH;
        }
        else if (isAlpha || isTransparency) {
            isValid = Number(newValue) <= MAX_COLOR_ALPHA;
        }
        else {
            isValid = Number(newValue) <= MAX_COLOR_RGB;
        }
        if (!isValid) {
            // If the new value is an empty string or other invalid value, save that to display.
            // (if the user still hasn't entered anything on blur, the last value is restored)
            this.setState({ editingColor: { component: component, value: newValue } });
        }
        else if (String(color[component]) === newValue) {
            // If the new value is the same as the current value, mostly ignore it.
            // Exception is that if the user was previously editing the value (but hadn't yet entered
            // a new valid value), we should clear the intermediate value.
            if (this.state.editingColor) {
                this.setState({ editingColor: undefined });
            }
        }
        else {
            // Should be a valid color. Update the value.
            var newColor = isHex
                ? getColorFromString('#' + newValue)
                : isTransparency
                    ? updateT(color, Number(newValue))
                    : getColorFromRGBA(__assign(__assign({}, color), (_a = {}, _a[component] = Number(newValue), _a)));
            this._updateColor(event, newColor);
        }
    };
    /**
     * Update the displayed color and call change handlers if appropriate.
     * @param ev - Event if call was triggered by an event (undefined if triggered by props change)
     * @param newColor - Updated color
     */
    ColorPickerBase.prototype._updateColor = function (ev, newColor) {
        if (!newColor) {
            return;
        }
        var _a = this.state, color = _a.color, editingColor = _a.editingColor;
        // For black or white, the hue can change without changing the string.
        var isDifferentColor = newColor.h !== color.h || newColor.str !== color.str;
        if (isDifferentColor || editingColor) {
            // If ev is undefined, it's an update from props (which should be unconditionally respected
            // and not call onChange).
            if (ev && this.props.onChange) {
                this.props.onChange(ev, newColor);
                if (ev.defaultPrevented) {
                    return;
                }
            }
            this.setState({ color: newColor, editingColor: undefined });
        }
    };
    ColorPickerBase.defaultProps = {
        alphaType: 'alpha',
        strings: {
            rootAriaLabelFormat: 'Color picker, {0} selected.',
            hex: 'Hex',
            red: 'Red',
            green: 'Green',
            blue: 'Blue',
            alpha: 'Alpha',
            transparency: 'Transparency',
            hueAriaLabel: 'Hue',
            svAriaLabel: ColorRectangleBase.defaultProps.ariaLabel,
            svAriaValueFormat: ColorRectangleBase.defaultProps.ariaValueFormat,
            svAriaDescription: ColorRectangleBase.defaultProps.ariaDescription,
        },
    };
    return ColorPickerBase;
}(React.Component));
export { ColorPickerBase };
function _getColorFromProps(props) {
    var color = props.color;
    return typeof color === 'string' ? getColorFromString(color) : color;
}
//# sourceMappingURL=ColorPicker.base.js.map