import { Injectable } from '@angular/core';
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
import { BehaviorSubject, combineLatest, merge, Observable, Subject, timer } from 'rxjs';
import { distinctUntilChanged, filter, first, map, shareReplay, switchMap } from 'rxjs/operators';

import { AppConfig, EditionLevelType } from '../app.config';
import { Page, ViewMode } from '../models/evt-models';
import { EVTModelService } from './evt-model.service';

export type URLParamsKeys = 'd' | 'p' | 'el' | 'ws' | 'vs';
export type URLParams = { [T in URLParamsKeys]: string };

@Injectable({
    providedIn: 'root',
})
export class EVTStatusService {
    public availableEditionLevels = AppConfig.evtSettings.edition.availableEditionLevels?.filter((e => !e.disabled)) || [];
    get defaultEditionLevelId(): EditionLevelType {
        const defaultConfig = AppConfig.evtSettings.edition.defaultEdition;
        const availableEditionLevels = AppConfig.evtSettings.edition.availableEditionLevels?.filter((e => !e.disabled)) ?? [];
        let defaultEdition = availableEditionLevels[0];
        if (defaultConfig) {
            defaultEdition = availableEditionLevels.find(e => e.id === defaultConfig) ?? defaultEdition;
        }

        return defaultEdition?.id;
    }

    get availableViewModes() {
        return AppConfig.evtSettings.edition.availableViewModes?.filter((e => !e.disabled)) ?? [];
    }
    get defaultViewMode(): ViewMode {
        const defaultConfig = AppConfig.evtSettings.edition.defaultViewMode;
        let defaultViewMode = this.availableViewModes[0];
        if (defaultConfig) {
            defaultViewMode = this.availableViewModes.find(e => e.id === defaultConfig) ?? defaultViewMode;
        }

        return defaultViewMode;
    }
    public updateViewMode$: BehaviorSubject<ViewMode> = new BehaviorSubject(undefined);
    public updateDocument$: BehaviorSubject<string> = new BehaviorSubject('');
    public updatePage$: Subject<Page> = new Subject();
    public updateEditionLevels$: Subject<EditionLevelType[]> = new Subject();
    public updateWitnesses$: BehaviorSubject<string[]> = new BehaviorSubject([]);
    public updateVersions$: BehaviorSubject<string[]> = new BehaviorSubject([]);

    public currentViewMode$ = this.updateViewMode$.asObservable();
    public currentDocument$ = merge(
        this.route.queryParams.pipe(map((params: URLParams) => params.d)),
        this.updateDocument$,
    );
    public currentPage$ = combineLatest([
        merge(
            this.route.queryParams.pipe(map((params: URLParams) => params.p)),
            this.updatePage$.pipe(map(p => p.id)),
        ),
        this.evtModelService.pages$.pipe(
            filter((pages) => !!pages && pages.length > 0),
        ),
    ]).pipe(
        map(([id, pages]) => !id ? pages[0] : pages.find((p) => p.id === id) || pages[0]),
    );
    public currentEditionLevels$ = merge(
        this.route.queryParams.pipe(
            map((params: URLParams) => (params.el?.split(',') ?? [])),
            map((editionLevels) => editionLevels?.length > 0 ? editionLevels : [this.defaultEditionLevelId]),
            map((editionLevels: EditionLevelType[]) => editionLevels.filter(el => !!el)),
        ),
        this.updateEditionLevels$,
    );
    public currentWitnesses$ = merge(
        this.route.queryParams.pipe(map((params: URLParams) => params.ws?.split(',') ?? [])),
        this.updateWitnesses$,
    );
    public currentVersions$ = merge(
        this.route.queryParams.pipe(map((params: URLParams) => params.vs?.split(',') ?? [])),
        this.updateVersions$,
    );

    public currentStatus$: Observable<AppStatus> = combineLatest([
        this.updateViewMode$,
        this.currentDocument$,
        this.currentPage$,
        this.currentEditionLevels$,
        this.currentWitnesses$,
        this.currentVersions$,
    ]).pipe(
        distinctUntilChanged((x, y) => JSON.stringify(x) === JSON.stringify(y)),
        shareReplay(1),
        map(([
            viewMode,
            document,
            page,
            editionLevels,
            witnesses,
            versions,
        ]) => {
            if (viewMode.id === 'textText') {
                if (editionLevels.length === 1) {
                    editionLevels.push(this.availableEditionLevels.filter(e => e.id !== editionLevels[0])[0].id);
                }
            } else if (viewMode.id === 'collation') {
                editionLevels = [];
            } else if (editionLevels.length > 1) {
                editionLevels = editionLevels.slice(0, 1);
            }

            return {
                viewMode,
                document,
                page,
                editionLevels,
                witnesses,
                versions,
            };
        }),
    );

    public currentUrl$: Observable<{ view: string; params: URLParams }> = this.currentStatus$.pipe(
        map(currentStatus => this.getUrlFromStatus(currentStatus)),
    );

    public currentNamedEntityId$: BehaviorSubject<string> = new BehaviorSubject(undefined);

    constructor(
        private evtModelService: EVTModelService,
        private router: Router,
        private route: ActivatedRoute,
    ) {
        this.currentStatus$.subscribe(currentStatus => {
            const { view, params } = this.getUrlFromStatus(currentStatus);
            if (Object.keys(params).length > 0) {
                this.router.navigate([`/${view}`], { queryParams: params });
            } else {
                this.router.navigate([`/${view}`]);
            }
        });
        this.router.events.pipe(
            filter(event => event instanceof NavigationStart),
            first(),
        ).subscribe((event: NavigationStart) => {
            const currentViewMode = this.updateViewMode$.getValue();
            if (!currentViewMode) {
                const pathMatch = event.url.match(/(?<!\?.+)(?<=\/)[\w-]+(?=[/\r\n?]|$)/gm);
                if (pathMatch) {
                    this.updateViewMode$.next(this.availableViewModes.find(vm => vm.id === pathMatch[0]));
                } else {
                    this.updateViewMode$.next(this.defaultViewMode);
                }
            }
        });
        this.currentNamedEntityId$.pipe(
            filter(id => !!id),
            switchMap(id => timer(5000).pipe(map(() => id))),
        ).subscribe(() => this.currentNamedEntityId$.next(undefined));
    }

    getUrlFromStatus(status: AppStatus) {
        const params = {
            d: status.document || '',
            p: status.page?.id ?? '',
            el: status.editionLevels.join(','),
            ws: status.witnesses.join(','),
            vs: status.versions.join(','),
        };
        Object.keys(params).forEach((key) => (params[key] === '') && delete params[key]);

        return {
            view: status.viewMode.id,
            params,
        };
    }
}

export interface AppStatus {
    viewMode: ViewMode;
    document: string;
    page: Page;
    editionLevels: EditionLevelType[];
    witnesses: string[];
    versions: string[];
}
