import { isEqual, pick, upperFirst } from 'lodash';
import AlertCircleIcon from 'mdi-react/AlertCircleIcon';
import * as React from 'react';
import { combineLatest, Subject, Subscription } from 'rxjs';
import { catchError, distinctUntilChanged, map, mapTo, startWith, switchMap, tap } from 'rxjs/operators';
import { makeRepoURI } from '..';
import { gql, queryGraphQL } from '../../backend/graphql';
import { HeroPage } from '../../components/HeroPage';
import { PageTitle } from '../../components/PageTitle';
import { isDiscussionsEnabled } from '../../discussions';
import { eventLogger } from '../../tracking/eventLogger';
import { createAggregateError, isErrorLike } from '../../util/errors';
import { memoizeObservable } from '../../util/memoize';
import { lprToRange, parseHash } from '../../util/url';
import { RepoHeaderContributionPortal } from '../RepoHeaderContributionPortal';
import { ToggleDiscussionsPanel } from './actions/ToggleDiscussions';
import { ToggleHistoryPanel } from './actions/ToggleHistoryPanel';
import { ToggleLineWrap } from './actions/ToggleLineWrap';
import { ToggleRenderedFileMode } from './actions/ToggleRenderedFileMode';
import { Blob } from './Blob';
import { BlobPanel } from './panel/BlobPanel';
import { RenderedFile } from './RenderedFile';
export function fetchBlobCacheKey(parsed) {
    return makeRepoURI(parsed) + parsed.isLightTheme + parsed.disableTimeout;
}
export const fetchBlob = memoizeObservable((args) => queryGraphQL(gql `
                query Blob(
                    $repoPath: String!
                    $commitID: String!
                    $filePath: String!
                    $isLightTheme: Boolean!
                    $disableTimeout: Boolean!
                ) {
                    repository(name: $repoPath) {
                        commit(rev: $commitID) {
                            file(path: $filePath) {
                                content
                                richHTML
                                highlight(disableTimeout: $disableTimeout, isLightTheme: $isLightTheme) {
                                    aborted
                                    html
                                }
                            }
                        }
                    }
                }
            `, args).pipe(map(({ data, errors }) => {
    if (!data ||
        !data.repository ||
        !data.repository.commit ||
        !data.repository.commit.file ||
        !data.repository.commit.file.highlight) {
        throw createAggregateError(errors);
    }
    return data.repository.commit.file;
})), fetchBlobCacheKey);
export class BlobPage extends React.PureComponent {
    constructor(props) {
        super(props);
        this.propsUpdates = new Subject();
        this.extendHighlightingTimeoutClicks = new Subject();
        this.subscriptions = new Subscription();
        this.onDidUpdateLineWrap = (value) => this.setState({ wrapCode: value });
        this.onExtendHighlightingTimeoutClick = () => this.extendHighlightingTimeoutClicks.next();
        this.state = {
            wrapCode: ToggleLineWrap.getValue(),
        };
    }
    logViewEvent() {
        eventLogger.logViewEvent('Blob', { fileShown: true });
    }
    componentDidMount() {
        this.logViewEvent();
        // Fetch repository revision.
        this.subscriptions.add(combineLatest(this.propsUpdates.pipe(map(props => pick(props, 'repoPath', 'commitID', 'filePath', 'isLightTheme')), distinctUntilChanged((a, b) => isEqual(a, b))), this.extendHighlightingTimeoutClicks.pipe(mapTo(true), startWith(false)))
            .pipe(tap(() => this.setState({ blobOrError: undefined })), switchMap(([{ repoPath, commitID, filePath, isLightTheme }, extendHighlightingTimeout]) => fetchBlob({
            repoPath,
            commitID,
            filePath,
            isLightTheme,
            disableTimeout: extendHighlightingTimeout,
        }).pipe(catchError(error => {
            console.error(error);
            return [error];
        }))))
            .subscribe(blobOrError => this.setState({ blobOrError }), err => console.error(err)));
        this.propsUpdates.next(this.props);
    }
    componentWillReceiveProps(newProps) {
        this.propsUpdates.next(newProps);
        if (newProps.repoPath !== this.props.repoPath ||
            newProps.commitID !== this.props.commitID ||
            newProps.filePath !== this.props.filePath ||
            ToggleRenderedFileMode.getModeFromURL(newProps.location) !==
                ToggleRenderedFileMode.getModeFromURL(this.props.location)) {
            this.logViewEvent();
        }
    }
    componentWillUnmount() {
        this.subscriptions.unsubscribe();
    }
    render() {
        if (isErrorLike(this.state.blobOrError)) {
            return (React.createElement(HeroPage, { icon: AlertCircleIcon, title: "Error", subtitle: upperFirst(this.state.blobOrError.message) }));
        }
        const renderMode = ToggleRenderedFileMode.getModeFromURL(this.props.location);
        // If url explicitly asks for a certain renderMode, renderAs is set to that renderMode, else it checks:
        // - If file contains richHTML and url does not include a line number: We render in richHTML.
        // - If file does not contain richHTML or the url includes a line number: We render in code view.
        const renderAs = renderMode ||
            (this.state.blobOrError && this.state.blobOrError.richHTML && !parseHash(this.props.location.hash).line
                ? 'rendered'
                : 'code');
        // Always render these to avoid UI jitter during loading when switching to a new file.
        const alwaysRender = (React.createElement(React.Fragment, null,
            React.createElement(PageTitle, { title: this.getPageTitle() }),
            React.createElement(RepoHeaderContributionPortal, { position: "right", priority: 20, element: React.createElement(ToggleHistoryPanel, { key: "toggle-blob-panel", location: this.props.location, history: this.props.history }), repoHeaderContributionsLifecycleProps: this.props.repoHeaderContributionsLifecycleProps }),
            renderAs === 'code' && (React.createElement(RepoHeaderContributionPortal, { position: "right", priority: 99, element: React.createElement(ToggleLineWrap, { key: "toggle-line-wrap", onDidUpdate: this.onDidUpdateLineWrap }), repoHeaderContributionsLifecycleProps: this.props.repoHeaderContributionsLifecycleProps })),
            isDiscussionsEnabled(this.props.configurationCascade) && (React.createElement(RepoHeaderContributionPortal, { position: "right", priority: 20, element: React.createElement(ToggleDiscussionsPanel, { key: "toggle-blob-discussion-panel", location: this.props.location, history: this.props.history }), repoHeaderContributionsLifecycleProps: this.props.repoHeaderContributionsLifecycleProps }))));
        if (!this.state.blobOrError) {
            // Render placeholder for layout before content is fetched.
            return React.createElement("div", { className: "blob-page__placeholder" }, alwaysRender);
        }
        return (React.createElement(React.Fragment, null,
            alwaysRender,
            this.state.blobOrError.richHTML && (React.createElement(RepoHeaderContributionPortal, { position: "right", priority: 100, element: React.createElement(ToggleRenderedFileMode, { key: "toggle-rendered-file-mode", mode: renderMode || 'rendered', location: this.props.location }), repoHeaderContributionsLifecycleProps: this.props.repoHeaderContributionsLifecycleProps })),
            this.state.blobOrError.richHTML &&
                renderAs === 'rendered' && (React.createElement(RenderedFile, { dangerousInnerHTML: this.state.blobOrError.richHTML, location: this.props.location })),
            renderAs === 'code' &&
                !this.state.blobOrError.highlight.aborted && (React.createElement(Blob, { className: "blob-page__blob", repoPath: this.props.repoPath, commitID: this.props.commitID, filePath: this.props.filePath, content: this.state.blobOrError.content, html: this.state.blobOrError.highlight.html, rev: this.props.rev, mode: this.props.mode, configurationCascade: this.props.configurationCascade, extensions: this.props.extensions, extensionsController: this.props.extensionsController, extensionsOnVisibleTextDocumentsChange: this.props.extensionsOnVisibleTextDocumentsChange, wrapCode: this.state.wrapCode, renderMode: renderMode, location: this.props.location, history: this.props.history })),
            !this.state.blobOrError.richHTML &&
                this.state.blobOrError.highlight.aborted && (React.createElement("div", { className: "blob-page__aborted" },
                React.createElement("div", { className: "alert alert-info" },
                    "Syntax-highlighting this file took too long. \u00A0",
                    React.createElement("button", { onClick: this.onExtendHighlightingTimeoutClick, className: "btn btn-sm btn-primary" }, "Try again")))),
            React.createElement(BlobPanel, Object.assign({}, this.props, { repoID: this.props.repoID, repoPath: this.props.repoPath, commitID: this.props.commitID, extensions: this.props.extensions, extensionsController: this.props.extensionsController, position: lprToRange(parseHash(this.props.location.hash))
                    ? lprToRange(parseHash(this.props.location.hash)).start
                    : undefined, repoHeaderContributionsLifecycleProps: this.props.repoHeaderContributionsLifecycleProps }))));
    }
    getPageTitle() {
        const repoPathSplit = this.props.repoPath.split('/');
        const repoStr = repoPathSplit.length > 2 ? repoPathSplit.slice(1).join('/') : this.props.repoPath;
        if (this.props.filePath) {
            const fileOrDir = this.props.filePath.split('/').pop();
            return `${fileOrDir} - ${repoStr}`;
        }
        return `${repoStr}`;
    }
}
//# sourceMappingURL=BlobPage.js.map