import { type DataFrame, type PanelData, type Field, getFieldDisplayName, ReducerID } from '@grafana/data';
import { config } from '@grafana/runtime';
import {
  type ResourceDimensionConfig,
  type ScaleDimensionConfig,
  type TextDimensionConfig,
  type ColorDimensionConfig,
  type ScalarDimensionConfig,
  type DirectionDimensionConfig,
  type ConnectionDirection,
} from '@grafana/schema';

import { getColorDimension } from './color';
import { getDirectionDimension } from './direction';
import { getResourceDimension } from './resource';
import { getScalarDimension } from './scalar';
import { getScaledDimension } from './scale';
import { getTextDimension } from './text';
import { type DimensionSupplier } from './types';

export function getColorDimensionFromData(
  data: PanelData | undefined,
  cfg: ColorDimensionConfig
): DimensionSupplier<string> {
  if (data?.series && cfg.field) {
    for (const frame of data.series) {
      const d = getColorDimension(frame, cfg, config.theme2);
      if (!d.isAssumed || data.series.length === 1) {
        return d;
      }
    }
  }
  return getColorDimension(undefined, cfg, config.theme2);
}

export function getDirectionDimensionFromData(
  data: PanelData | undefined,
  cfg: DirectionDimensionConfig
): DimensionSupplier<ConnectionDirection> {
  if (data?.series && cfg.field) {
    for (const frame of data.series) {
      const d = getDirectionDimension(frame, cfg);
      if (!d.isAssumed || data.series.length === 1) {
        return d;
      }
    }
  }
  return getDirectionDimension(undefined, cfg);
}

export function getScaleDimensionFromData(
  data: PanelData | undefined,
  cfg: ScaleDimensionConfig
): DimensionSupplier<number> {
  if (data?.series && cfg.field) {
    for (const frame of data.series) {
      const d = getScaledDimension(frame, cfg);
      if (!d.isAssumed || data.series.length === 1) {
        return d;
      }
    }
  }
  return getScaledDimension(undefined, cfg);
}

export function getScalarDimensionFromData(
  data: PanelData | undefined,
  cfg: ScalarDimensionConfig
): DimensionSupplier<number> {
  if (data?.series && cfg.field) {
    for (const frame of data.series) {
      const d = getScalarDimension(frame, cfg);
      if (!d.isAssumed || data.series.length === 1) {
        return d;
      }
    }
  }
  return getScalarDimension(undefined, cfg);
}

export function getResourceDimensionFromData(
  data: PanelData | undefined,
  cfg: ResourceDimensionConfig
): DimensionSupplier<string> {
  if (data?.series && cfg.field) {
    for (const frame of data.series) {
      const d = getResourceDimension(frame, cfg);
      if (!d.isAssumed || data.series.length === 1) {
        return d;
      }
    }
  }
  return getResourceDimension(undefined, cfg);
}

export function getTextDimensionFromData(
  data: PanelData | undefined,
  cfg: TextDimensionConfig
): DimensionSupplier<string> {
  if (data?.series && cfg.field) {
    for (const frame of data.series) {
      const d = getTextDimension(frame, cfg);
      if (!d.isAssumed || data.series.length === 1) {
        return d;
      }
    }
  }
  return getTextDimension(undefined, cfg);
}

export function findField(frame?: DataFrame, name?: string): Field | undefined {
  const idx = findFieldIndex(name, frame);
  return idx == null ? undefined : frame!.fields[idx];
}

export function findFieldIndex(name?: string, frame?: DataFrame, frames?: DataFrame[]): number | undefined {
  if (!frame || !name?.length) {
    return undefined;
  }

  for (let i = 0; i < frame.fields.length; i++) {
    const field = frame.fields[i];
    if (name === field.name) {
      return i;
    }
    const disp = getFieldDisplayName(field, frame, frames);
    if (name === disp) {
      return i;
    }
  }
  return undefined;
}

export function getLastNotNullFieldValue<T>(field: Field): T {
  const calcs = field.state?.calcs;
  if (calcs) {
    const v = calcs[ReducerID.lastNotNull];
    if (v != null) {
      return v;
    }
  }

  const data = field.values;
  let idx = data.length - 1;
  while (idx >= 0) {
    const v = data[idx--];
    if (v != null) {
      return v;
    }
  }
  return undefined as T;
}
