// (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 { Component, Optional, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Params } from '@angular/router';
import { CoreSite } from '@classes/site';
import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component';
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
import { CoreCourse } from '@features/course/services/course';
import { IonContent } from '@ionic/angular';
import { CoreGroupInfo, CoreGroups } from '@services/groups';
import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import {
    AddonModAssign,
    AddonModAssignAssign,
    AddonModAssignProvider,
    AddonModAssignSubmissionGradingSummary,
} from '../../services/assign';
import { AddonModAssignOffline } from '../../services/assign-offline';
import {
    AddonModAssignAutoSyncData,
    AddonModAssignSync,
    AddonModAssignSyncProvider,
    AddonModAssignSyncResult,
} from '../../services/assign-sync';
import { AddonModAssignModuleHandlerService } from '../../services/handlers/module';
import { AddonModAssignSubmissionComponent } from '../submission/submission';

/**
 * Component that displays an assignment.
 */
@Component({
    selector: 'addon-mod-assign-index',
    templateUrl: 'addon-mod-assign-index.html',
})
export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy {

   @ViewChild(AddonModAssignSubmissionComponent) submissionComponent?: AddonModAssignSubmissionComponent;

    component = AddonModAssignProvider.COMPONENT;
    moduleName = 'assign';

    assign?: AddonModAssignAssign; // The assign object.
    canViewAllSubmissions = false; // Whether the user can view all submissions.
    canViewOwnSubmission = false; // Whether the user can view their own submission.
    timeRemaining?: string; // Message about time remaining to submit.
    lateSubmissions?: string; // Message about late submissions.
    showNumbers = true; // Whether to show number of submissions with each status.
    summary?: AddonModAssignSubmissionGradingSummary; // The grading summary.
    needsGradingAvalaible = false; // Whether we can see the submissions that need grading.

    groupInfo: CoreGroupInfo = {
        groups: [],
        separateGroups: false,
        visibleGroups: false,
        defaultGroupId: 0,
    };

    // Status.
    submissionStatusSubmitted = AddonModAssignProvider.SUBMISSION_STATUS_SUBMITTED;
    submissionStatusDraft = AddonModAssignProvider.SUBMISSION_STATUS_DRAFT;
    needGrading = AddonModAssignProvider.NEED_GRADING;

    protected currentUserId?: number; // Current user ID.
    protected currentSite?: CoreSite; // Current user ID.
    protected syncEventName = AddonModAssignSyncProvider.AUTO_SYNCED;

    // Observers.
    protected savedObserver?: CoreEventObserver;
    protected submittedObserver?: CoreEventObserver;
    protected gradedObserver?: CoreEventObserver;

    constructor(
        protected content?: IonContent,
        @Optional() courseContentsPage?: CoreCourseContentsPage,
    ) {
        super('AddonModLessonIndexComponent', content, courseContentsPage);
    }

    /**
     * Component being initialized.
     */
    async ngOnInit(): Promise<void> {
        super.ngOnInit();

        this.currentUserId = CoreSites.getCurrentSiteUserId();
        this.currentSite = CoreSites.getCurrentSite();

        // Listen to events.
        this.savedObserver = CoreEvents.on(
            AddonModAssignProvider.SUBMISSION_SAVED_EVENT,
            (data) => {
                if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
                // Assignment submission saved, refresh data.
                    this.showLoadingAndRefresh(true, false);
                }
            },
            this.siteId,
        );

        this.submittedObserver = CoreEvents.on(
            AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT,
            (data) => {
                if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
                // Assignment submitted, check completion.
                    CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);

                    // Reload data since it can have offline data now.
                    this.showLoadingAndRefresh(true, false);
                }
            },
            this.siteId,
        );

        this.gradedObserver = CoreEvents.on(AddonModAssignProvider.GRADED_EVENT, (data) => {
            if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
                // Assignment graded, refresh data.
                this.showLoadingAndRefresh(true, false);
            }
        }, this.siteId);

        await this.loadContent(false, true);

        try {
            await AddonModAssign.logView(this.assign!.id, this.assign!.name);
            CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
        } catch {
            // Ignore errors. Just don't check Module completion.
        }

        if (this.canViewAllSubmissions) {
            // User can see all submissions, log grading view.
            CoreUtils.ignoreErrors(AddonModAssign.logGradingView(this.assign!.id, this.assign!.name));
        } else if (this.canViewOwnSubmission) {
            // User can only see their own submission, log view the user submission.
            CoreUtils.ignoreErrors(AddonModAssign.logSubmissionView(this.assign!.id, this.assign!.name));
        }
    }

    /**
     * Expand the description.
     */
    expandDescription(ev?: Event): void {
        ev?.preventDefault();
        ev?.stopPropagation();

        if (this.assign && (this.description || this.assign.introattachments)) {
            CoreTextUtils.viewText(Translate.instant('core.description'), this.description || '', {
                component: this.component,
                componentId: this.module.id,
                files: this.assign.introattachments,
                filter: true,
                contextLevel: 'module',
                instanceId: this.module.id,
                courseId: this.courseId,
            });
        }
    }

    /**
     * Get assignment data.
     *
     * @param refresh If it's refreshing content.
     * @param sync If it should try to sync.
     * @param showErrors If show errors to the user of hide them.
     * @return Promise resolved when done.
     */
    protected async fetchContent(refresh = false, sync = false, showErrors = false): Promise<void> {

        // Get assignment data.
        try {
            this.assign = await AddonModAssign.getAssignment(this.courseId, this.module.id);

            this.dataRetrieved.emit(this.assign);
            this.description = this.assign.intro;

            if (sync) {
                // Try to synchronize the assign.
                await CoreUtils.ignoreErrors(this.syncActivity(showErrors));
            }

            // Check if there's any offline data for this assign.
            this.hasOffline = await AddonModAssignOffline.hasAssignOfflineData(this.assign.id);

            // Get assignment submissions.
            const submissions = await AddonModAssign.getSubmissions(this.assign.id, { cmId: this.module.id });
            const time = CoreTimeUtils.timestamp();

            this.canViewAllSubmissions = submissions.canviewsubmissions;

            if (submissions.canviewsubmissions) {

                // Calculate the messages to display about time remaining and late submissions.
                if (this.assign.duedate > 0) {
                    if (this.assign.duedate - time <= 0) {
                        this.timeRemaining = Translate.instant('addon.mod_assign.assignmentisdue');
                    } else {
                        this.timeRemaining = CoreTimeUtils.formatDuration(this.assign.duedate - time, 3);

                        if (this.assign.cutoffdate) {
                            if (this.assign.cutoffdate > time) {
                                this.lateSubmissions = Translate.instant(
                                    'addon.mod_assign.latesubmissionsaccepted',
                                    { $a: CoreTimeUtils.userDate(this.assign.cutoffdate * 1000) },
                                );
                            } else {
                                this.lateSubmissions = Translate.instant('addon.mod_assign.nomoresubmissionsaccepted');
                            }
                        } else {
                            this.lateSubmissions = '';
                        }
                    }
                } else {
                    this.timeRemaining = '';
                    this.lateSubmissions = '';
                }

                // Check if groupmode is enabled to avoid showing wrong numbers.
                this.groupInfo = await CoreGroups.getActivityGroupInfo(this.assign.cmid, false);
                this.showNumbers = (this.groupInfo.groups && this.groupInfo.groups.length == 0) ||
                    this.currentSite!.isVersionGreaterEqualThan('3.5');

                await this.setGroup(CoreGroups.validateGroupId(this.group, this.groupInfo));

                return;
            }

            try {
                // Check if the user can view their own submission.
                await AddonModAssign.getSubmissionStatus(this.assign.id, { cmId: this.module.id });
                this.canViewOwnSubmission = true;
            } catch (error) {
                this.canViewOwnSubmission = false;

                if (error.errorcode !== 'nopermission') {
                    throw error;
                }
            }
        } finally {
            this.fillContextMenu(refresh);
        }
    }

    /**
     * Set group to see the summary.
     *
     * @param groupId Group ID.
     * @return Resolved when done.
     */
    async setGroup(groupId = 0): Promise<void> {
        this.group = groupId;

        const submissionStatus = await AddonModAssign.getSubmissionStatus(this.assign!.id, {
            groupId: this.group,
            cmId: this.module.id,
        });

        this.summary = submissionStatus.gradingsummary;
        if (!this.summary) {
            this.needsGradingAvalaible = false;

            return;
        }

        if (this.summary?.warnofungroupedusers === true) {
            this.summary.warnofungroupedusers = 'ungroupedusers';
        } else {
            switch (this.summary?.warnofungroupedusers) {
                case AddonModAssignProvider.WARN_GROUPS_REQUIRED:
                    this.summary.warnofungroupedusers = 'ungroupedusers';
                    break;
                case AddonModAssignProvider.WARN_GROUPS_OPTIONAL:
                    this.summary.warnofungroupedusers = 'ungroupedusersoptional';
                    break;
                default:
                    this.summary.warnofungroupedusers = '';
                    break;
            }
        }

        this.needsGradingAvalaible =
            (submissionStatus.gradingsummary?.submissionsneedgradingcount || 0) > 0 &&
            this.currentSite!.isVersionGreaterEqualThan('3.2');
    }

    /**
     * Go to view a list of submissions.
     *
     * @param status Status to see.
     * @param hasSubmissions If the status has any submission.
     */
    goToSubmissionList(status?: string, hasSubmissions = false): void {
        if (typeof status != 'undefined' && !hasSubmissions && this.showNumbers) {
            return;
        }

        const params: Params = {
            groupId: this.group || 0,
            moduleName: this.moduleName,
        };
        if (typeof status != 'undefined') {
            params.status = status;
        }

        CoreNavigator.navigateToSitePath(
            AddonModAssignModuleHandlerService.PAGE_NAME + `/${this.courseId}/${this.module.id}/submission`,
            {
                params,
            },
        );
    }

    /**
     * Checks if sync has succeed from result sync data.
     *
     * @param result Data returned by the sync function.
     * @return If succeed or not.
     */
    protected hasSyncSucceed(result: AddonModAssignSyncResult): boolean {
        if (result.updated) {
            this.submissionComponent?.invalidateAndRefresh(false);
        }

        return result.updated;
    }

    /**
     * Perform the invalidate content function.
     *
     * @return Resolved when done.
     */
    protected async invalidateContent(): Promise<void> {
        const promises: Promise<void>[] = [];

        promises.push(AddonModAssign.invalidateAssignmentData(this.courseId));

        if (this.assign) {
            promises.push(AddonModAssign.invalidateAllSubmissionData(this.assign.id));

            if (this.canViewAllSubmissions) {
                promises.push(AddonModAssign.invalidateSubmissionStatusData(this.assign.id, undefined, this.group));
            }
        }

        await Promise.all(promises).finally(() => {
            this.submissionComponent?.invalidateAndRefresh(true);
        });
    }

    /**
     * User entered the page that contains the component.
     */
    ionViewDidEnter(): void {
        super.ionViewDidEnter();

        this.submissionComponent?.ionViewDidEnter();
    }

    /**
     * User left the page that contains the component.
     */
    ionViewDidLeave(): void {
        super.ionViewDidLeave();

        this.submissionComponent?.ionViewDidLeave();
    }

    /**
     * Compares sync event data with current data to check if refresh content is needed.
     *
     * @param syncEventData Data receiven on sync observer.
     * @return True if refresh is needed, false otherwise.
     */
    protected isRefreshSyncNeeded(syncEventData: AddonModAssignAutoSyncData): boolean {
        if (this.assign && syncEventData.assignId == this.assign.id) {
            if (syncEventData.warnings && syncEventData.warnings.length) {
                // Show warnings.
                CoreDomUtils.showErrorModal(syncEventData.warnings[0]);
            }

            return true;
        }

        return false;
    }

    /**
     * Performs the sync of the activity.
     *
     * @return Promise resolved when done.
     */
    protected async sync(): Promise<void> {
        await AddonModAssignSync.syncAssign(this.assign!.id);
    }

    /**
     * Component being destroyed.
     */
    ngOnDestroy(): void {
        super.ngOnDestroy();

        this.savedObserver?.off();
        this.submittedObserver?.off();
        this.gradedObserver?.off();
    }

}
