// Libraries
import React, { CSSProperties } from 'react';
import tinycolor from 'tinycolor2';

// Utils
import { formattedValueToString, DisplayValue, getColorForTheme, FieldConfig } from '@grafana/data';
import { calculateFontSize } from '../../utils/measureText';

// Types
import { BigValueColorMode, Props, BigValueJustifyMode, BigValueTextMode } from './BigValue';
import { getTextColorForBackground } from '../../utils';
import { DrawStyle, GraphFieldConfig } from '../uPlot/config';
import { Sparkline } from '../Sparkline/Sparkline';

const LINE_HEIGHT = 1.2;
const MAX_TITLE_SIZE = 30;

export abstract class BigValueLayout {
  titleFontSize: number;
  valueFontSize: number;
  chartHeight: number;
  chartWidth: number;
  valueColor: string;
  panelPadding: number;
  justifyCenter: boolean;
  titleToAlignTo?: string;
  valueToAlignTo: string;
  maxTextWidth: number;
  maxTextHeight: number;
  textValues: BigValueTextValues;

  constructor(private props: Props) {
    const { width, height, value, theme, text } = props;

    this.valueColor = getColorForTheme(value.color || 'green', theme);
    this.panelPadding = height > 100 ? 12 : 8;
    this.textValues = getTextValues(props);
    this.justifyCenter = shouldJustifyCenter(props.justifyMode, this.textValues.title);
    this.valueToAlignTo = this.textValues.valueToAlignTo;
    this.titleToAlignTo = this.textValues.titleToAlignTo;
    this.titleFontSize = 14;
    this.valueFontSize = 14;
    this.chartHeight = 0;
    this.chartWidth = 0;
    this.maxTextWidth = width - this.panelPadding * 2;
    this.maxTextHeight = height - this.panelPadding * 2;

    // Explicit font sizing
    if (text) {
      if (text.titleSize) {
        this.titleFontSize = text.titleSize;
        this.titleToAlignTo = undefined;
      }
      if (text.valueSize) {
        this.valueFontSize = text.valueSize;
        this.valueToAlignTo = '';
      }
    }
  }

  getTitleStyles(): CSSProperties {
    const styles: CSSProperties = {
      fontSize: `${this.titleFontSize}px`,
      lineHeight: LINE_HEIGHT,
    };

    if (this.props.colorMode === BigValueColorMode.Background) {
      styles.color = getTextColorForBackground(this.valueColor);
    }

    return styles;
  }

  getValueStyles(): CSSProperties {
    const styles: CSSProperties = {
      fontSize: this.valueFontSize,
      fontWeight: 500,
      lineHeight: LINE_HEIGHT,
      position: 'relative',
      zIndex: 1,
    };

    if (this.justifyCenter) {
      styles.textAlign = 'center';
    }

    switch (this.props.colorMode) {
      case BigValueColorMode.Value:
        styles.color = this.valueColor;
        break;
      case BigValueColorMode.Background:
        styles.color = getTextColorForBackground(this.valueColor);
    }

    return styles;
  }

  getValueAndTitleContainerStyles() {
    const styles: CSSProperties = {
      display: 'flex',
    };

    if (this.justifyCenter) {
      styles.alignItems = 'center';
      styles.justifyContent = 'center';
      styles.flexGrow = 1;
    }

    return styles;
  }

  getPanelStyles(): CSSProperties {
    const { width, height, theme, colorMode } = this.props;

    const panelStyles: CSSProperties = {
      width: `${width}px`,
      height: `${height}px`,
      padding: `${this.panelPadding}px`,
      borderRadius: '3px',
      position: 'relative',
      display: 'flex',
    };

    const themeFactor = theme.isDark ? 1 : -0.7;

    switch (colorMode) {
      case BigValueColorMode.Background:
        const bgColor2 = tinycolor(this.valueColor)
          .darken(15 * themeFactor)
          .spin(8)
          .toRgbString();
        const bgColor3 = tinycolor(this.valueColor)
          .darken(5 * themeFactor)
          .spin(-8)
          .toRgbString();
        panelStyles.background = `linear-gradient(120deg, ${bgColor2}, ${bgColor3})`;
        break;
      case BigValueColorMode.Value:
        panelStyles.background = `transparent`;
        break;
    }

    if (this.justifyCenter) {
      panelStyles.alignItems = 'center';
      panelStyles.flexDirection = 'row';
    }

    return panelStyles;
  }

  renderChart(): JSX.Element | null {
    const { sparkline, colorMode } = this.props;

    if (!sparkline || !sparkline.y) {
      return null;
    }

    let fillColor: string;
    let lineColor: string;

    switch (colorMode) {
      case BigValueColorMode.Value:
        lineColor = this.valueColor;
        fillColor = tinycolor(this.valueColor).setAlpha(0.2).toRgbString();
        break;
      case BigValueColorMode.Background:
        fillColor = 'rgba(255,255,255,0.4)';
        lineColor = tinycolor(this.valueColor).brighten(40).toRgbString();
    }

    // The graph field configuration applied to Y values
    const config: FieldConfig<GraphFieldConfig> = {
      custom: {
        drawStyle: DrawStyle.Line,
        lineWidth: 1,
        fillColor,
        lineColor,
      },
    };

    return (
      <div style={this.getChartStyles()}>
        <Sparkline
          height={this.chartHeight}
          width={this.chartWidth}
          sparkline={sparkline}
          config={config}
          theme={this.props.theme}
        />
      </div>
    );
  }
  getChartStyles(): CSSProperties {
    return {
      position: 'absolute',
      right: 0,
      bottom: 0,
    };
  }
}

export class WideNoChartLayout extends BigValueLayout {
  constructor(props: Props) {
    super(props);

    const valueWidthPercent = this.titleToAlignTo?.length ? 0.3 : 1.0;

    if (this.valueToAlignTo.length) {
      // initial value size
      this.valueFontSize = calculateFontSize(
        this.valueToAlignTo,
        this.maxTextWidth * valueWidthPercent,
        this.maxTextHeight,
        LINE_HEIGHT
      );
    }

    if (this.titleToAlignTo?.length) {
      // How big can we make the title and still have it fit
      this.titleFontSize = calculateFontSize(
        this.titleToAlignTo,
        this.maxTextWidth * 0.6,
        this.maxTextHeight,
        LINE_HEIGHT,
        MAX_TITLE_SIZE
      );

      // make sure it's a bit smaller than valueFontSize
      this.titleFontSize = Math.min(this.valueFontSize * 0.7, this.titleFontSize);
    }
  }

  getValueAndTitleContainerStyles() {
    const styles = super.getValueAndTitleContainerStyles();
    styles.flexDirection = 'row';
    styles.alignItems = 'center';
    styles.flexGrow = 1;

    if (!this.justifyCenter) {
      styles.justifyContent = 'space-between';
    }

    return styles;
  }

  renderChart(): JSX.Element | null {
    return null;
  }

  getPanelStyles() {
    const panelStyles = super.getPanelStyles();
    panelStyles.alignItems = 'center';
    return panelStyles;
  }
}

export class WideWithChartLayout extends BigValueLayout {
  constructor(props: Props) {
    super(props);

    const { width, height } = props;

    const chartHeightPercent = 0.5;
    const titleWidthPercent = 0.6;
    const valueWidthPercent = 1 - titleWidthPercent;
    const textHeightPercent = 0.4;

    this.chartWidth = width;
    this.chartHeight = height * chartHeightPercent;

    if (this.titleToAlignTo?.length) {
      this.titleFontSize = calculateFontSize(
        this.titleToAlignTo,
        this.maxTextWidth * titleWidthPercent,
        this.maxTextHeight * textHeightPercent,
        LINE_HEIGHT,
        MAX_TITLE_SIZE
      );
    }

    if (this.valueToAlignTo.length) {
      this.valueFontSize = calculateFontSize(
        this.valueToAlignTo,
        this.maxTextWidth * valueWidthPercent,
        this.maxTextHeight * chartHeightPercent,
        LINE_HEIGHT
      );
    }
  }

  getValueAndTitleContainerStyles() {
    const styles = super.getValueAndTitleContainerStyles();
    styles.flexDirection = 'row';
    styles.flexGrow = 1;

    if (!this.justifyCenter) {
      styles.justifyContent = 'space-between';
    }

    return styles;
  }

  getPanelStyles() {
    const styles = super.getPanelStyles();
    styles.flexDirection = 'row';
    styles.justifyContent = 'space-between';
    return styles;
  }
}

export class StackedWithChartLayout extends BigValueLayout {
  constructor(props: Props) {
    super(props);

    const { width, height } = props;
    const titleHeightPercent = 0.15;
    const chartHeightPercent = 0.25;
    let titleHeight = 0;

    this.chartHeight = height * chartHeightPercent;
    this.chartWidth = width;

    if (this.titleToAlignTo?.length) {
      this.titleFontSize = calculateFontSize(
        this.titleToAlignTo,
        this.maxTextWidth,
        height * titleHeightPercent,
        LINE_HEIGHT,
        MAX_TITLE_SIZE
      );
    }
    titleHeight = this.titleFontSize * LINE_HEIGHT;

    if (this.valueToAlignTo.length) {
      this.valueFontSize = calculateFontSize(
        this.valueToAlignTo,
        this.maxTextWidth,
        this.maxTextHeight - this.chartHeight - titleHeight,
        LINE_HEIGHT
      );
    }

    // make title fontsize it's a bit smaller than valueFontSize
    if (this.titleToAlignTo?.length) {
      this.titleFontSize = Math.min(this.valueFontSize * 0.7, this.titleFontSize);
    }

    // make chart take up unused space
    this.chartHeight = height - this.titleFontSize * LINE_HEIGHT - this.valueFontSize * LINE_HEIGHT;
  }

  getValueAndTitleContainerStyles() {
    const styles = super.getValueAndTitleContainerStyles();
    styles.flexDirection = 'column';
    styles.justifyContent = 'center';
    return styles;
  }

  getPanelStyles() {
    const styles = super.getPanelStyles();
    styles.flexDirection = 'column';
    return styles;
  }
}

export class StackedWithNoChartLayout extends BigValueLayout {
  constructor(props: Props) {
    super(props);

    const { height } = props;
    const titleHeightPercent = 0.15;
    let titleHeight = 0;

    if (this.titleToAlignTo?.length) {
      this.titleFontSize = calculateFontSize(
        this.titleToAlignTo,
        this.maxTextWidth,
        height * titleHeightPercent,
        LINE_HEIGHT,
        MAX_TITLE_SIZE
      );

      titleHeight = this.titleFontSize * LINE_HEIGHT;
    }

    if (this.valueToAlignTo.length) {
      this.valueFontSize = calculateFontSize(
        this.valueToAlignTo,
        this.maxTextWidth,
        this.maxTextHeight - titleHeight,
        LINE_HEIGHT
      );
    }

    // make title fontsize it's a bit smaller than valueFontSize
    this.titleFontSize = Math.min(this.valueFontSize * 0.7, this.titleFontSize);
  }

  getValueAndTitleContainerStyles() {
    const styles = super.getValueAndTitleContainerStyles();
    styles.flexDirection = 'column';
    styles.flexGrow = 1;
    return styles;
  }

  getPanelStyles() {
    const styles = super.getPanelStyles();
    styles.alignItems = 'center';
    return styles;
  }
}

export function buildLayout(props: Props): BigValueLayout {
  const { width, height, sparkline } = props;
  const useWideLayout = width / height > 2.5;

  if (useWideLayout) {
    if (height > 50 && !!sparkline) {
      return new WideWithChartLayout(props);
    } else {
      return new WideNoChartLayout(props);
    }
  }

  // stacked layouts
  if (height > 100 && !!sparkline) {
    return new StackedWithChartLayout(props);
  } else {
    return new StackedWithNoChartLayout(props);
  }
}

export function shouldJustifyCenter(justifyMode?: BigValueJustifyMode, title?: string) {
  if (justifyMode === BigValueJustifyMode.Center) {
    return true;
  }

  return (title ?? '').length === 0;
}

export interface BigValueTextValues extends DisplayValue {
  valueToAlignTo: string;
  titleToAlignTo?: string;
  tooltip?: string;
}

function getTextValues(props: Props): BigValueTextValues {
  const { value, alignmentFactors, count } = props;
  let { textMode } = props;

  const titleToAlignTo = alignmentFactors ? alignmentFactors.title : value.title;
  const valueToAlignTo = formattedValueToString(alignmentFactors ? alignmentFactors : value);

  // In the auto case we only show title if this big value is part of more panes (count > 1)
  if (textMode === BigValueTextMode.Auto && (count ?? 1) === 1) {
    textMode = BigValueTextMode.Value;
  }

  switch (textMode) {
    case BigValueTextMode.Name:
      return {
        ...value,
        title: undefined,
        prefix: undefined,
        suffix: undefined,
        text: value.title || '',
        titleToAlignTo: undefined,
        valueToAlignTo: titleToAlignTo ?? '',
        tooltip: formattedValueToString(value),
      };
    case BigValueTextMode.Value:
      return {
        ...value,
        title: undefined,
        titleToAlignTo: undefined,
        valueToAlignTo,
        tooltip: value.title,
      };
    case BigValueTextMode.None:
      return {
        numeric: value.numeric,
        color: value.color,
        title: undefined,
        text: '',
        titleToAlignTo: undefined,
        valueToAlignTo: '1',
        tooltip: `Name: ${value.title}\nValue: ${formattedValueToString(value)}`,
      };
    case BigValueTextMode.ValueAndName:
    default:
      return {
        ...value,
        titleToAlignTo,
        valueToAlignTo,
      };
  }
}
