// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { AddonModGlossaryHelper } from '@addons/mod/glossary/services/glossary-helper';
import { AddonModGlossaryOffline, AddonModGlossaryOfflineEntry } from '@addons/mod/glossary/services/glossary-offline';
import { Component, OnDestroy, OnInit, Optional, ViewChild } from '@angular/core';
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreCommentsCommentsComponent } from '@features/comments/components/comments/comments';
import { CoreComments } from '@features/comments/services/comments';
import { CoreRatingInfo } from '@features/rating/services/rating';
import { CoreTag } from '@features/tag/services/tag';
import { FileEntry } from '@awesome-cordova-plugins/file/ngx';
import { CoreNavigator } from '@services/navigator';
import { CoreNetwork } from '@services/network';
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { AddonModGlossaryEntriesSource, AddonModGlossaryEntryItem } from '../../classes/glossary-entries-source';
import {
    AddonModGlossary,
    AddonModGlossaryEntry,
    AddonModGlossaryGlossary,
    AddonModGlossaryProvider,
    GLOSSARY_ENTRY_UPDATED,
} from '../../services/glossary';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';

/**
 * Page that displays a glossary entry.
 */
@Component({
    selector: 'page-addon-mod-glossary-entry',
    templateUrl: 'entry.html',
})
export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {

    @ViewChild(CoreCommentsCommentsComponent) comments?: CoreCommentsCommentsComponent;

    component = AddonModGlossaryProvider.COMPONENT;
    componentId?: number;
    onlineEntry?: AddonModGlossaryEntry;
    offlineEntry?: AddonModGlossaryOfflineEntry;
    offlineEntryFiles?: FileEntry[];
    entries!: AddonModGlossaryEntryEntriesSwipeManager;
    glossary?: AddonModGlossaryGlossary;
    entryUpdatedObserver?: CoreEventObserver;
    loaded = false;
    showAuthor = false;
    showDate = false;
    ratingInfo?: CoreRatingInfo;
    tagsEnabled = false;
    canEdit = false;
    canDelete = false;
    commentsEnabled = false;
    courseId!: number;
    cmId!: number;

    protected logView: () => void;

    constructor(@Optional() protected splitView: CoreSplitViewComponent, protected route: ActivatedRoute) {
        this.logView = CoreTime.once(async () => {
            if (!this.onlineEntry || !this.glossary || !this.componentId) {
                return;
            }

            await CoreUtils.ignoreErrors(AddonModGlossary.logEntryView(this.onlineEntry.id, this.componentId));

            this.analyticsLogEvent('mod_glossary_get_entry_by_id', `/mod/glossary/showentry.php?eid=${this.onlineEntry.id}`);
        });
    }

    get entry(): AddonModGlossaryEntry | AddonModGlossaryOfflineEntry | undefined {
        return this.onlineEntry ?? this.offlineEntry;
    }

    /**
     * @inheritdoc
     */
    async ngOnInit(): Promise<void> {
        let onlineEntryId: number | null = null;
        let offlineEntryTimeCreated: number | null = null;

        try {
            this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
            this.tagsEnabled = CoreTag.areTagsAvailableInSite();
            this.commentsEnabled = CoreComments.areCommentsEnabledInSite();
            this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');

            const entrySlug = CoreNavigator.getRequiredRouteParam<string>('entrySlug');
            const routeData = this.route.snapshot.data;
            const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
                AddonModGlossaryEntriesSource,
                [this.courseId, this.cmId, routeData.glossaryPathPrefix ?? ''],
            );

            this.entries = new AddonModGlossaryEntryEntriesSwipeManager(source);

            await this.entries.start();

            if (entrySlug.startsWith('new-')) {
                offlineEntryTimeCreated = Number(entrySlug.slice(4));
            } else {
                onlineEntryId = Number(entrySlug);
            }
        } catch (error) {
            CoreDomUtils.showErrorModal(error);
            CoreNavigator.back();

            return;
        }

        this.entryUpdatedObserver = CoreEvents.on(GLOSSARY_ENTRY_UPDATED, data => {
            if (data.glossaryId !== this.glossary?.id) {
                return;
            }

            if (
                (this.onlineEntry && this.onlineEntry.id === data.entryId) ||
                (this.offlineEntry && this.offlineEntry.timecreated === data.timecreated)
            ) {
                this.doRefresh();
            }
        });

        try {
            if (onlineEntryId) {
                await this.loadOnlineEntry(onlineEntryId);
            } else if (offlineEntryTimeCreated) {
                await this.loadOfflineEntry(offlineEntryTimeCreated);
            }
        } finally {
            this.loaded = true;
        }
    }

    /**
     * @inheritdoc
     */
    ngOnDestroy(): void {
        this.entries.destroy();
        this.entryUpdatedObserver?.off();
    }

    /**
     * Edit entry.
     */
    async editEntry(): Promise<void> {
        await CoreNavigator.navigate('./edit');
    }

    /**
     * Delete entry.
     */
    async deleteEntry(): Promise<void> {
        // Log analytics even if the user cancels for consistency with LMS.
        this.analyticsLogEvent(
            'mod_glossary_delete_entry',
            `/mod/glossary/deleteentry.php?id=${this.glossary?.id}&mode=delete&entry=${this.onlineEntry?.id}`,
        );

        const glossaryId = this.glossary?.id;
        const cancelled = await CoreUtils.promiseFails(
            CoreDomUtils.showConfirm(Translate.instant('addon.mod_glossary.areyousuredelete')),
        );

        if (!glossaryId || cancelled) {
            return;
        }

        const modal = await CoreDomUtils.showModalLoading();

        try {
            if (this.onlineEntry) {
                const entryId = this.onlineEntry.id;

                await AddonModGlossary.deleteEntry(glossaryId, entryId);
                await Promise.all([
                    CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntry(entryId)),
                    CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntriesByLetter(glossaryId)),
                    CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntriesByAuthor(glossaryId)),
                    CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntriesByCategory(glossaryId)),
                    CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntriesByDate(glossaryId, 'CREATION')),
                    CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntriesByDate(glossaryId, 'UPDATE')),
                    CoreUtils.ignoreErrors(this.entries.getSource().invalidateCache(false)),
                ]);
            } else if (this.offlineEntry) {
                const concept = this.offlineEntry.concept;
                const timecreated = this.offlineEntry.timecreated;

                await AddonModGlossaryOffline.deleteOfflineEntry(glossaryId, timecreated);
                await AddonModGlossaryHelper.deleteStoredFiles(glossaryId, concept, timecreated);
            }

            CoreDomUtils.showToast('addon.mod_glossary.entrydeleted', true, ToastDuration.LONG);

            if (this.splitView?.outletActivated) {
                await CoreNavigator.navigate('../../');
            } else {
                await CoreNavigator.back();
            }
        } catch (error) {
            CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errordeleting', true);
        } finally {
            modal.dismiss();
        }
    }

    /**
     * Refresh the data.
     *
     * @param refresher Refresher.
     * @returns Promise resolved when done.
     */
    async doRefresh(refresher?: HTMLIonRefresherElement): Promise<void> {
        if (this.onlineEntry && this.glossary?.allowcomments && this.onlineEntry.id > 0 && this.commentsEnabled && this.comments) {
            // Refresh comments asynchronously (without blocking the current promise).
            CoreUtils.ignoreErrors(this.comments.doRefresh());
        }

        try {
            if (this.onlineEntry) {
                await CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntry(this.onlineEntry.id));
                await this.loadOnlineEntry(this.onlineEntry.id);
            } else if (this.offlineEntry) {
                const entrySlug = CoreNavigator.getRequiredRouteParam<string>('entrySlug');
                const timecreated = Number(entrySlug.slice(4));

                await this.loadOfflineEntry(timecreated);
            }
        } finally {
            refresher?.complete();
        }
    }

    /**
     * Load online entry data.
     */
    protected async loadOnlineEntry(entryId: number): Promise<void> {
        try {
            const result = await AddonModGlossary.getEntry(entryId);
            const canDeleteEntries = CoreNetwork.isOnline() && await AddonModGlossary.canDeleteEntries();
            const canUpdateEntries = CoreNetwork.isOnline() && await AddonModGlossary.canUpdateEntries();

            this.onlineEntry = result.entry;
            this.ratingInfo = result.ratinginfo;
            this.canDelete = canDeleteEntries && !!result.permissions?.candelete;
            this.canEdit = canUpdateEntries && !!result.permissions?.canupdate;

            await this.loadGlossary();

            this.logView();
        } catch (error) {
            CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true);
        }
    }

    /**
     * Load offline entry data.
     *
     * @param timecreated Entry Timecreated.
     */
    protected async loadOfflineEntry(timecreated: number): Promise<void> {
        try {
            const glossary = await this.loadGlossary();

            this.offlineEntry = await AddonModGlossaryOffline.getOfflineEntry(glossary.id, timecreated);
            this.offlineEntryFiles = this.offlineEntry.attachments && this.offlineEntry.attachments.offline > 0
                ? await AddonModGlossaryHelper.getStoredFiles(
                    glossary.id,
                    this.offlineEntry.concept,
                    timecreated,
                )
                : undefined;
            this.canEdit = true;
            this.canDelete = true;
        } catch (error) {
            CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true);
        }
    }

    /**
     * Load glossary data.
     *
     * @returns Glossary.
     */
    protected async loadGlossary(): Promise<AddonModGlossaryGlossary> {
        if (this.glossary) {
            return this.glossary;
        }

        this.glossary = await AddonModGlossary.getGlossary(this.courseId, this.cmId);
        this.componentId = this.glossary.coursemodule;

        switch (this.glossary.displayformat) {
            case 'fullwithauthor':
            case 'encyclopedia':
                this.showAuthor = true;
                this.showDate = true;
                break;
            case 'fullwithoutauthor':
                this.showAuthor = false;
                this.showDate = true;
                break;
            default: // Default, and faq, simple, entrylist, continuous.
                this.showAuthor = false;
                this.showDate = false;
        }

        return this.glossary;
    }

    /**
     * Function called when rating is updated online.
     */
    ratingUpdated(): void {
        if (!this.onlineEntry) {
            return;
        }

        AddonModGlossary.invalidateEntry(this.onlineEntry.id);
    }

    /**
     * Log analytics event.
     *
     * @param wsName WS name.
     * @param url URL.
     */
    protected analyticsLogEvent(wsName: string, url: string): void {
        if (!this.onlineEntry || !this.glossary) {
            return;
        }

        CoreAnalytics.logEvent({
            type: CoreAnalyticsEventType.VIEW_ITEM,
            ws: wsName,
            name: this.onlineEntry.concept,
            data: { id: this.onlineEntry.id, glossaryid: this.glossary.id, category: 'glossary' },
            url,
        });
    }

}

/**
 * Helper to manage swiping within a collection of glossary entries.
 */
class AddonModGlossaryEntryEntriesSwipeManager
    extends CoreSwipeNavigationItemsManager<AddonModGlossaryEntryItem, AddonModGlossaryEntriesSource> {

    /**
     * @inheritdoc
     */
    protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
        const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;

        return `${this.getSource().GLOSSARY_PATH_PREFIX}entry/${snapshot.params.entrySlug}`;
    }

}
