import React, { ReactNode, CSSProperties } from 'react';
import { XAxis, YAxis } from '@data-ui/xy-chart';
import { ChartFrame, Margin, mergeMargin, Dimension } from '@superset-ui/core';
import { ChannelEncoder, PlainObject, Value, XFieldDef, YFieldDef } from 'encodable';
import createTickComponent from './createTickComponent';
import computeAxisLayout, { AxisLayout } from './computeAxisLayout';

export const DEFAULT_LABEL_ANGLE = 40;

// Additional margin to avoid content hidden behind scroll bar
const OVERFLOW_MARGIN = 8;

export interface XYChartLayoutConfig<XOutput extends Value, YOutput extends Value> {
  width: number;
  height: number;
  minContentWidth?: number;
  minContentHeight?: number;
  margin: Margin;
  xEncoder: ChannelEncoder<XFieldDef<XOutput>, XOutput>;
  xTickSize?: number;
  xTickTextStyle?: CSSProperties;
  autoAdjustXMargin?: boolean;
  yEncoder: ChannelEncoder<YFieldDef<YOutput>, YOutput>;
  yTickSize?: number;
  yTickTextStyle?: CSSProperties;
  autoAdjustYMargin?: boolean;
}

export default class XYChartLayout<XOutput extends Value, YOutput extends Value> {
  chartWidth: number;

  chartHeight: number;

  containerWidth: number;

  containerHeight: number;

  margin: Margin;

  xEncoder: ChannelEncoder<XFieldDef<XOutput>, XOutput>;

  xLayout?: AxisLayout;

  yEncoder: ChannelEncoder<YFieldDef<YOutput>, YOutput>;

  yLayout?: AxisLayout;

  constructor(config: XYChartLayoutConfig<XOutput, YOutput>) {
    const {
      width,
      height,
      minContentWidth = 0,
      minContentHeight = 0,
      margin,
      xEncoder,
      xTickSize,
      xTickTextStyle,
      autoAdjustXMargin = true,
      yEncoder,
      yTickSize,
      yTickTextStyle,
      autoAdjustYMargin = true,
    } = config;

    this.xEncoder = xEncoder;
    this.yEncoder = yEncoder;

    if (typeof yEncoder.axis !== 'undefined') {
      this.yLayout = computeAxisLayout(yEncoder.axis, {
        axisWidth: Math.max(height - margin.top - margin.bottom),
        defaultTickSize: yTickSize,
        tickTextStyle: yTickTextStyle,
      });
    }

    const secondMargin =
      this.yLayout && autoAdjustYMargin ? mergeMargin(margin, this.yLayout.minMargin) : margin;
    const innerWidth = Math.max(width - secondMargin.left - secondMargin.right, minContentWidth);

    if (typeof xEncoder.axis !== 'undefined') {
      this.xLayout = computeAxisLayout(xEncoder.axis, {
        axisWidth: innerWidth,
        defaultTickSize: xTickSize,
        tickTextStyle: xTickTextStyle,
      });
    }

    const finalMargin =
      this.xLayout && autoAdjustXMargin
        ? mergeMargin(secondMargin, this.xLayout.minMargin)
        : secondMargin;

    const innerHeight = Math.max(height - finalMargin.top - finalMargin.bottom, minContentHeight);

    const chartWidth = Math.round(innerWidth + finalMargin.left + finalMargin.right);
    const chartHeight = Math.round(innerHeight + finalMargin.top + finalMargin.bottom);

    const isOverFlowX = chartWidth > width;
    const isOverFlowY = chartHeight > height;
    if (isOverFlowX) {
      finalMargin.bottom += OVERFLOW_MARGIN;
    }
    if (isOverFlowY) {
      finalMargin.right += OVERFLOW_MARGIN;
    }
    this.chartWidth = isOverFlowX ? chartWidth + OVERFLOW_MARGIN : chartWidth;
    this.chartHeight = isOverFlowY ? chartHeight + OVERFLOW_MARGIN : chartHeight;
    this.containerWidth = width;
    this.containerHeight = height;
    this.margin = finalMargin;
  }

  renderChartWithFrame(renderChart: (input: Dimension) => ReactNode) {
    return (
      <ChartFrame
        width={this.containerWidth}
        height={this.containerHeight}
        contentWidth={this.chartWidth}
        contentHeight={this.chartHeight}
        renderContent={renderChart}
      />
    );
  }

  renderXAxis(props?: PlainObject) {
    const { axis } = this.xEncoder;

    return axis && this.xLayout ? (
      <XAxis
        label={axis.getTitle()}
        labelOffset={this.xLayout.labelOffset}
        numTicks={axis.config.tickCount}
        orientation={axis.config.orient}
        tickComponent={createTickComponent(this.xLayout)}
        tickFormat={axis.formatValue}
        {...props}
      />
    ) : null;
  }

  renderYAxis(props?: PlainObject) {
    const { axis } = this.yEncoder;

    return axis && this.yLayout ? (
      <YAxis
        label={axis.getTitle()}
        labelOffset={this.yLayout.labelOffset}
        numTicks={axis.config.tickCount}
        orientation={axis.config.orient}
        tickFormat={axis.formatValue}
        {...props}
      />
    ) : null;
  }
}
