import './graph';
import './series_overrides_ctrl';
import './thresholds_form';
import './time_regions_form';
import './annotation_tooltip';
import './event_editor';

import { auto } from 'angular';
import { defaults, find, without } from 'lodash';

import { DataFrame, FieldConfigProperty, PanelEvents, PanelPlugin } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { MetricsPanelCtrl } from 'app/angular/panel/metrics_panel_ctrl';
import config from 'app/core/config';
import TimeSeries from 'app/core/time_series2';
import { ThresholdMapper } from 'app/features/alerting/state/ThresholdMapper';
import { getPanelPluginToMigrateTo } from 'app/features/dashboard/state/getPanelPluginToMigrateTo';
import { changePanelPlugin } from 'app/features/panel/state/actions';
import { dispatch } from 'app/store/store';

import { appEvents } from '../../../core/core';
import { loadSnapshotData } from '../../../features/dashboard/utils/loadSnapshotData';
import { annotationsFromDataFrames } from '../../../features/query/state/DashboardQueryRunner/utils';
import { ZoomOutEvent } from '../../../types/events';

import { GraphContextMenuCtrl } from './GraphContextMenuCtrl';
import { graphPanelMigrationHandler } from './GraphMigrations';
import { axesEditorComponent } from './axes_editor';
import { DataProcessor } from './data_processor';
import template from './template';
import { DataWarning, GraphFieldConfig, GraphPanelOptions } from './types';
import { getDataTimeRange } from './utils';

export class GraphCtrl extends MetricsPanelCtrl {
  static template = template;

  renderError = false;
  hiddenSeries: any = {};
  hiddenSeriesTainted = false;
  seriesList: TimeSeries[] = [];
  dataList: DataFrame[] = [];
  annotations: any = [];
  alertState: any;

  dataWarning?: DataWarning;
  colors: any = [];
  subTabIndex = 0;
  processor: DataProcessor;
  contextMenuCtrl: GraphContextMenuCtrl;

  panelDefaults: any = {
    // datasource name, null = default datasource
    datasource: null,
    // sets client side (flot) or native graphite png renderer (png)
    renderer: 'flot',
    yaxes: [
      {
        label: null,
        show: true,
        logBase: 1,
        min: null,
        max: null,
        format: 'short',
      },
      {
        label: null,
        show: true,
        logBase: 1,
        min: null,
        max: null,
        format: 'short',
      },
    ],
    xaxis: {
      show: true,
      mode: 'time',
      name: null,
      values: [],
      buckets: null,
    },
    yaxis: {
      align: false,
      alignLevel: null,
    },
    // show/hide lines
    lines: true,
    // fill factor
    fill: 1,
    // fill gradient
    fillGradient: 0,
    // line width in pixels
    linewidth: 1,
    // show/hide dashed line
    dashes: false,
    // show/hide line
    hiddenSeries: false,
    // length of a dash
    dashLength: 10,
    // length of space between two dashes
    spaceLength: 10,
    // show hide points
    points: false,
    // point radius in pixels
    pointradius: 2,
    // show hide bars
    bars: false,
    // enable/disable stacking
    stack: false,
    // stack percentage mode
    percentage: false,
    // legend options
    legend: {
      show: true, // disable/enable legend
      values: false, // disable/enable legend values
      min: false,
      max: false,
      current: false,
      total: false,
      avg: false,
    },
    // how null points should be handled
    nullPointMode: 'null',
    // staircase line mode
    steppedLine: false,
    // tooltip options
    tooltip: {
      value_type: 'individual',
      shared: true,
      sort: 0,
    },
    // time overrides
    timeFrom: null,
    timeShift: null,
    // metric queries
    targets: [{}],
    // series color overrides
    aliasColors: {},
    // other style overrides
    seriesOverrides: [],
    thresholds: [],
    timeRegions: [],
    options: {
      // show/hide alert threshold lines and fill
      alertThreshold: true,
    },
  };

  static $inject = ['$scope', '$injector'];

  constructor($scope: any, $injector: auto.IInjectorService) {
    super($scope, $injector);

    defaults(this.panel, this.panelDefaults);
    defaults(this.panel.tooltip, this.panelDefaults.tooltip);
    defaults(this.panel.legend, this.panelDefaults.legend);
    defaults(this.panel.xaxis, this.panelDefaults.xaxis);
    defaults(this.panel.options, this.panelDefaults.options);

    this.useDataFrames = true;
    this.processor = new DataProcessor(this.panel);
    this.contextMenuCtrl = new GraphContextMenuCtrl($scope);

    this.events.on(PanelEvents.render, this.onRender.bind(this));
    this.events.on(PanelEvents.dataFramesReceived, this.onDataFramesReceived.bind(this));
    this.events.on(PanelEvents.dataSnapshotLoad, this.onDataSnapshotLoad.bind(this));
    this.events.on(PanelEvents.editModeInitialized, this.onInitEditMode.bind(this));
    this.events.on(PanelEvents.initPanelActions, this.onInitPanelActions.bind(this));

    // set axes format from field config
    const fieldConfigUnit = this.panel.fieldConfig.defaults.unit;
    if (fieldConfigUnit) {
      this.panel.yaxes[0].format = fieldConfigUnit;
    }
  }

  onInitEditMode() {
    this.addEditorTab('Display', 'public/app/plugins/panel/graph/tab_display.html');
    this.addEditorTab('Series overrides', 'public/app/plugins/panel/graph/tab_series_overrides.html');
    this.addEditorTab('Axes', axesEditorComponent);
    this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html');
    this.addEditorTab('Thresholds', 'public/app/plugins/panel/graph/tab_thresholds.html');
    this.addEditorTab('Time regions', 'public/app/plugins/panel/graph/tab_time_regions.html');
    this.subTabIndex = 0;
    this.hiddenSeriesTainted = false;
  }

  onInitPanelActions(actions: any[]) {
    actions.push({ text: 'Toggle legend', click: 'ctrl.toggleLegend()', shortcut: 'p l' });
  }

  zoomOut(evt: any) {
    appEvents.publish(new ZoomOutEvent({ scale: 2 }));
  }

  onDataSnapshotLoad(snapshotData: any) {
    const { series, annotations } = loadSnapshotData(this.panel, this.dashboard);
    this.panelData!.annotations = annotations;
    this.onDataFramesReceived(series);
  }

  onDataFramesReceived(data: DataFrame[]) {
    this.dataList = data;
    this.seriesList = this.processor.getSeriesList({
      dataList: this.dataList,
      range: this.range,
    });

    this.dataWarning = this.getDataWarning();

    this.alertState = undefined;
    (this.seriesList as any).alertState = undefined;
    if (this.panelData!.alertState) {
      this.alertState = this.panelData!.alertState;
      (this.seriesList as any).alertState = this.alertState.state;
    }

    this.annotations = [];
    if (this.panelData!.annotations?.length) {
      this.annotations = annotationsFromDataFrames(this.panelData!.annotations);
    }

    this.loading = false;
    this.render(this.seriesList);
  }

  getDataWarning(): DataWarning | undefined {
    const datapointsCount = this.seriesList.reduce((prev, series) => {
      return prev + series.datapoints.length;
    }, 0);

    if (datapointsCount === 0) {
      if (this.dataList) {
        for (const frame of this.dataList) {
          if (frame.length && frame.fields?.length) {
            return {
              title: 'Unable to graph data',
              tip: 'Data exists, but is not timeseries',
              actionText: 'Switch to table view',
              action: () => {
                dispatch(changePanelPlugin({ panel: this.panel, pluginId: 'table' }));
              },
            };
          }
        }
      }

      return {
        title: 'No data',
        tip: 'No data returned from query',
      };
    }

    // If any data is in range, do not return an error
    for (const series of this.seriesList) {
      if (!series.isOutsideRange) {
        return undefined;
      }
    }

    // All data is outside the time range
    const dataWarning: DataWarning = {
      title: 'Data outside time range',
      tip: 'Can be caused by timezone mismatch or missing time filter in query',
    };

    const range = getDataTimeRange(this.dataList);

    if (range) {
      dataWarning.actionText = 'Zoom to data';
      dataWarning.action = () => {
        locationService.partial({
          from: range.from,
          to: range.to,
        });
      };
    }

    return dataWarning;
  }

  onRender() {
    if (!this.seriesList) {
      return;
    }

    ThresholdMapper.alertToGraphThresholds(this.panel);

    for (const series of this.seriesList) {
      series.applySeriesOverrides(this.panel.seriesOverrides);

      // Always use the configured field unit
      if (series.unit) {
        this.panel.yaxes[series.yaxis - 1].format = series.unit;
      }
      if (this.hiddenSeriesTainted === false && series.hiddenSeries === true) {
        this.hiddenSeries[series.alias] = true;
      }
    }
  }

  onColorChange = (series: any, color: string) => {
    series.setColor(config.theme2.visualization.getColorByName(color));
    this.panel.aliasColors[series.alias] = color;
    this.render();
  };

  onToggleSeries = (hiddenSeries: any) => {
    this.hiddenSeriesTainted = true;
    this.hiddenSeries = hiddenSeries;
    this.render();
  };

  onToggleSort = (sortBy: any, sortDesc: any) => {
    this.panel.legend.sort = sortBy;
    this.panel.legend.sortDesc = sortDesc;
    this.render();
  };

  onToggleAxis = (info: { alias: any; yaxis: any }) => {
    let override: any = find(this.panel.seriesOverrides, { alias: info.alias });
    if (!override) {
      override = { alias: info.alias };
      this.panel.seriesOverrides.push(override);
    }
    override.yaxis = info.yaxis;
    this.render();
  };

  addSeriesOverride(override: any) {
    this.panel.seriesOverrides.push(override || {});
  }

  removeSeriesOverride(override: any) {
    this.panel.seriesOverrides = without(this.panel.seriesOverrides, override);
    this.render();
  }

  toggleLegend() {
    this.panel.legend.show = !this.panel.legend.show;
    this.render();
  }

  legendValuesOptionChanged() {
    const legend = this.panel.legend;
    legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total;
    this.render();
  }

  onContextMenuClose = () => {
    this.contextMenuCtrl.toggleMenu();
  };

  getTimeZone = () => this.dashboard.getTimezone();

  getDataFrameByRefId = (refId: string) => {
    return this.dataList.filter((dataFrame) => dataFrame.refId === refId)[0];
  };

  migrateToReact() {
    const panelType = getPanelPluginToMigrateTo(this.panel, true);
    this.onPluginTypeChange(config.panels[panelType!]);
  }
}

// Use new react style configuration
export const plugin = new PanelPlugin<GraphPanelOptions, GraphFieldConfig>(null)
  .useFieldConfig({
    disableStandardOptions: [
      FieldConfigProperty.NoValue,
      FieldConfigProperty.Thresholds,
      FieldConfigProperty.Max,
      FieldConfigProperty.Min,
      FieldConfigProperty.Decimals,
      FieldConfigProperty.Color,
      FieldConfigProperty.Mappings,
    ],
  })
  .setDataSupport({ annotations: true, alertStates: true })
  .setMigrationHandler(graphPanelMigrationHandler);

// Use the angular ctrt rather than a react one
plugin.angularPanelCtrl = GraphCtrl;
