import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
import { ListingComponent, listingProvidersFactory, LoadingService, SearchPositions, TableColumnConfig } from '@iqser/common-ui';
import { combineLatest, Observable, of } from 'rxjs';
import { debounceTime, map, startWith, switchMap, tap } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { workflowFileStatusTranslations } from '@translations/file-status-translations';
import { TranslateService } from '@ngx-translate/core';
import { RouterHistoryService } from '@services/router-history.service';
import {
    Dossier,
    DOSSIERS_ARCHIVE,
    DossierTemplate,
    IMatchedDocument,
    ISearchListItem,
    ISearchResponse,
    WorkflowFileStatus,
    WorkflowFileStatuses,
} from '@red/domain';
import { FilesMapService } from '@services/files/files-map.service';
import { PlatformSearchService } from '@services/entity-services/platform-search.service';
import { FeaturesService } from '@services/features.service';
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { UserService } from '@users/user.service';
import { IFilterGroup, keyChecker, NestedFilter } from '@iqser/common-ui/lib/filtering';
import { SortingOrders } from '@iqser/common-ui/lib/sorting';

@Component({
    templateUrl: './search-screen.component.html',
    styleUrls: ['./search-screen.component.scss'],
    providers: listingProvidersFactory(SearchScreenComponent),
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SearchScreenComponent extends ListingComponent<ISearchListItem> implements OnDestroy {
    readonly searchPositions = SearchPositions;

    readonly tableHeaderLabel = _('search-screen.table-header');
    readonly tableColumnConfigs: TableColumnConfig<ISearchListItem>[] = [
        { label: _('search-screen.cols.document'), width: '2fr' },
        { label: _('search-screen.cols.assignee') },
        { label: _('search-screen.cols.status') },
        { label: _('search-screen.cols.dossier') },
        { label: _('search-screen.cols.pages'), width: 'auto' },
    ];

    readonly searchResults$ = new Observable<ISearchListItem[]>();

    readonly dossierTemplates$ = this._dossierTemplateService.loadAll().pipe(tap(templates => this._addTemplateFilter(templates)));

    constructor(
        private readonly _router: Router,
        private readonly _activatedRoute: ActivatedRoute,
        private readonly _loadingService: LoadingService,
        private readonly _dossiersCacheService: DossiersCacheService,
        readonly routerHistoryService: RouterHistoryService,
        private readonly _translateService: TranslateService,
        private readonly _filesMapService: FilesMapService,
        private readonly _platformSearchService: PlatformSearchService,
        private readonly _featuresService: FeaturesService,
        private readonly _dossierTemplateService: DossierTemplatesService,
        private readonly _userService: UserService,
    ) {
        super();
        if (!Object.keys(this._activatedRoute.snapshot.queryParams).length) {
            this._router.navigate(['main']).then();
        }
        this.searchResults$ = combineLatest([this._queryChanged$, this._filtersChanged$]).pipe(
            tap(() => this._loadingService.start()),
            tap(([query, [dossierIds, workflowStatus, assignee, dossierTemplateIds, onlyActive]]) =>
                this._updateNavigation(query, dossierIds, workflowStatus, assignee, dossierTemplateIds, onlyActive),
            ),
            switchMap(([query, [dossierIds, workflowStatus, assignee, dossierTemplateIds, onlyActive]]) =>
                this._platformSearchService.search({
                    query,
                    dossierIds,
                    dossierTemplateIds,
                    workflowStatus,
                    assignee,
                    includeDeletedDossiers: false,
                    includeArchivedDossiers: !onlyActive,
                }),
            ),
            map(searchResult => this._toMatchedDocuments(searchResult)),
            map(docs => this._toListItems(docs)),
            tap(result => this.entitiesService.setEntities(result)),
            tap(() => this._loadingService.stop()),
        );
        this.searchService.skip = true;
        this.sortingService.setSortingOption({ column: 'searchKey', order: SortingOrders.desc });
        this._initFilters();
    }

    private get _routeDossierIds(): string[] {
        return this._activatedRoute.snapshot.queryParamMap.get('dossierIds').split(',');
    }

    private get _routeDossierTemplateIds(): string[] {
        return this._activatedRoute.snapshot.queryParamMap.get('dossierTemplateIds')?.split(',');
    }

    private get _routeStatus(): WorkflowFileStatus {
        return this._activatedRoute.snapshot.queryParamMap.get('status') as WorkflowFileStatus;
    }

    private get _routeAssignee(): string {
        return this._activatedRoute.snapshot.queryParamMap.get('assignee');
    }

    private get _routeOnlyActive(): boolean {
        return this._activatedRoute.snapshot.queryParamMap.get('onlyActive') === 'true';
    }

    private get _routeQuery(): string {
        return this._activatedRoute.snapshot.queryParamMap.get('query');
    }

    private get _queryChanged$(): Observable<string> {
        return this.searchService.valueChanges$.pipe(
            startWith(this._routeQuery),
            tap(query => (this.searchService.searchValue = query)),
            debounceTime(300),
        );
    }

    get #enabledArchive(): boolean {
        return this._featuresService.isEnabled(DOSSIERS_ARCHIVE);
    }

    private get _filtersChanged$(): Observable<[string[], WorkflowFileStatus, string, string[], boolean]> {
        const onlyActiveDossiers$ = this.#enabledArchive
            ? this.filterService.getSingleFilter('onlyActiveDossiers').pipe(map(f => !!f.checked))
            : of(true);
        const filterGroups$ = this.filterService.filterGroups$;
        return combineLatest([filterGroups$, onlyActiveDossiers$]).pipe(
            map(([groups, onlyActive]) => {
                const dossierIds: string[] = groups[0].filters.filter(v => v.checked).map(v => v.id);
                const workflowStatus: WorkflowFileStatus = groups[1].filters.filter(v => v.checked).map(v => v.id)[0] as WorkflowFileStatus;
                const assignee: string = groups[2].filters.filter(v => v.checked).map(v => v.id)[0];
                const dossierTemplateIds: string[] = groups[3]?.filters.filter(v => v.checked).map(v => v.id);
                return [dossierIds, workflowStatus, assignee, dossierTemplateIds, onlyActive];
            }),
            startWith<[string[], WorkflowFileStatus, string, string[], boolean]>([
                this._routeDossierIds,
                this._routeStatus,
                this._routeAssignee,
                this._routeDossierTemplateIds,
                this._routeOnlyActive,
            ]),
        );
    }

    mustContain(value: string) {
        const newQuery = this.searchService.searchValue.replace(value, `"${value}"`);
        this.searchService.searchValue = newQuery ?? '';
    }

    private _initFilters() {
        const dossierIds = this._routeDossierIds;
        const dossierToFilter = ({ dossierName, id }: Dossier) => {
            const checked = dossierIds.includes(id);
            return new NestedFilter({ id, label: dossierName, checked });
        };
        const dossierNameFilter: IFilterGroup = {
            slug: 'dossiers',
            label: this._translateService.instant('search-screen.filters.by-dossier'),
            filterceptionPlaceholder: this._translateService.instant('search-screen.filters.search-placeholder'),
            icon: 'red:folder',
            filters: this._dossiersCacheService.all.map(dossierToFilter),
            checker: keyChecker('dossierId'),
        };

        const status = this._routeStatus;
        const statusToFilter = (workflowStatus: WorkflowFileStatus) => {
            const checked = status === workflowStatus;
            return new NestedFilter({
                id: workflowStatus,
                label: this._translateService.instant(workflowFileStatusTranslations[workflowStatus]),
                checked,
            });
        };
        const workflowStatusFilter: IFilterGroup = {
            slug: 'status',
            label: this._translateService.instant('search-screen.filters.status'),
            icon: 'red:status',
            filters: Object.values(WorkflowFileStatuses).map(statusToFilter),
            singleSelect: true,
            checker: keyChecker('status'),
        };

        const assignee = this._routeAssignee;
        const assigneeToFilter = (userId: string) => {
            const checked = assignee === userId;
            return new NestedFilter({ id: userId, label: this._userService.getName(userId), checked });
        };
        const assigneeFilter: IFilterGroup = {
            slug: 'assignee',
            label: this._translateService.instant('search-screen.filters.assignee'),
            icon: 'red:user',
            filters: this._userService.all
                .filter(user => user.isUser)
                .map(user => user.id)
                .map(assigneeToFilter),
            singleSelect: true,
            checker: keyChecker('assignee'),
        };

        assigneeFilter.filters.push(
            new NestedFilter({
                id: null,
                label: this._translateService.instant('initials-avatar.unassigned'),
            }),
        );

        this.filterService.addFilterGroups([dossierNameFilter, workflowStatusFilter, assigneeFilter]);
        const onlyActiveLabel = this._translateService.instant('search-screen.filters.only-active');
        if (this.#enabledArchive) {
            this.filterService.addSingleFilter({ id: 'onlyActiveDossiers', label: onlyActiveLabel, checked: this._routeOnlyActive });
        }
    }

    private _addTemplateFilter(templates: DossierTemplate[]) {
        const templatesIds = this._routeDossierTemplateIds;
        const templateToFilter = ({ name, id }: DossierTemplate) => {
            const checked = templatesIds?.includes(id);
            return new NestedFilter({ id, label: name, checked });
        };
        const templateNameFilter: IFilterGroup = {
            slug: 'templates',
            label: this._translateService.instant('search-screen.filters.by-template'),
            filterceptionPlaceholder: this._translateService.instant('search-screen.filters.search-by-template-placeholder'),
            icon: 'red:template',
            filters: templates.map(templateToFilter),
            checker: keyChecker('dossierTemplateId'),
        };
        this.filterService.addFilterGroups([templateNameFilter]);
    }

    private _updateNavigation(
        query?: string,
        dossierIds?: string[],
        workflowStatus?: WorkflowFileStatus,
        assignee?: string,
        dossierTemplateIds?: string[],
        onlyActive?: boolean,
    ): Promise<boolean> {
        const queryParams = {
            query,
            dossierIds: dossierIds.join(','),
            dossierTemplateIds: dossierTemplateIds?.join(','),
            workflowStatus,
            assignee,
            onlyActive,
        };
        return this._router.navigate([], { queryParams, replaceUrl: true });
    }

    private _toMatchedDocuments({ matchedDocuments }: ISearchResponse): IMatchedDocument[] {
        return matchedDocuments.filter(doc => doc.score > 0 && doc.matchedTerms.length > 0);
    }

    private _toListItems(matchedDocuments: IMatchedDocument[]): ISearchListItem[] {
        return matchedDocuments.map(document => this._toListItem(document)).filter(value => value);
    }

    private _toListItem({
        dossierId,
        fileId,
        unmatchedTerms,
        highlights,
        score,
        dossierDeleted,
        dossierArchived,
    }: IMatchedDocument): ISearchListItem {
        const file = this._filesMapService.get(dossierId, fileId);
        if (!file) {
            return undefined;
        }

        const dossier = this._dossiersCacheService.get(dossierId);
        if (!dossier) {
            return undefined;
        }

        return {
            id: fileId,
            dossierId,
            dossierTemplateId: file.dossierTemplateId,
            deleted: dossierDeleted,
            archived: dossierArchived,
            unmatched: unmatchedTerms || null,
            highlights,
            status: file.workflowStatus,
            assignee: file.assignee,
            numberOfPages: file.numberOfPages,
            dossierName: dossier.dossierName,
            filename: file.filename,
            searchKey: score.toString(),
            routerLink: file.routerLink,
        };
    }
}
