import { LoadingSpinner } from '@sourcegraph/react-loading-spinner';
import * as React from 'react';
import { Link } from 'react-router-dom';
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 { Resizable } from '../../../components/Resizable';
import { isErrorLike } from '../../../util/errors';
import { asError } from '../../../util/errors';
import { RepositoryIcon } from '../../../util/icons'; // TODO: Switch to mdi icon
import { RepoLink } from '../../RepoLink';
import { FileLocations, FileLocationsError, FileLocationsNotFound } from './FileLocations';
/**
 * Displays a two-column view, with a repository list, and a list of file excerpts for the selected tree item.
 */
export class FileLocationsTree extends React.PureComponent {
    constructor() {
        super(...arguments);
        this.state = { loading: false };
        this.componentUpdates = new Subject();
        this.locationsUpdates = new Subject();
        this.subscriptions = new Subscription();
        this.onSelectTree = (e, repo) => {
            e.preventDefault();
            this.setState({ selectedRepo: repo });
            if (this.props.onSelectTree) {
                this.props.onSelectTree();
            }
        };
    }
    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 });
        }
        // Don't show tree until we have at least one item.
        if (this.state.locationsOrError === undefined) {
            return null;
        }
        else if (this.state.loading && this.state.locationsOrError.length === 0) {
            return React.createElement(LoadingSpinner, { className: "icon-inline p-2" });
        }
        // Locations grouped by repository.
        const locationsByRepo = new Map();
        // Repositories with >0 locations, in order (to avoid jitter as more results stream in).
        const orderedRepos = [];
        if (this.state.locationsOrError) {
            for (const loc of this.state.locationsOrError) {
                const { repoPath } = parseRepoURI(loc.uri);
                if (!locationsByRepo.has(repoPath)) {
                    locationsByRepo.set(repoPath, []);
                    orderedRepos.push(repoPath);
                }
                locationsByRepo.get(repoPath).push(loc);
            }
        }
        const selectedRepo = this.state.selectedRepo && orderedRepos.includes(this.state.selectedRepo)
            ? this.state.selectedRepo
            : orderedRepos[0];
        return (React.createElement("div", { className: `file-locations-tree ${this.props.className}` },
            React.createElement(Resizable, { className: "file-locations-tree__resizable", handlePosition: "right", storageKey: "file-locations-tree-resizable", defaultSize: 200 /* px */, element: React.createElement("div", { className: "list-group list-group-flush file-locations-tree__list" },
                    orderedRepos.map((repo, i) => (React.createElement(Link, { key: i, className: `list-group-item file-locations-tree__item ${selectedRepo === repo ? 'active' : ''}`, to: this.props.location, 
                        // tslint:disable-next-line:jsx-no-lambda
                        onClick: e => this.onSelectTree(e, repo) },
                        React.createElement("span", { className: "file-locations-tree__item-name", title: repo },
                            React.createElement(RepositoryIcon, { className: "icon-inline file-locations-tree__item-icon" }),
                            React.createElement("span", { className: "file-locations-tree__item-name-text" },
                                React.createElement(RepoLink, { to: null, repoPath: repo }))),
                        React.createElement("span", { className: "badge badge-secondary badge-pill file-locations-tree__item-badge" }, locationsByRepo.get(repo).length)))),
                    this.state.loading && React.createElement(LoadingSpinner, { className: "icon-inline p-2" })) }),
            React.createElement(FileLocations, { className: "file-locations-tree__content", 
                // tslint:disable-next-line:jsx-no-lambda
                query: () => of({
                    loading: false,
                    locations: selectedRepo ? locationsByRepo.get(selectedRepo) : [],
                }), updates: this.locationsUpdates, inputRepo: this.props.inputRepo, inputRevision: this.props.inputRevision, onSelect: this.props.onSelectLocation, icon: RepositoryIcon, pluralNoun: this.props.pluralNoun, isLightTheme: this.props.isLightTheme })));
    }
}
//# sourceMappingURL=FileLocationsTree.js.map