/*
 * 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.
 */
var __awaiter = (this && this.__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());
    });
};
import React from 'react';
import { Events, i18n, isAbortableResponse, isCodedError, isCommentaryResponse, isTabLayoutModificationResponse, isRadioTable, isReactResponse, isMarkdownResponse, isMixedResponse, isMultiModalResponse, isNavResponse, isXtermResponse, isTable, Util } from '@kui-shell/core';
import { hasUUID, hasCommand, hasBeenRerun, isActive, isBeingRerun, isFinished, isProcessingOrBeingRerun as isProcessing, isOk, isCancelled, isEmpty, isOutputOnly, isOutputRedirected, isOops, isReplay, isWithCompleteEvent } from './BlockModel';
import Actions from './Actions';
import Scalar from '../../../Content/Scalar/'; // !! DO NOT MAKE LAZY. See https://github.com/IBM/kui/issues/6758
import KuiContext from '../../../Client/context';
const Ansi = React.lazy(() => import('../../../Content/Scalar/Ansi'));
const ExpandableSection = React.lazy(() => import('../../../spi/ExpandableSection'));
const strings = i18n('plugin-client-common');
export default class Output extends React.PureComponent {
    constructor(props) {
        super(props);
        this._willRemove = () => this.props.willRemove(undefined, this.props.idx);
        this._willUpdateCommand = (command) => this.props.willUpdateCommand(this.props.idx, command);
        this.streamingOutput = [];
        this.unmounted = true;
        this._onRender = this.onRender.bind(this);
        const streamingConsumer = this.streamingConsumer.bind(this);
        this.state = {
            alreadyListen: false,
            isResultRendered: false,
            nStreamingOutputs: 0,
            streamingConsumer
        };
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    streamingConsumer(part) {
        return __awaiter(this, void 0, void 0, function* () {
            if (hasUUID(this.props.model)) {
                const tabUUID = this.props.uuid;
                const execUUID = this.props.model.execUUID;
                // done with this part... not done with all parts
                const done = () => {
                    this.props.onRender();
                    Events.eventChannelUnsafe.emit(`/command/stdout/done/${tabUUID}/${execUUID}`);
                };
                // part === null: the controller wants to clear prior output
                if (part === null) {
                    // remove last output
                    this.streamingOutput.pop();
                    this.setState(curState => ({
                        nStreamingOutputs: curState.nStreamingOutputs - 1
                    }));
                    done();
                }
                else {
                    if (isOutputRedirected(this.props.model)) {
                        // if we were asked to redirect to a file, then we can
                        // immediately indicate that we are done with this part
                        done();
                    }
                    else {
                        this.streamingOutput.push(Buffer.isBuffer(part) ? part.toString() : part);
                        // use setTimeout to introduce hysteresis, so we aren't
                        // forcing a react re-render for a bunch of tiny streaming
                        // updates
                        const update = () => {
                            this.setState({
                                nStreamingOutputs: this.streamingOutput.length
                            });
                            setTimeout(done, 10);
                        };
                        if (this.state.nStreamingOutputs === 0) {
                            update();
                        }
                        else {
                            setTimeout(update, 10);
                        }
                    }
                }
            }
        });
    }
    static getDerivedStateFromProps(props, state) {
        if (!state.alreadyListen && (isActive(props.model) || isProcessing(props.model))) {
            // listen for streaming output (unless the output has been redirected to a file)
            const tabUUID = props.uuid;
            Events.eventChannelUnsafe.on(`/command/stdout/${tabUUID}/${props.model.execUUID}`, state.streamingConsumer);
            return {
                alreadyListen: true,
                isResultRendered: false,
                streamingOutput: []
            };
        }
        else if (isFinished(props.model) && !state.isResultRendered) {
            const tabUUID = props.uuid;
            if (!isEmpty(props.model)) {
                Events.eventChannelUnsafe.off(`/command/stdout/${tabUUID}/${props.model.execUUID}`, state.streamingConsumer);
            }
            return {
                alreadyListen: false,
                isResultRendered: true
            };
        }
        else {
            return state;
        }
    }
    componentDidMount() {
        this.unmounted = false;
    }
    componentWillUnmount() {
        this.unmounted = true;
    }
    onRender(assertHasContent) {
        if (this.unmounted)
            return;
        else if (this.props.onRender && !isBeingRerun(this.props.model) && !hasBeenRerun(this.props.model)) {
            // we don't want reruns to trigger any scrolling behavior
            this.props.onRender();
        }
        this.setState({ assertHasContent });
    }
    hasStreamingOutput() {
        return this.state.nStreamingOutputs > 0;
    }
    outputWillOverflow() {
        // RadioTable currently uses the Dropdown component that will overflow
        return isWithCompleteEvent(this.props.model) && isRadioTable(this.props.model.response);
    }
    stream() {
        if (this.hasStreamingOutput()) {
            // we may have duplicates in streamingOutput and response
            // see https://github.com/kubernetes-sigs/kui/pull/8354
            const response = isWithCompleteEvent(this.props.model) ? this.props.model.response : undefined;
            const streamingOutput = !response
                ? this.streamingOutput
                : this.streamingOutput.filter(_ => {
                    if (isMixedResponse(response)) {
                        return !response.find(_2 => _ === _2);
                    }
                    else {
                        return _ !== response;
                    }
                });
            if (streamingOutput.length < this.streamingOutput.length) {
                this.streamingOutput = streamingOutput;
            }
            if (streamingOutput.every(_ => typeof _ === 'string')) {
                const combined = streamingOutput.join('');
                return (combined.length > 0 && (React.createElement("div", { className: "repl-result-like result-vertical", "data-stream": true },
                    React.createElement(React.Suspense, { fallback: React.createElement("div", null) },
                        React.createElement(Ansi, null, combined)))));
            }
            return (React.createElement("div", { className: "repl-result-like result-vertical", "data-stream": true }, streamingOutput.map((part, idx) => (React.createElement(React.Suspense, { fallback: React.createElement("div", null), key: idx },
                React.createElement(Scalar, { tab: this.props.tab, execUUID: hasUUID(this.props.model) && this.props.model.execUUID, response: part, isWidthConstrained: this.props.isWidthConstrained, willChangeSize: this.props.willChangeSize, willUpdateCommand: this._willUpdateCommand, onRender: this._onRender }))))));
        }
    }
    result() {
        if (isActive(this.props.model) || isProcessing(this.props.model)) {
            return React.createElement("div", { className: "repl-result" });
        }
        else if (isEmpty(this.props.model)) {
            // no result to display for these cases
            return React.createElement(React.Fragment, null);
        }
        else {
            const statusCode = isOops(this.props.model)
                ? isCodedError(this.props.model.response)
                    ? this.props.model.response.code || this.props.model.response.statusCode
                    : 500
                : isFinished(this.props.model)
                    ? 0
                    : undefined;
            return (React.createElement("div", { className: 'repl-result' +
                    (isOops(this.props.model) ? ' oops' : '') +
                    (isWithCompleteEvent(this.props.model) && isMixedResponse(this.props.model.response)
                        ? ' flex-column'
                        : '') +
                    (this.outputWillOverflow() ? ' overflow-visible' : ''), "data-status-code": statusCode }, isCancelled(this.props.model) ? (React.createElement(React.Fragment, null)) : (React.createElement(React.Suspense, { fallback: React.createElement("div", null) },
                React.createElement(Scalar, { tab: this.props.tab, execUUID: hasUUID(this.props.model) && this.props.model.execUUID, response: this.props.model.response, completeEvent: this.props.model.completeEvent, isWidthConstrained: this.props.isWidthConstrained, willChangeSize: this.props.willChangeSize, willFocusBlock: this.props.willFocusBlock, willRemove: this._willRemove, willUpdateCommand: this._willUpdateCommand, onRender: this._onRender })))));
        }
    }
    cursor() {
        return React.createElement(React.Fragment, null);
        /* if (isProcessing(this.props.model)) {
          return (
            <div className="repl-result-spinner">
              <div className="repl-result-spinner-inner"></div>
            </div>
          )
        } */
    }
    isShowingSomethingInTerminal(block) {
        if (isProcessing(this.props.model)) {
            return this.hasStreamingOutput();
        }
        else if (isFinished(block) && !isCancelled(block) && !isEmpty(block)) {
            const { response } = block;
            return (isOops(block) ||
                isAbortableResponse(response) ||
                isMultiModalResponse(response) ||
                isNavResponse(response) ||
                isCommentaryResponse(response) ||
                isTabLayoutModificationResponse(response) ||
                isReactResponse(response) ||
                Util.isHTML(response) ||
                isMarkdownResponse(response) ||
                (Array.isArray(response) && response.length > 0) ||
                (typeof response === 'string' && response.length > 0) ||
                typeof response === 'number' ||
                isTable(response) ||
                isMixedResponse(response) ||
                (isXtermResponse(response) && response.rows && response.rows.length !== 0) ||
                this.hasStreamingOutput());
        }
        else {
            return false;
        }
    }
    ok(hasContent) {
        if (isOk(this.props.model)) {
            if (hasContent) {
                return React.createElement("div", { className: "ok" });
            }
            else {
                return React.createElement("div", { className: "ok" }, strings('ok'));
            }
        }
    }
    ctx( /* insideBrackets: React.ReactNode = this.props.displayedIdx || this.props.idx + 1 */) {
        return (React.createElement(KuiContext.Consumer, null, config => !config.noPromptContext && (React.createElement("span", { className: "repl-context", onClick: this.props.willFocusBlock, "data-input-count": this.props.idx, "data-custom-prompt": !!config.prompt || undefined }))));
    }
    /** For output-only blocks, render the Block Actions */
    actions() {
        if (isOutputOnly(this.props.model)) {
            return React.createElement(Actions, Object.assign({}, this.props, { command: hasCommand(this.props.model) && this.props.model.command }));
        }
    }
    /** @return the UI for the main/content part of the Output */
    content(hasContent) {
        return (React.createElement("div", { className: "result-vertical" },
            this.stream(),
            this.result(),
            this.cursor(),
            this.ok(hasContent)));
    }
    render() {
        const hasContent = this.state.assertHasContent !== undefined
            ? this.state.assertHasContent
            : this.isShowingSomethingInTerminal(this.props.model);
        // when displaying blocks in a loaded Snapshot (isReplay) that
        // have not yet been rerun (hasBeenRerun), then wrap an expando
        // around them (except don't wrap expandos around output-only
        // blocks, such as for Commentary)
        const noExpando = !isReplay(this.props.model) || hasBeenRerun(this.props.model) || isOutputOnly(this.props.model);
        const content = noExpando ? (this.content(hasContent)) : (React.createElement(ExpandableSection, { className: "flex-fill", showMore: strings('Show Sample Output'), showLess: strings('Hide Sample Output') }, this.content(hasContent)));
        return (React.createElement("div", { className: 'repl-output ' + (hasContent ? ' repl-result-has-content' : '') },
            hasContent && this.ctx(),
            content,
            this.actions()));
    }
}
//# sourceMappingURL=Output.js.map