import { map } from 'rxjs/operators';

import {
  ArrayVector,
  DataFrame,
  DataTransformerID,
  DataTransformerInfo,
  FieldMatcherID,
  getFieldDisplayName,
  getFieldMatcher,
  MatcherConfig,
  reduceField,
} from '@grafana/data';

import {
  getFieldConfigFromFrame,
  FieldToConfigMapping,
  evaluteFieldMappings,
} from '../fieldToConfigMapping/fieldToConfigMapping';

export interface ConfigFromQueryTransformOptions {
  configRefId?: string;
  mappings: FieldToConfigMapping[];
  applyTo?: MatcherConfig;
}

export function extractConfigFromQuery(options: ConfigFromQueryTransformOptions, data: DataFrame[]) {
  let configFrame: DataFrame | null = null;

  for (const frame of data) {
    if (frame.refId === options.configRefId) {
      configFrame = frame;
      break;
    }
  }

  if (!configFrame) {
    return data;
  }

  const reducedConfigFrame: DataFrame = {
    fields: [],
    length: 1,
  };

  const mappingResult = evaluteFieldMappings(configFrame, options.mappings ?? [], false);

  // reduce config frame
  for (const field of configFrame.fields) {
    const newField = { ...field };
    const fieldName = getFieldDisplayName(field, configFrame);
    const fieldMapping = mappingResult.index[fieldName];
    const result = reduceField({ field, reducers: [fieldMapping.reducerId] });
    newField.values = new ArrayVector([result[fieldMapping.reducerId]]);
    reducedConfigFrame.fields.push(newField);
  }

  const output: DataFrame[] = [];
  const matcher = getFieldMatcher(options.applyTo || { id: FieldMatcherID.numeric });

  for (const frame of data) {
    // Skip config frame in output
    if (frame === configFrame && data.length > 1) {
      continue;
    }

    const outputFrame: DataFrame = {
      fields: [],
      length: frame.length,
    };

    for (const field of frame.fields) {
      if (matcher(field, frame, data)) {
        const dataConfig = getFieldConfigFromFrame(reducedConfigFrame, 0, mappingResult);
        outputFrame.fields.push({
          ...field,
          config: {
            ...field.config,
            ...dataConfig,
          },
        });
      } else {
        outputFrame.fields.push(field);
      }
    }

    output.push(outputFrame);
  }

  return output;
}

export const configFromDataTransformer: DataTransformerInfo<ConfigFromQueryTransformOptions> = {
  id: DataTransformerID.configFromData,
  name: 'Config from query results',
  description: 'Set unit, min, max and more from data',
  defaultOptions: {
    configRefId: 'config',
    mappings: [],
  },

  /**
   * Return a modified copy of the series.  If the transform is not or should not
   * be applied, just return the input series
   */
  operator: (options) => (source) => source.pipe(map((data) => extractConfigFromQuery(options, data))),
};
