"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = exports.InputProvider = void 0;
exports.isHTMLInputElement = isHTMLInputElement;
exports.isHTMLTextAreaElement = isHTMLTextAreaElement;
var _react = _interopRequireDefault(require("react"));
var _core = require("@kui-shell/core");
var _Timer = _interopRequireDefault(require("./Timer"));
var _Actions = _interopRequireDefault(require("./Actions"));
var _Spinner = _interopRequireDefault(require("./Spinner"));
var _OnPaste = _interopRequireDefault(require("./OnPaste"));
var _OnKeyDown = _interopRequireDefault(require("./OnKeyDown"));
var _OnKeyPress = _interopRequireDefault(require("./OnKeyPress"));
var _visible = _interopRequireDefault(require("../visible"));
var _context = _interopRequireDefault(require("../../../Client/context"));
var _ActiveISearch = require("./ActiveISearch");
var _selection = _interopRequireDefault(require("../../../../util/selection"));
var _multilineInput = require("../../util/multiline-input");
var _BlockModel = require("./BlockModel");
var _FancyPipeline = _interopRequireDefault(require("./FancyPipeline"));
var _MutabilityContext = require("../../../Client/MutabilityContext");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/*
 * Copyright 2020 The Kubernetes Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const Tag = _react.default.lazy(() => Promise.resolve().then(() => require('../../../spi/Tag')));
const TextArea = _react.default.lazy(() => Promise.resolve().then(() => require('../../../spi/TextArea')));
const SourceRef = _react.default.lazy(() => Promise.resolve().then(() => require('../SourceRef')));
const Icons = _react.default.lazy(() => Promise.resolve().then(() => require('../../../spi/Icons')));
const strings = (0, _core.i18n)('plugin-client-common');
function isHTMLInputElement(element) {
  const input = element;
  return input && input.tagName === 'INPUT';
}
function isHTMLTextAreaElement(element) {
  const input = element;
  return input && input.tagName === 'TEXTAREA';
}
/**
 * This helps to work around React swallowing change events when
 * setting value via ref; e.g.
 *
 *   element.value='foo'.
 *   element.dispatchEvent(new Event('input', { bubbles: true }))
 *
 */
function setNativeValue(element, value) {
  const {
    set: valueSetter
  } = Object.getOwnPropertyDescriptor(element, 'value') || {};
  const prototype = Object.getPrototypeOf(element);
  const {
    set: prototypeValueSetter
  } = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
  if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
    prototypeValueSetter.call(element, value);
  } else if (valueSetter) {
    valueSetter.call(element, value);
  } else {
    throw new Error('The given element does not have a value setter');
  }
}
class InputProvider extends _react.default.PureComponent {
  constructor() {
    super(...arguments);
    this._cancelReEdit = this.cancelReEdit.bind(this);
  }
  /** rendered to the left of the input element */
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  status() {}
  cancelReEdit() {
    this.setState(() => {
      return {
        isReEdit: false
      };
    });
  }
  contextContent(insideBrackets = this.idx) {
    return this.state.isReEdit ? _react.default.createElement("a", {
      href: "#",
      className: "kui--block-action",
      title: strings('Cancel edit'),
      onClick: this._cancelReEdit
    }, _react.default.createElement(Icons, {
      icon: "Edit",
      className: "clickable"
    })) : _react.default.createElement("span", {
      className: "repl-context-inner"
    }, ' ', "[", insideBrackets, "]"); // this.props.model.cwd
  }
  /** the "xxx" part of "xxx >" of the prompt */
  promptLeft() {
    return !this.props.noPromptContext && _react.default.createElement(_context.default.Consumer, null, config => !config.noPromptContext && this.props.model && _react.default.createElement("span", {
      className: "repl-context",
      onClick: this.props.willFocusBlock,
      "data-input-count": this.props.idx
    }, this.contextContent()));
  }
  /** the ">" part of "xxx >" of the prompt */
  promptRight() {
    // &#x2771; "heavy right-pointing angle bracket ornament"
    // another option: &#x276f; "heavy right-pointing angle quotation mark ornament"
    const active = this.props.model && (0, _BlockModel.isActive)(this.props.model);
    return _react.default.createElement(_context.default.Consumer, null, config => _react.default.createElement("span", {
      className: "repl-prompt-righty"
    }, config.prompt === 'CWD' ? _react.default.createElement("span", {
      className: "clickable",
      onClick: () => this.props.tab.REPL.pexec(`ls ${this.props.model.cwd}`)
    }, active && '[', this.props.model.cwd, active && ']') : config.prompt || '/'));
  }
  isearchPrompt() {
    return _react.default.createElement("div", {
      className: "repl-prompt"
    }, this.state.isearch.render());
  }
  normalPrompt() {
    return _react.default.createElement(_context.default.Consumer, null, config => _react.default.createElement("div", {
      className: "repl-context",
      "data-custom-prompt": !!config.prompt || undefined,
      onClick: this.props.willFocusBlock,
      "data-input-count": this.props.idx
    }, config.prompt ? _react.default.createElement("div", {
      className: "repl-prompt"
    }, this.promptRight()) : this.contextContent()));
  }
  /** the "xxx >" prompt part of the input section */
  prompt() {
    if (this.state && this.state.isearch && this.state.prompt) {
      try {
        return this.isearchPrompt();
      } catch (err) {
        console.error('error rendering i-search', err);
        return this.normalPrompt();
      }
    } else {
      return this.normalPrompt();
    }
  }
  sourceRef() {
    return this.props.model && (0, _BlockModel.isWithCompleteEvent)(this.props.model) && (0, _core.isTable)(this.props.model.response) && (0, _core.hasSourceReferences)(this.props.model.response) && _react.default.createElement(SourceRef, {
      tab: this.props.tab,
      isWidthConstrained: this.props.isWidthConstrained,
      response: this.props.model.response
    });
  }
  get promptValue() {
    return (0, _BlockModel.hasValue)(this.props.model) ? this.props.model.value : (0, _BlockModel.hasCommand)(this.props.model) ? this.props.model.command : '';
  }
  get idx() {
    return this.props.displayedIdx || this.props.idx + 1;
  }
  render() {
    return _react.default.createElement(_react.default.Suspense, {
      fallback: _react.default.createElement("div", null)
    }, _react.default.createElement("div", {
      className: 'repl-input' + (this.state && this.state.isearch ? ' kui--isearch-active' : '')
    }, this.prompt(), _react.default.createElement("div", {
      className: "kui--input-and-context"
    }, this.props.children, this.input(), this.status()), this.state && this.state.tabCompletion && this.state.tabCompletion.render()), this.sourceRef());
  }
}
exports.InputProvider = InputProvider;
class Input extends InputProvider {
  constructor(props) {
    super(props);
    this._onKeyPress = this.onKeyPress.bind(this);
    this._onKeyDown = this.onKeyDown.bind(this);
    this._onKeyUp = this.onKeyUp.bind(this);
    this._onPaste = this.onPaste.bind(this);
    /** This is the onFocus property of the active prompt */
    this._onFocus = evt => {
      this.props.onInputFocus && this.props.onInputFocus(evt);
      this.willFocusBlock(evt);
    };
    /** This is the onBLur property of the active prompt */
    this._onBlur = evt => {
      this.props.onInputBlur && this.props.onInputBlur(evt);
      const valueNotChanged = (0, _BlockModel.hasCommand)(this.props.model) && this.state.prompt && this.props.model.command === this.state.prompt.value;
      this.setState(curState => {
        if (curState.isReEdit && valueNotChanged) {
          return {
            isReEdit: false,
            prompt: undefined
          };
        }
      });
    };
    /** This is the onClick property of the prompt for Active blocks */
    this._onClickActive = evt => {
      this.props.onInputClick && this.props.onInputClick(evt);
      this.willFocusBlock(evt);
    };
    /** This is the onClick property of the prompt for Finished blocks */
    this._onClickFinished = (0, _selection.default)(evt => {
      this.willFocusBlock(evt);
      this.setState(curState => {
        if (!curState.isReEdit) {
          return {
            isReEdit: true,
            multiline: (0, _BlockModel.hasCommand)(this.props.model) && ((0, _multilineInput.includesBachSlash)(this.props.model.command) || (0, _multilineInput.isMultiLineHereDoc)(this.props.model.command))
          };
        }
      });
    });
    /** For Processing blocks, we use an overlay <input/> element. We need to manage focus, since it has pointer-events: none */
    this._restoreFocusToOverlayInput = (0, _selection.default)(evt => {
      ;
      evt.currentTarget.querySelector('.kui--invisible-overlay').focus();
    });
    this.state = {
      model: props.model,
      isReEdit: false,
      execUUID: (0, _BlockModel.hasUUID)(props.model) ? props.model.execUUID : undefined,
      prompt: undefined
    };
  }
  /** @return the current value of the prompt */
  value() {
    return this.state.prompt && this.state.prompt.value;
  }
  /** @return the value to be added to the prompt */
  static valueToBeDisplayed(props) {
    return !(0, _BlockModel.isBeingRerun)(props.model) && (0, _BlockModel.hasValue)(props.model) ? props.model.value : (0, _BlockModel.hasCommand)(props.model) ? props.model.command : '';
  }
  /** Owner wants us to focus on the current prompt */
  doFocus() {
    if (this.props.isFocused && this.state.prompt && document.activeElement !== this.state.prompt) {
      setTimeout(() => this.state.prompt.focus(), 50);
    }
  }
  contextContent() {
    return super.contextContent(this.showSpinnerInContext() && (0, _BlockModel.isProcessingOrBeingRerun)(this.props.model) ? this.spinner() : undefined);
  }
  static getDerivedStateFromProps(props, state) {
    if ((0, _BlockModel.hasUUID)(props.model)) {
      return {
        model: props.model,
        execUUID: props.model.execUUID
      };
    } else if (state.prompt && (0, _BlockModel.isActive)(props.model) && state.execUUID !== undefined && !state.isearch) {
      // e.g. terminal has been cleared; we need to excise the current
      // <input/> because react aggressively caches these
      return {
        model: props.model,
        prompt: undefined,
        execUUID: undefined
      };
    } else if ((0, _BlockModel.isActiveAndDifferent)(props.model, state.model)) {
      return {
        model: props.model,
        prompt: undefined,
        execUUID: undefined
      };
    }
    return state;
  }
  onKeyPress(evt) {
    if (!this.state.isearch) {
      _OnKeyPress.default.bind(this)(evt);
    }
    this.props.onInputKeyPress && this.props.onInputKeyPress(evt);
  }
  onKeyDown(evt) {
    if (!this.state.isearch) {
      _OnKeyDown.default.bind(this)(evt);
    }
    this.props.onInputKeyDown && this.props.onInputKeyDown(evt);
  }
  onKeyUp(evt) {
    _ActiveISearch.onKeyUp.bind(this)(evt);
    this.props.onInputKeyUp && this.props.onInputKeyUp(evt);
  }
  onPaste(evt) {
    if (!this.state.isearch) {
      _OnPaste.default.bind(this)(evt.nativeEvent, this.props.tab, this.state.prompt);
    }
  }
  onTextAreaRef(c) {
    if (c && (!this.state.prompt || isHTMLInputElement(this.state.prompt) || this.state.isReEdit)) {
      let value = this.promptValue;
      if (isHTMLInputElement(this.state.prompt)) {
        if ((0, _multilineInput.endsWithBackSlash)(this.state.prompt.value)) {
          value += `${this.state.prompt.value}\n`;
        } else if (this.state.pasteMultiLineTexts) {
          value += `${this.state.prompt.value}${this.state.pasteMultiLineTexts}`;
        } else {
          value += this.state.prompt.value;
        }
        setNativeValue(c, value); // see the comment on the function; React swallows the event otherwise
        c.dispatchEvent(new Event('input', {
          bubbles: true
        })); // to get TextArea to adjust its height
      }

      this.setState({
        prompt: c
      });
      if (this.props.isFocused && document.activeElement !== c) {
        c.focus();
      }
    } else if (c && this.props.isFocused && (0, _visible.default)(c)) {
      c.focus();
    }
  }
  onRef(c) {
    if (c && (!this.state.prompt || this.state.isReEdit)) {
      c.value = (0, _BlockModel.hasValue)(this.props.model) ? this.props.model.value : (0, _BlockModel.hasCommand)(this.props.model) ? this.props.model.command : '';
      this.setState({
        prompt: c
      });
      if (this.props.isFocused && document.activeElement !== c) {
        c.focus();
      }
    } else if (c && this.props.isFocused && (0, _visible.default)(c)) {
      c.focus();
    }
  }
  willFocusBlock(evt) {
    if (this.props.willFocusBlock) {
      this.props.willFocusBlock(evt);
    }
  }
  /** This is the input overlay for Processing blocks */
  inputOverlayForProcessingBlocks(value) {
    return _react.default.createElement("input", {
      className: "kui--invisible-overlay",
      readOnly: true,
      onKeyDown: evt => {
        if (evt.key === 'c' && evt.ctrlKey) {
          (0, _core.doCancel)(this.props.tab, this.props._block, value);
        }
      },
      ref: c => c && c.focus()
    });
  }
  /** if in multi-line mode, render text area */
  multilineInput() {
    return _react.default.createElement("div", {
      className: "repl-input-element-wrapper flex-layout flex-fill",
      "data-is-reedit": this.state.isReEdit || undefined
    }, _react.default.createElement(TextArea, {
      autoResize: true,
      autoFocus: this.props.isFocused && (0, _visible.default)(this.props._block),
      autoCorrect: "off",
      autoComplete: "off",
      spellCheck: "false",
      autoCapitalize: "off",
      className: 'repl-input-element' + (this.state.isearch ? ' repl-input-hidden' : '') + ' repl-textarea',
      "aria-label": "Command Input",
      tabIndex: 1,
      "data-scrollback-uuid": this.props.uuid,
      "data-input-count": this.props.idx,
      onBlur: this._onBlur,
      onFocus: this._onFocus,
      onKeyPress: this._onKeyPress,
      onKeyDown: this._onKeyDown,
      innerRef: this.onTextAreaRef.bind(this) /* DO NOT MEMOIZE THIS; see https://github.com/kubernetes-sigs/kui/issues/7648 */
    }), this.state.typeahead && _react.default.createElement("span", {
      className: "kui--input-typeahead"
    }, this.state.typeahead));
  }
  /** if not in multi-line mode, render an inline input */
  inlineInput() {
    return _react.default.createElement("div", {
      className: "repl-input-element-wrapper flex-layout flex-fill",
      "data-is-reedit": this.state.isReEdit || undefined,
      key: this.state.execUUID
    }, _react.default.createElement("input", {
      type: "text",
      autoFocus: this.props.isFocused && (0, _visible.default)(this.props._block),
      autoCorrect: "off",
      autoComplete: "off",
      spellCheck: "false",
      autoCapitalize: "off",
      className: 'repl-input-element' + (this.state.isearch ? ' repl-input-hidden' : ''),
      "aria-label": "Command Input",
      tabIndex: 1,
      "data-scrollback-uuid": this.props.uuid,
      "data-input-count": this.props.idx,
      onBlur: this._onBlur,
      onFocus: this._onFocus,
      onMouseDown: this.props.onInputMouseDown,
      onMouseMove: this.props.onInputMouseMove,
      onChange: this.props.onInputChange,
      onClick: this._onClickActive,
      onKeyPress: this._onKeyPress,
      onKeyDown: this._onKeyDown,
      onKeyUp: this._onKeyUp,
      onPaste: this._onPaste,
      ref: this.onRef.bind(this) /* DO NOT MEMOIZE THIS; see https://github.com/kubernetes-sigs/kui/issues/7648 */
    }), this.state.typeahead && _react.default.createElement("span", {
      className: "kui--input-typeahead"
    }, this.state.typeahead));
  }
  /** @return the UI for the input part of Finished blocks (i.e. those not processing or active) */
  inputForFinishedBlock() {
    const value = Input.valueToBeDisplayed(this.props);
    const isInProgress = (0, _BlockModel.isProcessingOrBeingRerun)(this.props.model);
    // for Processing or Done blocks, render the value as a plain div
    // for Processing, though, we will need an inputOverlay... to capture ctrl+c
    return _react.default.createElement(_MutabilityContext.MutabilityContext.Consumer, null, mutability => _react.default.createElement("div", {
      "data-input-count": this.props.idx,
      className: 'repl-input-element-wrapper flex-layout flex-fill',
      onClick: isInProgress ? this._restoreFocusToOverlayInput : mutability.editable ? this._onClickFinished : undefined
    }, this.fancyValue(value), value.length === 0 && _react.default.createElement("span", {
      className: "kui--repl-input-element-nbsp"
    }, "\u00A0"), isInProgress && this.inputOverlayForProcessingBlocks(value), this.inputStatus(value)));
  }
  /** the element that represents the command being/having been/going to be executed */
  input() {
    const active = (0, _BlockModel.isActive)(this.props.model) || this.state.isReEdit;
    if (active) {
      if (this.state.prompt) {
        if (this.props.isFocused) {
          if (document.activeElement !== this.state.prompt) {
            setTimeout(() => {
              if ((0, _visible.default)(this.state.prompt) && this.props.isFocused) {
                this.state.prompt.focus();
              }
            });
          }
        } else {
          // @starpit 20210128; i think this is leftover from earlier buggier days
          // keeping around, commented out, for a bit just in case
          // setTimeout(() => this.state.prompt.scrollIntoView(), 10)
        }
      }
      return this.state.multiline ? this.multilineInput() : this.inlineInput();
    } else {
      return this.inputForFinishedBlock();
    }
  }
  /**
   * Turn the value part of the input into a fancier form,
   * e.g. rendering pipelines or command names in a better way.
   *
   */
  fancyValue(value) {
    if (!(0, _BlockModel.isBeingRerun)(this.props.model) && (0, _BlockModel.isWithCompleteEvent)(this.props.model) && this.props.model.completeEvent.pipeStages !== undefined) {
      return _react.default.createElement(_FancyPipeline.default, Object.assign({
        REPL: this.props.tab.REPL
      }, this.props.model.completeEvent.pipeStages));
    } else if ((0, _BlockModel.hasStartEvent)(this.props.model) && this.props.model.startEvent.pipeStages !== undefined) {
      return _react.default.createElement(_FancyPipeline.default, Object.assign({
        REPL: this.props.tab.REPL
      }, this.props.model.startEvent.pipeStages));
    } else {
      return _react.default.createElement("span", {
        className: "repl-input-element flex-fill"
      }, value);
    }
  }
  componentDidCatch(error, errorInfo) {
    console.error(error, errorInfo);
  }
  /** render a tag for experimental command */
  experimentalTag() {
    if (this.props.isExperimental) {
      return _react.default.createElement("span", {
        className: "kui--repl-block-right-element left-pad kui--repl-block-experimental-tag-wrapper",
        key: "experimental-tag"
      }, _react.default.createElement(Tag, {
        spanclassname: "kui--repl-block-experimental-tag",
        title: strings('HoverExperimentalTag'),
        type: "warning"
      }, strings('ExperimentalTag')));
    }
  }
  /** render the time the block started processing */
  timestamp() {
    if (!(0, _BlockModel.isEmpty)(this.props.model) && ((0, _BlockModel.isProcessingOrBeingRerun)(this.props.model) || (0, _BlockModel.isFinished)(this.props.model))) {
      const replayedAndNotRerun = (0, _BlockModel.isReplay)(this.props.model) && !(0, _BlockModel.hasBeenRerun)(this.props.model) && !(0, _BlockModel.isBeingRerun)(this.props.model);
      const completed = this.props.model.startTime && (0, _BlockModel.isWithCompleteEvent)(this.props.model) && !(0, _BlockModel.isBeingRerun)(this.props.model);
      const showingDate = !replayedAndNotRerun && completed && !this.props.isWidthConstrained;
      const status = (0, _BlockModel.isProcessingOrBeingRerun)(this.props.model) ? 'processing' : (0, _BlockModel.isOk)(this.props.model) ? 'done' : (0, _BlockModel.isOops)(this.props.model) ? 'error' : 'not-yet';
      const {
        startTime
      } = this.props.model;
      const endTime = (0, _BlockModel.isWithCompleteEvent)(this.props.model) && !(0, _BlockModel.isBeingRerun)(this.props.model) && this.props.model.completeEvent.completeTime;
      return _react.default.createElement(_Timer.default, {
        key: "timer",
        innerClassName: "small-left-pad sub-text",
        className: "kui--repl-block-timestamp kui--repl-block-right-element",
        status: status,
        endTime: endTime,
        startTime: startTime,
        showDate: showingDate
      });
    }
  }
  /** spinner for processing blocks */
  spinner() {
    return _react.default.createElement(_Spinner.default, {
      key: "spinner"
    });
  }
  /** error icon for error blocks */
  /* private errorIcon() {
    if (isOops(this.props.model)) {
      return <Icons className="kui--repl-block-error-icon" icon="Error" data-mode="error" />
    }
  } */
  /** DropDown menu for completed blocks */
  actions(command) {
    if (((0, _BlockModel.isFinished)(this.props.model) || (0, _BlockModel.isProcessingOrBeingRerun)(this.props.model)) && !!this.props.tab && !!this.props.model) {
      return _react.default.createElement(_Actions.default, Object.assign({
        command: command,
        idx: this.props.idx
      }, this.props));
    }
  }
  /**
   * Status elements associated with the block as a whole; even though
   * these also pertain to the Output part of a Block, these are
   * currently housed in this Input component.
   *
   */
  status() {
    return _react.default.createElement("span", {
      className: "repl-prompt-right-elements"
    });
  }
  /** Should we show the spinner in the In[...] context area, or in the [<input/> ...] area? */
  showSpinnerInContext() {
    return !this.props.isWidthConstrained;
  }
  /** Experimental tag, timestamp, etc. */
  rightElements() {
    const children = [this.experimentalTag(), !this.showSpinnerInContext() && (0, _BlockModel.isProcessingOrBeingRerun)(this.props.model) && this.spinner(), this.timestamp()].filter(_ => _);
    if (children.length > 0) {
      return _react.default.createElement("span", {
        className: "repl-prompt-right-elements"
      }, children);
    }
  }
  /** Status elements placed in with <input> part of the block */
  inputStatus(input) {
    return _react.default.createElement(_react.default.Fragment, null, this.rightElements(), this.actions(input));
  }
}
exports.default = Input;