import { createHoverifier, HoverOverlay, } from '@sourcegraph/codeintellify';
import { LoadingSpinner } from '@sourcegraph/react-loading-spinner';
import { isEqual, upperFirst } from 'lodash';
import * as React from 'react';
import { Link } from 'react-router-dom';
import { merge, of, Subject, Subscription } from 'rxjs';
import { catchError, distinctUntilChanged, filter, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
import { getHover, getJumpURL } from '../../backend/features';
import { gql, queryGraphQL } from '../../backend/graphql';
import { PageTitle } from '../../components/PageTitle';
import { eventLogger } from '../../tracking/eventLogger';
import { getModeFromPath } from '../../util';
import { asError, createAggregateError, isErrorLike } from '../../util/errors';
import { memoizeObservable } from '../../util/memoize';
import { propertyIsDefined } from '../../util/types';
import { GitCommitNode } from '../commits/GitCommitNode';
import { gitCommitFragment } from '../commits/RepositoryCommitsPage';
import { FileDiffConnection } from '../compare/FileDiffConnection';
import { FileDiffNode } from '../compare/FileDiffNode';
import { queryRepositoryComparisonFileDiffs } from '../compare/RepositoryCompareDiffPage';
const queryCommit = memoizeObservable((args) => queryGraphQL(gql `
                query RepositoryCommit($repo: ID!, $revspec: String!) {
                    node(id: $repo) {
                        ... on Repository {
                            commit(rev: $revspec) {
                                __typename # necessary so that isErrorLike(x) is false when x: GQL.IGitCommit
                                ...GitCommitFields
                            }
                        }
                    }
                }
                ${gitCommitFragment}
            `, args).pipe(map(({ data, errors }) => {
    if (!data || !data.node) {
        throw createAggregateError(errors);
    }
    const repo = data.node;
    if (!repo.commit) {
        throw createAggregateError(errors || [new Error('Commit not found')]);
    }
    return repo.commit;
})), args => `${args.repo}:${args.revspec}`);
const logTelemetryEvent = (event, data) => eventLogger.log(event, data);
const LinkComponent = (props) => React.createElement(Link, Object.assign({}, props));
/** Displays a commit. */
export class RepositoryCommitPage extends React.Component {
    constructor(props) {
        super(props);
        this.componentUpdates = new Subject();
        /** Emits whenever the ref callback for the hover element is called */
        this.hoverOverlayElements = new Subject();
        this.nextOverlayElement = (element) => this.hoverOverlayElements.next(element);
        /** Emits whenever the ref callback for the hover element is called */
        this.repositoryCommitPageElements = new Subject();
        this.nextRepositoryCommitPageElement = (element) => this.repositoryCommitPageElements.next(element);
        /** Emits when the go to definition button was clicked */
        this.goToDefinitionClicks = new Subject();
        this.nextGoToDefinitionClick = (event) => this.goToDefinitionClicks.next(event);
        /** Emits when the close button was clicked */
        this.closeButtonClicks = new Subject();
        this.nextCloseButtonClick = (event) => this.closeButtonClicks.next(event);
        this.subscriptions = new Subscription();
        this.queryDiffs = (args) => queryRepositoryComparisonFileDiffs(Object.assign({}, args, { repo: this.props.repo.id, base: commitParentOrEmpty(this.state.commitOrError), head: this.state.commitOrError.oid }));
        this.hoverifier = createHoverifier({
            closeButtonClicks: this.closeButtonClicks,
            goToDefinitionClicks: this.goToDefinitionClicks,
            hoverOverlayElements: this.hoverOverlayElements,
            hoverOverlayRerenders: this.componentUpdates.pipe(withLatestFrom(this.hoverOverlayElements, this.repositoryCommitPageElements), map(([, hoverOverlayElement, repositoryCommitPageElement]) => ({
                hoverOverlayElement,
                // The root component element is guaranteed to be rendered after a componentDidUpdate
                relativeElement: repositoryCommitPageElement,
            })), 
            // Can't reposition HoverOverlay if it wasn't rendered
            filter(propertyIsDefined('hoverOverlayElement'))),
            pushHistory: path => this.props.history.push(path),
            logTelemetryEvent,
            fetchHover: hoveredToken => getHover(this.getLSPTextDocumentPositionParams(hoveredToken), this.props),
            fetchJumpURL: hoveredToken => getJumpURL(this.getLSPTextDocumentPositionParams(hoveredToken), this.props),
        });
        this.subscriptions.add(this.hoverifier);
        this.state = this.hoverifier.hoverState;
        this.subscriptions.add(this.hoverifier.hoverStateUpdates.subscribe(update => {
            this.setState(update);
        }));
    }
    getLSPTextDocumentPositionParams(hoveredToken) {
        return {
            repoPath: hoveredToken.repoPath,
            rev: hoveredToken.rev,
            filePath: hoveredToken.filePath,
            commitID: hoveredToken.commitID,
            position: hoveredToken,
            mode: getModeFromPath(hoveredToken.filePath || ''),
        };
    }
    componentDidMount() {
        eventLogger.logViewEvent('RepositoryCommit');
        this.subscriptions.add(this.componentUpdates
            .pipe(distinctUntilChanged((a, b) => a.repo.id === b.repo.id && a.match.params.revspec === b.match.params.revspec), switchMap(({ repo, match }) => merge(of({ commitOrError: undefined }), queryCommit({ repo: repo.id, revspec: match.params.revspec }).pipe(catchError(error => [asError(error)]), map(c => ({ commitOrError: c })), tap(({ commitOrError }) => {
            if (isErrorLike(commitOrError)) {
                this.props.onDidUpdateExternalLinks(undefined);
            }
            else {
                this.props.onDidUpdateExternalLinks(commitOrError.externalURLs);
            }
        })))))
            .subscribe(stateUpdate => this.setState(stateUpdate), error => console.error(error)));
        this.componentUpdates.next(this.props);
    }
    shouldComponentUpdate(nextProps, nextState) {
        return !isEqual(this.props, nextProps) || !isEqual(this.state, nextState);
    }
    componentDidUpdate() {
        this.componentUpdates.next(this.props);
    }
    componentWillUnmount() {
        this.props.onDidUpdateExternalLinks(undefined);
        this.subscriptions.unsubscribe();
    }
    render() {
        return (React.createElement("div", { className: "repository-commit-page area", ref: this.nextRepositoryCommitPageElement },
            React.createElement(PageTitle, { title: this.state.commitOrError && !isErrorLike(this.state.commitOrError)
                    ? this.state.commitOrError.subject
                    : `Commit ${this.props.match.params.revspec}` }),
            React.createElement("div", { className: "area__content" }, this.state.commitOrError === undefined ? (React.createElement(LoadingSpinner, { className: "icon-inline mt-2" })) : isErrorLike(this.state.commitOrError) ? (React.createElement("div", { className: "alert alert-danger mt-2" },
                "Error: ",
                upperFirst(this.state.commitOrError.message))) : (React.createElement(React.Fragment, null,
                React.createElement("div", { className: "card repository-commit-page__card" },
                    React.createElement("div", { className: "card-body" },
                        React.createElement(GitCommitNode, { node: this.state.commitOrError, repoName: this.props.repo.name, expandCommitMessageBody: true, showSHAAndParentsRow: true }))),
                React.createElement("div", { className: "mb-3" }),
                React.createElement(FileDiffConnection, { listClassName: "list-group list-group-flush", noun: "changed file", pluralNoun: "changed files", queryConnection: this.queryDiffs, nodeComponent: FileDiffNode, nodeComponentProps: {
                        base: {
                            repoPath: this.props.repo.name,
                            repoID: this.props.repo.id,
                            rev: commitParentOrEmpty(this.state.commitOrError),
                            commitID: commitParentOrEmpty(this.state.commitOrError),
                        },
                        head: {
                            repoPath: this.props.repo.name,
                            repoID: this.props.repo.id,
                            rev: this.state.commitOrError.oid,
                            commitID: this.state.commitOrError.oid,
                        },
                        lineNumbers: true,
                        extensions: this.props.extensions,
                        location: this.props.location,
                        history: this.props.history,
                        hoverifier: this.hoverifier,
                        extensionsController: this.props.extensionsController,
                    }, updateOnChange: `${this.props.repo.id}:${this.state.commitOrError.oid}`, defaultFirst: 25, hideSearch: true, noSummaryIfAllNodesVisible: true, history: this.props.history, location: this.props.location, extensionsOnVisibleTextDocumentsChange: this.props.extensionsOnVisibleTextDocumentsChange })))),
            this.state.hoverOverlayProps && (React.createElement(HoverOverlay, Object.assign({}, this.state.hoverOverlayProps, { logTelemetryEvent: logTelemetryEvent, linkComponent: LinkComponent, hoverRef: this.nextOverlayElement, onGoToDefinitionClick: this.nextGoToDefinitionClick, onCloseButtonClick: this.nextCloseButtonClick })))));
    }
}
function commitParentOrEmpty(commit) {
    // 4b825dc642cb6eb9a060e54bf8d69288fbee4904 is `git hash-object -t tree /dev/null`, which is used as the base
    // when computing the `git diff` of the root commit.
    return commit.parents.length > 0 ? commit.parents[0].oid : '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
}
//# sourceMappingURL=RepositoryCommitPage.js.map