import { LinearScale, LogarithmicScale, PointOptions, LinearScaleOptions, LogarithmicScaleOptions } from 'chart.js';
import { merge, drawPoint } from 'chart.js/helpers';
import { baseDefaults, ILegendScaleOptions, LegendScale, LogarithmicLegendScale } from './LegendScale';

export interface ISizeScaleOptions extends ILegendScaleOptions {
  // support all options from linear scale -> https://www.chartjs.org/docs/latest/axes/cartesian/linear.html#linear-cartesian-axis
  // e.g. for tick manipulation, ...

  /**
   * radius range in pixel, the minimal data value will be mapped to the
   * first entry,  the maximal one to the second and a linear interpolation
   * for all values in between.
   *
   * @default [2, 20]
   */
  range: [number, number];

  /**
   * operation mode for the scale, area means that the area is linearly increasing whereas radius the radius is.
   * The area one is the default since it gives a better visual comparison of values
   * @default area
   */
  mode: 'radius' | 'area';

  /**
   * radius to render for missing values
   * @default 1
   */
  missing: number;
}

const scaleDefaults = {
  missing: 1,
  mode: 'area', // 'radius'
  // mode: 'radius',
  range: [2, 20],
  legend: {
    align: 'bottom',
    length: 90,
    width: 70,
    indicatorWidth: 42,
  },
};

export class SizeScale extends LegendScale<ISizeScaleOptions & LinearScaleOptions> {
  /**
   * @hidden
   */
  _model: PointOptions | null = null;

  /**
   * @hidden
   */
  getSizeForValue(value: number): number {
    const v = this._getNormalizedValue(value);
    if (v == null || Number.isNaN(v)) {
      return this.options.missing;
    }
    return this.getSizeImpl(v);
  }

  /**
   * @hidden
   */
  getSizeImpl(normalized: number): number {
    const [r0, r1] = this.options.range;
    if (this.options.mode === 'area') {
      const a1 = r1 * r1 * Math.PI;
      const a0 = r0 * r0 * Math.PI;
      const range = a1 - a0;
      const a = normalized * range + a0;
      return Math.sqrt(a / Math.PI);
    }
    const range = r1 - r0;
    return normalized * range + r0;
  }

  /**
   * @hidden
   */
  _drawIndicator(): void {
    /** @type {CanvasRenderingContext2D} */
    const { ctx } = this;
    const shift = this.options.legend.indicatorWidth / 2;

    const isHor = this.isHorizontal();
    const values = this.ticks;
    const labelItems = this.getLabelItems();
    const positions = labelItems
      ? labelItems.map((el: any) => ({ [isHor ? 'x' : 'y']: el.options.translation[isHor ? 0 : 1] }))
      : values.map((_, i) => ({ [isHor ? 'x' : 'y']: this.getPixelForTick(i) }));

    ((this as any)._gridLineItems || []).forEach((item: any) => {
      ctx.save();
      ctx.strokeStyle = item.color;
      ctx.lineWidth = item.width;

      if (ctx.setLineDash) {
        ctx.setLineDash(item.borderDash);
        ctx.lineDashOffset = item.borderDashOffset;
      }

      ctx.beginPath();

      if (this.options.grid.drawTicks) {
        switch (this.options.legend.align) {
          case 'left':
            ctx.moveTo(0, item.ty1);
            ctx.lineTo(shift, item.ty2);
            break;
          case 'top':
            ctx.moveTo(item.tx1, 0);
            ctx.lineTo(item.tx2, shift);
            break;
          case 'bottom':
            ctx.moveTo(item.tx1, shift);
            ctx.lineTo(item.tx2, shift * 2);
            break;
          default:
            // right
            ctx.moveTo(shift, item.ty1);
            ctx.lineTo(shift * 2, item.ty2);
            break;
        }
      }
      ctx.stroke();
      ctx.restore();
    });

    if (this._model) {
      const props = this._model;
      ctx.strokeStyle = props.borderColor;
      ctx.lineWidth = props.borderWidth || 0;
      ctx.fillStyle = props.backgroundColor;
    } else {
      ctx.fillStyle = 'blue';
    }

    values.forEach((v, i) => {
      const pos = positions[i];
      const radius = this.getSizeForValue(v.value);
      const x = isHor ? pos.x : shift;
      const y = isHor ? shift : pos.y;
      const renderOptions = {
        pointStyle: 'circle' as const,
        borderWidth: 0,
        ...(this._model || {}),
        radius,
      };
      drawPoint(ctx, renderOptions, x, y);
    });
  }

  static readonly id = 'size';

  /**
   * @hidden
   */
  static readonly defaults: any = /* #__PURE__ */ merge({}, [LinearScale.defaults, baseDefaults, scaleDefaults]);

  /**
   * @hidden
   */
  static readonly descriptors = /* #__PURE__ */ {
    _scriptable: true,
    _indexable: (name: string): boolean => name !== 'range',
  };
}

export class SizeLogarithmicScale extends LogarithmicLegendScale<ISizeScaleOptions & LogarithmicScaleOptions> {
  /**
   * @hidden
   */
  _model: PointOptions | null = null;

  /**
   * @hidden
   */
  getSizeForValue(value: number): number {
    const v = this._getNormalizedValue(value);
    if (v == null || Number.isNaN(v)) {
      return this.options.missing;
    }
    return this.getSizeImpl(v);
  }

  /**
   * @hidden
   */
  getSizeImpl(normalized: number): number {
    return SizeScale.prototype.getSizeImpl.call(this, normalized);
  }

  /**
   * @hidden
   */
  _drawIndicator(): void {
    SizeScale.prototype._drawIndicator.call(this);
  }

  static readonly id = 'sizeLogarithmic';

  /**
   * @hidden
   */
  static readonly defaults: any = /* #__PURE__ */ merge({}, [LogarithmicScale.defaults, baseDefaults, scaleDefaults]);
}

declare module 'chart.js' {
  export interface SizeScaleTypeRegistry {
    size: {
      options: ISizeScaleOptions & LinearScaleOptions;
    };
    sizeLogarithmic: {
      options: ISizeScaleOptions & LogarithmicScaleOptions;
    };
  }

  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
  export interface ScaleTypeRegistry extends SizeScaleTypeRegistry {}
}
