import { each, filter, find } from 'lodash';
import { DashboardModel } from '../state/DashboardModel';
import { contextSrv } from 'app/core/services/context_srv';
import { appEvents } from 'app/core/app_events';
import { UnsavedChangesModal } from '../components/SaveDashboard/UnsavedChangesModal';
import { DashboardSavedEvent, ShowModalReactEvent } from '../../../types/events';
import { locationService } from '@grafana/runtime';
import angular from 'angular';

export class ChangeTracker {
  init(dashboard: DashboardModel, originalCopyDelay: number) {
    let original: object | null = null;
    let originalPath = locationService.getLocation().pathname;

    // register events
    const savedEventUnsub = appEvents.subscribe(DashboardSavedEvent, () => {
      original = dashboard.getSaveModelClone();
      originalPath = locationService.getLocation().pathname;
    });

    if (originalCopyDelay && !dashboard.meta.fromExplore) {
      setTimeout(() => {
        // wait for different services to patch the dashboard (missing properties)
        original = dashboard.getSaveModelClone();
      }, originalCopyDelay);
    } else {
      original = dashboard.getSaveModelClone();
    }

    const history = locationService.getHistory();

    const blockUnsub = history.block((location) => {
      if (originalPath === location.pathname) {
        return;
      }

      if (this.ignoreChanges(dashboard, original)) {
        return;
      }

      if (!this.hasChanges(dashboard, original!)) {
        return;
      }

      appEvents.publish(
        new ShowModalReactEvent({
          component: UnsavedChangesModal,
          props: {
            dashboard: dashboard,
            onSaveSuccess: () => {
              original = dashboard.getSaveModelClone();
              history.push(location);
            },
            onDiscard: () => {
              original = dashboard.getSaveModelClone();
              history.push(location);
            },
          },
        })
      );

      return false;
    });

    const historyListenUnsub = history.listen((location) => {
      if (originalPath !== location.pathname) {
        blockUnsub();
        historyListenUnsub();
        savedEventUnsub.unsubscribe();
      }
    });
  }

  // for some dashboards and users
  // changes should be ignored
  ignoreChanges(current: DashboardModel, original: object | null) {
    if (!original) {
      return true;
    }

    // Ignore changes if the user has been signed out
    if (!contextSrv.isSignedIn) {
      return true;
    }

    if (!current || !current.meta) {
      return true;
    }

    const { canSave, fromScript, fromFile } = current.meta;
    if (!contextSrv.isEditor && !canSave) {
      return true;
    }

    return !canSave || fromScript || fromFile;
  }

  // remove stuff that should not count in diff
  cleanDashboardFromIgnoredChanges(dashData: any) {
    // need to new up the domain model class to get access to expand / collapse row logic
    const model = new DashboardModel(dashData);

    // Expand all rows before making comparison. This is required because row expand / collapse
    // change order of panel array and panel positions.
    model.expandRows();

    const dash = model.getSaveModelClone();

    // ignore time and refresh
    dash.time = 0;
    dash.refresh = 0;
    dash.schemaVersion = 0;
    dash.timezone = 0;

    // ignore iteration property
    delete dash.iteration;

    dash.panels = filter(dash.panels, (panel) => {
      if (panel.repeatPanelId) {
        return false;
      }

      // remove scopedVars
      panel.scopedVars = undefined;

      // ignore panel legend sort
      if (panel.legend) {
        delete panel.legend.sort;
        delete panel.legend.sortDesc;
      }

      return true;
    });

    // ignore template variable values
    each(dash.getVariables(), (variable: any) => {
      variable.current = null;
      variable.options = null;
      variable.filters = null;
    });

    return dash;
  }

  hasChanges(current: DashboardModel, original: any) {
    const currentClean = this.cleanDashboardFromIgnoredChanges(current.getSaveModelClone());
    const originalClean = this.cleanDashboardFromIgnoredChanges(original);

    const currentTimepicker: any = find((currentClean as any).nav, { type: 'timepicker' });
    const originalTimepicker: any = find((originalClean as any).nav, { type: 'timepicker' });

    if (currentTimepicker && originalTimepicker) {
      currentTimepicker.now = originalTimepicker.now;
    }

    const currentJson = angular.toJson(currentClean);
    const originalJson = angular.toJson(originalClean);

    return currentJson !== originalJson;
  }
}
