import { LoadingSpinner } from '@sourcegraph/react-loading-spinner';
import { upperFirst } from 'lodash';
import AlertCircleIcon from 'mdi-react/AlertCircleIcon';
import MapSearchIcon from 'mdi-react/MapSearchIcon';
import * as React from 'react';
import { combineLatest, merge, of, Subject, Subscription } from 'rxjs';
import { catchError, delay, distinctUntilChanged, map, startWith, switchMap, takeUntil } from 'rxjs/operators';
import { isError } from 'util';
import { parseRepoURI } from '../..';
import { FileMatch } from '../../../components/FileMatch';
import { VirtualList } from '../../../components/VirtualList';
import { asError } from '../../../util/errors';
import { isErrorLike } from '../../../util/errors';
import { propertyIsDefined } from '../../../util/types';
import { toPrettyBlobURL, toRepoURL } from '../../../util/url';
export const FileLocationsError = ({ pluralNoun, error }) => (React.createElement("div", { className: "file-locations__error alert alert-danger m-2" },
    React.createElement(AlertCircleIcon, { className: "icon-inline" }),
    " Error getting ",
    pluralNoun,
    ": ",
    upperFirst(error.message)));
export const FileLocationsNotFound = ({ pluralNoun }) => (React.createElement("div", { className: "file-locations__not-found m-2" },
    React.createElement(MapSearchIcon, { className: "icon-inline" }),
    " No ",
    pluralNoun,
    " found"));
/**
 * Displays a flat list of file excerpts. For a tree view, use FileLocationsTree.
 */
export class FileLocations extends React.PureComponent {
    constructor() {
        super(...arguments);
        this.state = {
            itemsToShow: 3,
            loading: false,
        };
        this.componentUpdates = new Subject();
        this.subscriptions = new Subscription();
        this.onShowMoreItems = () => {
            this.setState(state => ({ itemsToShow: state.itemsToShow + 3 }));
        };
        this.onSelect = () => {
            if (this.props.onSelect) {
                this.props.onSelect();
            }
        };
    }
    componentDidMount() {
        // Manually requested refreshes.
        const refreshRequests = new Subject();
        // Changes to the query callback function.
        const queryFuncChanges = this.componentUpdates.pipe(map(({ query }) => query), distinctUntilChanged());
        // Force updates from parent component.
        if (this.props.updates) {
            this.subscriptions.add(this.props.updates.subscribe(c => refreshRequests.next()));
        }
        this.subscriptions.add(combineLatest(queryFuncChanges, refreshRequests.pipe(startWith(void 0)))
            .pipe(switchMap(([query]) => {
            const result = query().pipe(catchError(error => [asError(error)]), map(c => ({
                locationsOrError: isError(c) ? c : c.locations,
                loading: isError(c) ? false : c.loading,
            })));
            return merge(result, of({ loading: true }).pipe(delay(50), takeUntil(result)) // delay loading spinner to reduce jitter
            ).pipe(startWith({ locationsOrError: undefined, loading: false }) // clear old data immediately
            );
        }))
            .subscribe(stateUpdate => this.setState(stateUpdate), error => console.error(error)));
        this.componentUpdates.next(this.props);
    }
    componentWillReceiveProps(nextProps) {
        this.componentUpdates.next(nextProps);
    }
    componentWillUnmount() {
        this.subscriptions.unsubscribe();
    }
    render() {
        if (isErrorLike(this.state.locationsOrError)) {
            return React.createElement(FileLocationsError, { pluralNoun: this.props.pluralNoun, error: this.state.locationsOrError });
        }
        if (!this.state.loading && this.state.locationsOrError && this.state.locationsOrError.length === 0) {
            return React.createElement(FileLocationsNotFound, { pluralNoun: this.props.pluralNoun });
        }
        // Locations by fully qualified URI, like git://github.com/gorilla/mux?rev#mux.go
        const locationsByURI = new Map();
        // URIs with >0 locations, in order (to avoid jitter as more results stream in).
        const orderedURIs = [];
        if (this.state.locationsOrError) {
            for (const loc of this.state.locationsOrError) {
                if (!locationsByURI.has(loc.uri)) {
                    locationsByURI.set(loc.uri, []);
                    const { repoPath } = parseRepoURI(loc.uri);
                    orderedURIs.push({ uri: loc.uri, repo: repoPath });
                }
                locationsByURI.get(loc.uri).push(loc);
            }
        }
        return (React.createElement("div", { className: `file-locations ${this.props.className}` },
            React.createElement(VirtualList, { itemsToShow: this.state.itemsToShow, onShowMoreItems: this.onShowMoreItems, items: orderedURIs.map(({ uri, repo }, i) => (React.createElement(FileMatch, { key: i, expanded: true, result: refsToFileMatch(uri, repo === this.props.inputRepo ? this.props.inputRevision : undefined, locationsByURI.get(uri)), icon: this.props.icon, onSelect: this.onSelect, showAllMatches: true, isLightTheme: this.props.isLightTheme }))) }),
            this.state.loading && React.createElement(LoadingSpinner, { className: "icon-inline p-2" })));
    }
}
function refsToFileMatch(uri, rev, refs) {
    const p = parseRepoURI(uri);
    return {
        file: {
            path: p.filePath || '',
            url: toPrettyBlobURL({ repoPath: p.repoPath, filePath: p.filePath, rev: rev || p.commitID || '' }),
            commit: {
                oid: p.commitID || p.rev || rev,
            },
        },
        repository: {
            name: p.repoPath,
            url: toRepoURL(p.repoPath),
        },
        limitHit: false,
        lineMatches: refs.filter(propertyIsDefined('range')).map((ref) => ({
            preview: '',
            limitHit: false,
            lineNumber: ref.range.start.line,
            offsetAndLengths: [[ref.range.start.character, ref.range.end.character - ref.range.start.character]],
        })),
    };
}
//# sourceMappingURL=FileLocations.js.map