import { scaleLinear, ScaleContinuousNumeric, ScaleLinear } from "d3-scale";
import React, { createContext, useContext, useMemo } from "react";
import h from "@macrostrat/hyper";

type HeightRange = [number, number];
type ColumnScale = ScaleContinuousNumeric<HeightRange, number> | any;

type ColumnScaleClamped = ScaleLinear<number, number>;

export declare interface ColumnDivision {
  section_id: string;
  id: number;
  surface: number;
  bottom: number;
  top: number;
  // Extra properties that are there for legacy purposes
  flooding_surface_order?: number;
  grainsize?: string;
  covered?: boolean;
  // Used for boundary management control
  definite_boundary?: boolean;
  facies?: string;
}

enum ColumnAxisType {
  AGE = "age",
  HEIGHT = "height",
  DEPTH = "depth",
  ORDINAL = "ordinal",
}
export interface ColumnCtx<T extends ColumnDivision> {
  divisions: T[];
  scaleClamped: ColumnScaleClamped;
  pixelsPerMeter: number;
  scale: ColumnScale;
  axisType?: ColumnAxisType;
  pixelHeight?: number;
  zoom: number;
}

export const ColumnContext = createContext<ColumnCtx<ColumnDivision>>({
  scale: scaleLinear(),
  divisions: [],
  scaleClamped: scaleLinear().clamp(true),
  pixelsPerMeter: 1,
  zoom: 1,
});

export interface ColumnProviderProps<T extends ColumnDivision> {
  pixelsPerMeter?: number;
  divisions: T[];
  range?: HeightRange | any;
  height?: number;
  zoom?: number;
  width?: number;
  axisType?: ColumnAxisType;
  children?: any;
}

function ColumnProvider<T extends ColumnDivision>(
  props: ColumnProviderProps<T>
) {
  /**
    Lays out a column on its Y (height) axis.
    This component would be swapped to provide eventual generalization to a Wheeler-diagram
    (time-domain) framework.
    */

  let {
    children,
    pixelsPerMeter = 20,
    zoom = 1,
    height,
    range,
    divisions = [],
    width = 150,
    axisType = ColumnAxisType.HEIGHT,
    ...rest
  } = props;

  // Check if "rest" actually changed
  // This is a hack to avoid re-rendering the column
  // when the "rest" props change
  const restStr = JSON.stringify(rest);
  const restRef = React.useRef(null);
  if (restStr !== restRef.current) {
    restRef.current = restStr;
    if (Object.keys(rest).length > 0) {
      console.warn(
        "Passing extra properties to ColumnProvider is deprecated:",
        rest
      );
    }
  }

  //# Calculate correct range and height
  // Range overrides height if set
  const value: ColumnCtx<T> = useMemo(() => {
    if (range != null) {
      height = Math.abs(range[1] - range[0]);
    } else {
      range = [0, height];
    }

    // same as the old `innerHeight`
    const pixelHeight = height * pixelsPerMeter * zoom;

    const scale = scaleLinear().domain(range).range([pixelHeight, 0]);
    const scaleClamped = scale.copy().clamp(true);

    return {
      pixelsPerMeter,
      pixelHeight,
      zoom,
      range,
      height,
      scale,
      scaleClamped,
      divisions,
      width,
      axisType,
      ...rest,
    };
  }, [
    axisType,
    height,
    pixelsPerMeter,
    range,
    zoom,
    divisions,
    width,
    restRef.current,
  ]);
  return h(ColumnContext.Provider, { value }, children);
}

const useColumn = () => useContext(ColumnContext);
const useColumnDivisions = () => useContext(ColumnContext).divisions;

export { ColumnProvider, ColumnAxisType, useColumnDivisions, useColumn };
