"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _debug = _interopRequireDefault(require("debug"));
var _react = _interopRequireDefault(require("react"));
var _uuid = require("uuid");
var _core = require("@kui-shell/core");
var _StreamingConsumer = _interopRequireDefault(require("./StreamingConsumer"));
var _Timer = _interopRequireDefault(require("./Timer"));
var _Spinner = _interopRequireDefault(require("./Spinner"));
var _Scalar = _interopRequireDefault(require("../../../Content/Scalar"));
var _MutabilityContext = require("../../../Client/MutabilityContext");
var _Tooltip = _interopRequireDefault(require("../../../spi/Tooltip"));
var _TwoFaceIcon = _interopRequireDefault(require("../../../spi/Icons/TwoFaceIcon"));
var _CodeBlockEvents = require("./CodeBlockEvents");
var _LinkStatus = require("../../../Content/LinkStatus");
var _ProgressStepper = require("../../../Content/ProgressStepper");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/*
 * Copyright 2021 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.
 */
var __awaiter = void 0 && (void 0).__awaiter || function (thisArg, _arguments, P, generator) {
  function adopt(value) {
    return value instanceof P ? value : new P(function (resolve) {
      resolve(value);
    });
  }
  return new (P || (P = Promise))(function (resolve, reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    }
    function rejected(value) {
      try {
        step(generator["throw"](value));
      } catch (e) {
        reject(e);
      }
    }
    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
    }
    step((generator = generator.apply(thisArg, _arguments || [])).next());
  });
};
const BadgeUI = _react.default.lazy(() => Promise.resolve().then(() => require('../../../spi/Tag')));
const SourceRef = _react.default.lazy(() => Promise.resolve().then(() => require('../SourceRef')));
const CodeSnippet = _react.default.lazy(() => Promise.resolve().then(() => require('../../../spi/CodeSnippet')));
const ExpandableSection = _react.default.lazy(() => Promise.resolve().then(() => require('../../../spi/ExpandableSection')));
const debug = (0, _debug.default)('plugin-client-common/components/Views/Terminal/Block/CodeBlock');
const strings = (0, _core.i18n)('plugin-client-common');
/**
 * Thin wrapper over the Badge component that places the badge in the
 * top left of the surrounding CodeBlock component.
 */
function Badge(props) {
  return _react.default.createElement("span", {
    className: "kui--code-block-actions",
    "data-align": props.position
  }, _react.default.createElement(BadgeUI, null, props.children));
}
class CodeBlock extends _StreamingConsumer.default {
  constructor(props) {
    super(props);
    this.mounted = false;
    this.cleaners = [];
    /**
     * Re-execute command upon receipt of an event on the specified
     * `this.props.watch` channel.
     *
     */
    this.onWatchEvent = () => this._onRun();
    /** Is this code block ready for execution? E.g. perhaps the prereqs have yet to be satisfied. */
    this._updateReadiness = ready => {
      if (this.mounted) {
        this.setState({
          ready
        });
      }
    };
    /** Updates coming from the CodeSnippet component */
    this.onContentChange = codeSnippetValue => {
      if (this.mounted) {
        this.setState({
          codeSnippetValue
        });
      }
    };
    /**
     * We are in offline mode, we have sample output, and the user
     * clicked the Run button. We only need to shift to showing the
     * original status of the code block execution.
     */
    this._onRunUsingSampleOutput = () => {
      if (this.props.response && this.mounted) {
        this.setState({
          execution: this.props.rawStatus
        });
      }
    };
    this._onRun = () => {
      this.onRun(this.state.value);
    };
    this._onCleanup = () => __awaiter(this, void 0, void 0, function* () {
      if (this.mounted) {
        yield this.onRun(this.props.cleanup);
        this.setState({
          validated: false,
          execution: 'not-yet'
        });
      }
    });
    this.state = CodeBlock.getDerivedStateFromProps(props);
  }
  componentDidMount() {
    this.mounted = true;
    this.initLinkEvents();
    this.initWatchEvents();
    this.initCodeBlockEvents();
    this.doValidate();
    if (this.props.executeImmediately && !this.props.response) {
      setTimeout(this._onRun);
    }
  }
  componentWillUnmount() {
    this.mounted = false;
    this.cleaners.forEach(_ => _());
  }
  componentDidUpdate(prevProps) {
    if (prevProps.validate !== this.props.validate) {
      this.doValidate();
    }
  }
  doValidate() {
    if (this.props.blockId && this.props.validate) {
      setTimeout(() => __awaiter(this, void 0, void 0, function* () {
        try {
          // .toString() in case of e.g. `validate: true` which yaml
          // parsers pass to us as a boolean
          this.emitLinkStatus('processing');
          yield (0, _core.pexecInCurrentTab)(this.props.validate.toString(), undefined, true, true);
          this.emitLinkStatus('done');
          if (this.mounted) {
            this.setState({
              validated: true
            });
          }
        } catch (err) {
          this.emitLinkStatus('not-yet');
          if (this.mounted) {
            this.setState({
              validated: false
            });
          }
        }
      }), 1000);
    }
  }
  /** Is this code block ready for execution? E.g. perhaps the prereqs have yet to be satisfied. */
  initCodeBlockEvents() {
    (0, _CodeBlockEvents.onCodeBlockReadiness)(this.props.blockId, this._updateReadiness);
    this.cleaners.push(() => (0, _CodeBlockEvents.offCodeBlockReadiness)(this.props.blockId, this._updateReadiness));
    (0, _CodeBlockEvents.emitGetCodeBlockReadiness)(this.props.blockId, this._updateReadiness);
  }
  initWatchEvents() {
    if (this.props.watch) {
      const channels = this.props.watch.split(/\s*,\s*/);
      debug(`initializing code block watch events on ${channels.length} channel${channels.length === 1 ? '' : 's'}: ${channels}`);
      channels.forEach(channel => {
        _core.Events.eventChannelUnsafe.on(channel, this.onWatchEvent);
        this.cleaners.push(() => _core.Events.eventChannelUnsafe.off(channel, this.onWatchEvent));
      });
    }
  }
  emitLinkStatus(execution = this.state.execution) {
    _core.Events.eventChannelUnsafe.emit((0, _LinkStatus.linkUpdateChannel)(this.props.blockId), execution === 'not-yet' || execution === 'replayed' ? [0, 0, 0] // indicates nothing has happened yet, in this session
    : execution === 'processing' ? [0, 0, 1] : execution === 'error' ? [0, 1, 0] : [1, 0, 0], this.props.blockId);
  }
  initLinkEvents() {
    if (this.props.blockId) {
      const get = (0, _LinkStatus.linkGetChannel)(this.props.blockId);
      const emit = this.emitLinkStatus.bind(this);
      _core.Events.eventChannelUnsafe.on(get, emit);
      this.cleaners.push(() => _core.Events.eventChannelUnsafe.off(get, emit));
    }
  }
  static getDerivedStateFromProps(props, state) {
    const usePropsValue = !state || !state.codeSnippetValue;
    if (!usePropsValue && state && state.codeSnippetValue) {
      return Object.assign(state, {
        value: state.codeSnippetValue
      });
    } else if (!state || state.value !== props.value || state.language !== props.language) {
      const execUUID = (0, _uuid.v4)();
      return Object.assign({
        ready: true,
        execution: props.status || 'not-yet',
        value: usePropsValue ? props.value : state.codeSnippetValue || state.value,
        language: props.language,
        validated: false
      }, _StreamingConsumer.default.initStreamingState(execUUID));
    } else {
      return state;
    }
  }
  /** Are we in the middle of an execution of this code block? */
  get isExecutionInProgress() {
    return this.state.execution === 'processing' && this.state.startTime && !this.state.endTime;
  }
  get isExecutedSuccessfullyOrValidated() {
    return this.state.validated || this.state.execution === 'done';
  }
  /** UI to denote duration of the current Run */
  timer() {
    return _react.default.createElement(Badge, {
      position: "top-right"
    }, _react.default.createElement(_Timer.default, {
      startTime: this.state.startTime,
      endTime: this.state.endTime,
      status: this.state.execution
    }));
  }
  /** UI to denote the execution status of this code block */
  status() {
    const execution = this.state.validated ? 'done' : this.state.execution;
    if (execution === 'done' || execution === 'error') {
      return _react.default.createElement("div", {
        className: "kui--code-block-actions flex-layout",
        "data-align": "top-left"
      }, _react.default.createElement(_ProgressStepper.ProgressStepper, {
        className: "kui--code-block-status"
      }, _react.default.createElement(_ProgressStepper.ProgressStep, {
        title: "",
        defaultStatus: execution === 'done' ? 'success' : 'error'
      })));
    } else if (this.props.optional) {
      return _react.default.createElement(Badge, {
        position: "top-left"
      }, strings('Optional'));
    }
  }
  actions(showAsExecutable, butUseSampleOutputOnRun) {
    if (this.props.readonly !== true && showAsExecutable) {
      if (this.state.execution === 'processing') {
        return _react.default.createElement(_Spinner.default, null);
      } else {
        return _react.default.createElement(_react.default.Fragment, null, this.props.cleanup && this.isExecutedSuccessfullyOrValidated && _react.default.createElement(Cleanup, {
          onCleanup: this._onCleanup
        }), _react.default.createElement(Run, {
          ready: this.state.ready,
          optional: this.props.optional,
          execution: this.state.execution,
          onRun: butUseSampleOutputOnRun ? this._onRunUsingSampleOutput : this._onRun
        }));
      }
    }
  }
  input(showAsExecutable, butUseSampleOutputOnRun) {
    return _react.default.createElement("div", {
      className: "repl-input-element-wrapper flex-layout flex-fill kui--inverted-color-context kui--relative-positioning"
    }, _react.default.createElement("div", {
      className: "flex-fill"
    }, _react.default.createElement(CodeSnippet, {
      wordWrap: "on",
      value: this.state.value,
      language: this.state.language,
      onContentChange: this.onContentChange,
      readonly: this.props.readonly !== false,
      tabUUID: this.props.tab ? this.props.tab.uuid : undefined
    })), this.timer(), this.status(), this.actions(showAsExecutable, butUseSampleOutputOnRun));
  }
  sourceRef() {
    return this.props.response && (0, _core.isTable)(this.props.response) && (0, _core.hasSourceReferences)(this.props.response) && _react.default.createElement(SourceRef, {
      tab: this.props.tab,
      response: this.props.response
    });
  }
  /**
   * If we have a command line e.g. `echo hi; echo ho`, then it would
   * be nice to smash these into a single group, so that they are
   * rendered as if they were executed together. See "semicolons
   * between commands and escape newlines" below for why this isn't
   * the case without some fixups here.
   *
   */
  combineContiguousXtermResponses() {
    if (!(0, _core.isMixedResponse)(this.props.response)) {
      return this.props.response;
    } else {
      return this.props.response.reduce((A, res) => {
        if (A.length === 0) {
          A.push(res);
        } else {
          const prev = A[A.length - 1];
          if ((0, _core.isXtermResponse)(prev) && (0, _core.isXtermResponse)(res)) {
            // then we have two consecutive XtermResponses; smash them
            // together
            prev.rows = prev.rows.concat(res.rows);
            if (prev.code === 0) {
              // conservatively take non-zero exit codes over zero
              // exit codes
              prev.code = res.code;
            }
          } else {
            A.push(res);
          }
        }
        return A;
      }, []);
    }
  }
  nonstreamingOutput() {
    const response = this.combineContiguousXtermResponses();
    return this.state.execution !== 'processing' && this.props.response && _react.default.createElement(_Scalar.default, {
      tab: this.props.tab,
      execUUID: this.state.execUUID,
      response: response,
      willChangeSize: this.nope
    });
  }
  get hasOutput() {
    return this.props.response || this.hasStreamingOutput();
  }
  output(mutability) {
    const content = this.hasOutput && _react.default.createElement(_MutabilityContext.MutabilityContext.Provider, {
      value: this.showingReplayedOutput ? _MutabilityContext.MutabilityStateOffline : mutability
    }, _react.default.createElement("div", {
      className: "repl-output repl-result-has-content"
    }, _react.default.createElement("div", {
      className: "result-vertical"
    }, _react.default.createElement("div", {
      className: "repl-result"
    }, this.state.execution === 'processing' && this.streamingOutput(), this.nonstreamingOutput()))));
    return this.state.execution !== 'replayed' ? content : content && _react.default.createElement(ExpandableSection, {
      className: "kui--sample-output",
      showMore: strings('Show Sample Output'),
      showLess: strings('Hide Sample Output')
    }, content);
  }
  onRun(cmdline) {
    return __awaiter(this, void 0, void 0, function* () {
      if (!this.mounted) {
        return;
      }
      try {
        this.setState({
          execution: 'processing',
          startTime: Date.now(),
          endTime: null
        });
        this.emitLinkStatus('processing');
        // semicolons between commands and escape newlines
        const cleanedUpCmdline = cmdline.replace(/^\s*#.*$/gm, '') // strip comments
        .replace(/([^\\])(;?)\n/g, (_, p1, p2) => `${p1}${p2 || ';'} `) // newline->semi (don't add semi when not needed)
        .replace(/&;/g, '&'); // oof, correct for &; as just &
        const response = yield this.execWithStream(cleanedUpCmdline);
        const endTime = Date.now();
        const execution = (0, _core.isXtermResponse)(response) ? response.code === 0 ? 'done' : 'error' : (0, _core.isError)(response) ? 'error' : (0, _core.isMixedResponse)(response) ? response.find(_ => (0, _core.isXtermResponse)(_) && _.code !== 0 || (0, _core.isError)(_)) ? 'error' : 'done' : 'done';
        this.setState({
          execution,
          endTime
        });
        this.props.onResponse(execution, response, this.props.arg1, this.props.arg2, this.props.arg3);
        this.emitLinkStatus(execution);
      } catch (err) {
        this.setState({
          execution: 'error',
          endTime: Date.now()
        });
        this.props.onResponse('error', err, this.props.arg1, this.props.arg2, this.props.arg3);
        this.emitLinkStatus('error');
      }
    });
  }
  responseStatus() {
    if (this.state.execution === 'not-yet') {
      return "empty" /* BlockState.Empty */;
    } else if (this.state.execution === 'processing') {
      return "processing" /* BlockState.Processing */;
    } else if (this.state.execution === 'error') {
      return "error" /* BlockState.Error */;
    } else {
      return "valid-response" /* BlockState.ValidResponse */;
    }
  }

  get outputOnly() {
    return this.props.outputOnly === true;
  }
  /** Extract any data- properties from Props */
  dataProps() {
    return Object.entries(this.props).filter(([key]) => /^data-/.test(key)).reduce((M, [key, value]) => {
      M[key] = value;
      return M;
    }, {});
  }
  get showingReplayedOutput() {
    return !!(this.state.execution === 'replayed' && this.hasOutput);
  }
  render() {
    return _react.default.createElement(_MutabilityContext.MutabilityContext.Consumer, null, mutability => {
      const showAsExecutable = this.showingReplayedOutput || mutability.executable || undefined;
      const butUseSampleOutputOnRun = !mutability.executable;
      return _react.default.createElement("li", Object.assign({
        id: this.props.id,
        className: `repl-block ${this.responseStatus()} ${this.props.className || ''}`,
        "data-is-executable": showAsExecutable,
        "data-is-output-only": this.outputOnly || undefined,
        "data-is-ready": this.state.ready || undefined,
        "data-is-optional": this.props.optional || undefined,
        "data-is-validated": this.state.validated || undefined,
        "data-is-sample-output": this.hasOutput && this.state.execution === 'replayed' || undefined
      }, this.dataProps()), !this.outputOnly && this.input(showAsExecutable, butUseSampleOutputOnRun), !this.outputOnly && this.sourceRef(), this.output(mutability));
    });
  }
}
exports.default = CodeBlock;
class ActionButton extends _react.default.PureComponent {
  classNameOf(cls) {
    return `kui--block-action ${cls}`;
  }
  render() {
    return _react.default.createElement("div", {
      className: "flex-layout"
    }, _react.default.createElement(_Tooltip.default, {
      content: this.tooltip,
      position: "bottom-end"
    }, _react.default.createElement(_TwoFaceIcon.default, {
      a: this.icon,
      b: "Checkmark",
      delay: 4000,
      onClick: this.onClick,
      classNameB: "green-text",
      className: this.className
    })));
  }
}
/** The Code Block Trash button */
class Cleanup extends ActionButton {
  constructor() {
    super(...arguments);
    this._className = this.classNameOf('kui--block-action-cleanup');
  }
  get className() {
    return this._className;
  }
  get tooltip() {
    return 'Clean up the effects of this code block';
  }
  get icon() {
    return 'Trash';
  }
  get onClick() {
    return this.props.onCleanup;
  }
}
/** The Code Block Run button */
class Run extends ActionButton {
  constructor() {
    super(...arguments);
    this._className = this.classNameOf('kui--block-action-run');
  }
  get className() {
    return this._className;
  }
  get tooltip() {
    return strings(!this.props.ready && this.props.execution !== 'done' && this.props.execution !== 'error' ? 'Execute with caution! The pre-requisites for this code block have not yet been satisfied.' : this.props.execution === 'done' ? 'Re-execute this command' : this.props.optional ? 'Execution of this command is optional' : 'Execute this command');
  }
  get icon() {
    return 'Play';
  }
  get onClick() {
    return this.props.onRun;
  }
}