// Copyright Project Harbor 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.
import {
    Component,
    EventEmitter,
    Input,
    OnChanges,
    OnDestroy,
    OnInit,
    Output,
    SimpleChanges,
} from '@angular/core';
import { Label } from '../../../../../ng-swagger-gen/models/label';
import { forkJoin, Observable, Subject, Subscription } from 'rxjs';
import { LabelService } from '../../../../../ng-swagger-gen/services/label.service';
import {
    debounceTime,
    distinctUntilChanged,
    filter,
    finalize,
    map,
    switchMap,
} from 'rxjs/operators';
import { Router } from '@angular/router';

const GLOBAL: string = 'g';
const PROJECT: string = 'p';
const PAGE_SIZE: number = 50;

@Component({
    selector: 'app-label-selector',
    templateUrl: './label-selector.component.html',
    styleUrls: ['./label-selector.component.scss'],
})
export class LabelSelectorComponent implements OnInit, OnChanges, OnDestroy {
    @Input()
    usedInDropdown: boolean = false;
    @Input()
    ownedLabels: Label[] = [];
    @Input()
    width: number = 180; // unit px
    @Input()
    scope: string = GLOBAL; // 'g' for global and 'p' for project, default 'g'
    @Input()
    projectId: number; // if scope = 'p', projectId is required
    @Input()
    dropdownOpened: boolean; // parent component opened status
    candidateLabels: Label[] = [];
    searchValue: string;
    loading: boolean = false;
    @Output()
    clickLabel = new EventEmitter<{
        label: Label;
        isAdd: boolean;
    }>();
    private _searchSubject = new Subject<string>();
    private _subSearch: Subscription;
    constructor(private labelService: LabelService, private router: Router) {
        if (!this._subSearch) {
            this._subSearch = this._searchSubject
                .pipe(
                    debounceTime(500),
                    distinctUntilChanged(),
                    filter(labelName => {
                        if (!labelName) {
                            this.initCandidateLabel();
                        }
                        return !!labelName;
                    }),
                    switchMap(labelName => {
                        return this.getLabelObservable(labelName);
                    })
                )
                .subscribe(res => {
                    this.candidateLabels = res;
                });
        }
    }

    ngOnInit(): void {
        this.checkProjectId();
        this.initCandidateLabel();
    }

    initCandidateLabel() {
        // Place the owned label at the top of the array then remove duplicates
        const Obs: Observable<Label[]>[] = [];
        if (this.ownedLabels?.length) {
            const projectLabelIds: number[] = [];
            const globalLabelIds: number[] = [];
            this.ownedLabels?.forEach(item => {
                if (item.scope === PROJECT) {
                    projectLabelIds.push(item.id);
                }
                if (item.scope === GLOBAL) {
                    globalLabelIds.push(item.id);
                }
            });
            if (projectLabelIds?.length) {
                Obs.push(
                    this.labelService.ListLabels({
                        page: 1,
                        pageSize: PAGE_SIZE,
                        scope: PROJECT,
                        projectId: this.projectId,
                        q: encodeURIComponent(
                            `id={${projectLabelIds.join(' ')}}`
                        ),
                    })
                );
            }
            if (globalLabelIds?.length) {
                Obs.push(
                    this.labelService.ListLabels({
                        page: 1,
                        pageSize: PAGE_SIZE,
                        scope: GLOBAL,
                        q: encodeURIComponent(
                            `id={${globalLabelIds.join(' ')}}`
                        ),
                    })
                );
            }
        }
        Obs.push(this.getLabelObservable(''));
        forkJoin(Obs)
            .pipe(
                map(result => [].concat.apply([], result)),
                map((result: Label[]) => {
                    return result.filter(
                        (v, i, a) => a.findIndex(v2 => v2.id === v.id) === i
                    );
                })
            )
            .subscribe(res => {
                this.candidateLabels = res;
            });
    }

    ngOnChanges(changes: SimpleChanges): void {
        this.checkProjectId();
    }
    ngOnDestroy() {
        if (this._subSearch) {
            this._subSearch.unsubscribe();
            this._subSearch = null;
        }
    }

    checkProjectId() {
        if (this.scope === PROJECT && !this.projectId) {
            throw new Error('Attribute [projectId] is required');
        }
    }

    search() {
        this._searchSubject.next(this.searchValue);
    }

    selectLabel(label: Label) {
        this.clickLabel.emit({ label: label, isAdd: !this.isSelect(label) });
    }
    isSelect(label: Label): boolean {
        if (this.ownedLabels?.length) {
            return this.ownedLabels.some(item => {
                return item.id === label.id && this.dropdownOpened;
            });
        }
        return false;
    }
    goToLabelPage() {
        if (this.scope === PROJECT) {
            this.router.navigate([
                'harbor',
                'projects',
                this.projectId,
                'labels',
            ]);
        } else {
            this.router.navigate(['harbor', 'labels']);
        }
    }

    getLabelObservable(labelName: string): Observable<Label[]> {
        this.loading = true;
        if (this.scope === PROJECT) {
            return forkJoin([
                this.labelService.ListLabels({
                    page: 1,
                    pageSize: PAGE_SIZE,
                    scope: PROJECT,
                    projectId: this.projectId,
                    q: labelName
                        ? encodeURIComponent(`name=~${labelName}`)
                        : null,
                }),
                this.labelService.ListLabels({
                    page: 1,
                    pageSize: PAGE_SIZE,
                    scope: GLOBAL,
                    q: labelName
                        ? encodeURIComponent(`name=~${labelName}`)
                        : null,
                }),
            ]).pipe(
                map(result => [].concat.apply([], result)),
                finalize(() => (this.loading = false))
            );
        }
        return this.labelService
            .ListLabels({
                page: 1,
                pageSize: PAGE_SIZE,
                scope: GLOBAL,
                q: labelName ? encodeURIComponent(`name=~${labelName}`) : null,
            })
            .pipe(finalize(() => (this.loading = false)));
    }
}
