// Wrapped so we can use hx-boost to fake no page refresh. Issue is,
// that means coming back to the chart would redefine globals that exist
// and make things complain - so we scope everything in a function and invoke
// it.
function sample_chart() {

const height = 400;
const width = 800;
const margin = {
    top: 10,
    bottom: 10,
    left: 40,
    right: 10,
};
const derivedSizes = {
    _rawHeight: height,
    _rawWidth: width,
    drawableHeight: height - margin.top - margin.bottom,
    drawableWidth: width - margin.left - margin.right,
    margin: margin,
};

/**
*root: d3.Selection<d3.BaseType, unknown, null, undefined>,
  derivedSizes: SVGRenderBounds,
  scaleX: d3.AxisScale<d3.AxisDomain>,
  scaleY: d3.AxisScale<d3.AxisDomain>,
*
*/
function renderAxes(
  root,
  derivedSizes,
  scaleX,
  scaleY,
) {
  // Add x axis
  root
    .append("g")
    .attr("transform", `translate(0,${derivedSizes.drawableHeight})`)
    .call(d3.axisBottom(scaleX));

  // Add y axis
  root
    .append("g")
    .attr("transform", `translate(${derivedSizes.margin.left}, 0)`)
    .call(d3.axisLeft(scaleY));
}

/**
celcius: number | null, useCelcius: boolean
returns: number
*/
function correctTempScale(celcius, useCelcius){
  // Handle data missing temps
  const fixedVal = celcius ?? 0;
  if (useCelcius) {
    return fixedVal;
  }
  return (9 / 5) * fixedVal + 32;
}

/**
root: d3.Selection<d3.BaseType, unknown, null, undefined>,
  data: DigestTest,
  devicesToAverage: string[],
  xKey: keyof DeviceState,
  yKey: keyof DeviceState,
  scaleX: d3.AxisScale<any>,
  scaleY: d3.AxisScale<any>,
  color: string,
  useCelcius: boolean,
  minDate: Date,
  maxDate: Date,
  dashed: boolean,

*/
function renderAverageTemperatureData(
  root,
  data,
  devicesToAverage,
  xKey,
  yKey,
  scaleX,
  scaleY,
  color,
  useCelcius,
  minDate,
  maxDate,
  dashed,
) {
  // { [key: string]: DeviceState[] }
  const dataToUse  = {};
  const theAvgSeries = [];
  devicesToAverage.forEach(function (thisID) {
    if (typeof data.devices[thisID] !== "undefined") {
      dataToUse[thisID] = data.devices[thisID];
    }
  });
  // this needs to bin the time, average over that bin for each skewer, and then average that across devices to make the result
  // just go by days for the interval we care about, we are picking from the 26th and 30 days after it
  for (let i = 0; i < 3000; i++) {
    // number[]
    const tempBlort = [];
    if (
      new Date().valueOf() <
      new Date("2024-10-26").valueOf() + 1000 * 60 * 60 * 24 * i
    ) {
      break; // we don't go into the future with this
    }
    // we have the 12 hour offset so it shows as noon each day
    const thisDate = new Date("2024-10-26").valueOf() + 1000 * 60 * 60 * 24 * i;
    const thisNextDate =
      new Date("2024-10-26").valueOf() + 1000 * 60 * 60 * 24 * (i + 1);
    const valDate = new Date(thisDate + 1000 * 60 * 60 * 12); // we list the time as noon each day
    Object.keys(dataToUse).forEach(function (thisID) {
      const filteredData = dataToUse[thisID].filter(
        (x) =>
          new Date(x.collection_time).valueOf() > thisDate &&
          new Date(x.collection_time).valueOf() < thisNextDate &&
          x[yKey] !== null,
      );
      if (filteredData.length > 0) {
        // lazy fake cast
        tempBlort.push(
          filteredData.reduce(
            // a[yKey] as number
            (partialSum, a) => partialSum + (a[yKey] ? (a[yKey]) : 0),
            0,
          ) / filteredData.length,
        );
      }
    });
    theAvgSeries.push({
      x: valDate,
      y:
        tempBlort.reduce((partialSum, a) => partialSum + (a ? a : 0), 0) /
        tempBlort.length,
    });
  }

  const avgSeries = theAvgSeries.filter((x) => {
    return !isNaN(x.y) && x.x >= minDate && x.x <= maxDate;
  });

  const line = d3
    //  <x: number; y: number>
    .line()
    .x((d) => scaleX(d.x) ?? 0)
    .y((d) => scaleY(correctTempScale(d.y, useCelcius)) ?? 0);

  root
    .append("path")
    .attr("fill", "none")
    .attr("stroke", color)
    .attr("stroke-width", 7)
    // @ts-ignore
    .attr("d", line(avgSeries))
    .attr("opacity", "0.5")
    .attr("stroke-dasharray", dashed ? "10,10" : "");
}

/**
root: d3.Selection<d3.BaseType, unknown, null, undefined>,
  deviceData: DeviceState[],
  xKey: keyof DeviceState,
  yKey: keyof DeviceState,
  scaleX: d3.AxisScale<any>,
  scaleY: d3.AxisScale<any>,
  renderPaths: boolean,
  renderDots: boolean,
  renderAverage: boolean,
  color: string,
  useCelcius: boolean,
*/
function renderTemperatureData(
  root,
  deviceData,
  xKey,
  yKey,
  scaleX,
  scaleY,
  renderPaths,
  renderDots,
  renderAverage,
  color,
  useCelcius,
) {
  const lineTemperatureOverTime = d3
    // DeviceState
    .line()
    // d[xKey] as string | number | Date
    .x((d) => scaleX(new Date(d[xKey])) ?? 0)
    // @ts-ignore
    .y((d) => scaleY(correctTempScale(d[yKey], useCelcius)));

  // Stride our data for simple filtering
  const chartData = deviceData
//    .filter((_, idx) => idx % 10 == 0)
    .sort((a, b) => {
      return a.time < b.time ? -1 : 1;
    });

  // Render paths
  if (renderPaths) {
    root
      .append("path")
      .attr("fill", "none")
      .attr("stroke", color)
      .attr("stroke-width", 1.5)
      .attr("d", lineTemperatureOverTime(chartData));
  }

  // if (renderAverage) {
  //   root
  //     .append("path")
  //     .attr("fill", "none")
  //     .attr("stroke", color)
  //     .attr("stroke-width", 1.5)
  //     .attr(
  //       "d",
  //       lineTemperatureOverTime(calculateAverageTemperature(deviceData)),
  //     );
  // }

  // Render dots
  if (renderDots) {
    root
      .append("g")
      .selectAll("circle")
      // @ts-ignore
      .data(chartData, (d) => (d ? d[xKey] : this.id))
      .join("circle")
      .attr("r", "4")
      // @ts-ignore
      .attr("cy", (d) => scaleY(correctTempScale(d[yKey], useCelcius)))
      .attr(
        "cx",
        // d[xKey] as string | number | Date
        (d) => scaleX(new Date(d[xKey])) ?? 0,
      )
      .attr("stroke", "blue")
      .attr("fill", "transparent")
      .style("opacity", 0.4)
      .attr("data-device-id", (d) => d.device_id);
  }
}

/**
 * Renders rectangles representing temperature bands of interest
 type TargetBand = {
  name: string;
  color: string;
  minInclusive: number;
  maxExclusive: number;
};
 */
/*
root: d3.Selection<d3.BaseType, unknown, null, undefined>,
  bands: TargetBand[],
  scaleY: d3.AxisScale<d3.AxisDomain>,
  bounds: SVGRenderBounds,
  useCelcius: boolean,
*/
function renderTargetBands(
  root,
  bands,
  scaleY,
  bounds,
  useCelcius,
) {
  root
    .append("g")
    .selectAll("rect")
    .data(bands)
    .join("rect")
    .attr("x", bounds.margin.left)
    .attr("y", (d) => scaleY(correctTempScale(d.maxExclusive, useCelcius)) ?? 0)
    .attr("width", bounds.drawableWidth - bounds.margin.left)
    .attr(
      "height",
      (d) =>
        (scaleY(correctTempScale(d.minInclusive, useCelcius)) ?? 0) -
        (scaleY(correctTempScale(d.maxExclusive, useCelcius)) ?? 0),
    )
    .attr("fill", (d) => d.color)
    .style("opacity", 0.1);
}

function init(props) {
    const root = d3.select("#chart-root");

//    root.addEventListener("mousemove", (e) => {
//        if (!(e.target instanceof SVGElement)) {
//          return;
//        }
//        const target = e.target;
//        let device_id = target.dataset.deviceId;
//
//        if (device_id !== undefined) {
//          setHoveredDevice(device_id);
//          switch (target.nodeName) {
//            case "circle":
//              break;
//            default:
//              break;
//          }
//        } else {
//          setHoveredDevice(undefined);
//        }
//    });

    const lowerDateBound = new Date(props.startDate);
    const upperDateBound = new Date(props.endDate);

    let scaleY = d3
      .scaleLinear()
      .domain([32, 212])
      .range([derivedSizes.drawableHeight, margin.top]);
    if (props.useCelcius) {
      scaleY = d3
        .scaleLinear()
        .domain([0, 100])
        .range([derivedSizes.drawableHeight, margin.top]);
    }
    const scaleX = d3
      .scaleTime()
      .domain([new Date(props.startDate), new Date(props.endDate)])
      .range([margin.left, derivedSizes.drawableWidth]);

    // TODO: The Right Way (TM) after we get things working
    root.selectAll("*").remove();

    renderAxes(
      root,
      derivedSizes,
      scaleX,
      scaleY,
    );

    renderTargetBands(
      root,
      [
        { name: "too_hot", minInclusive: 72, maxExclusive: 83, color: "red" },
        { name: "warm", minInclusive: 42, maxExclusive: 72, color: "yellow" },
        { name: "good", minInclusive: 38, maxExclusive: 42, color: "green" },
      ],
      scaleY,
      derivedSizes,
      props.useCelcius,
    );

    if (props.renderAverage) {
      renderAverageTemperatureData(
        root,
        props.data,
        props.devicesOfInterest,
        "collection_time",
        "temperature_18_inch",
        scaleX,
        scaleY,
        "lightblue",
        props.useCelcius,
        new Date(props.startDate),
        new Date(props.endDate),
        false,
      );
      renderAverageTemperatureData(
        root,
        props.data,
        props.devicesOfInterest,
        "collection_time",
        "temperature_36_inch",
        scaleX,
        scaleY,
        "darkblue",
        props.useCelcius,
        new Date(props.startDate),
        new Date(props.endDate),
        true,
      );
    }

    // Hacked together, we can clean up the data processing outside?
    Object.keys(props.data.devices).forEach((key, idx) => {
      const deviceData = props.data.devices[key].filter((d) => {
        const sampleDate = new Date(d.collection_time);

        return sampleDate >= lowerDateBound && sampleDate <= upperDateBound;
      });
      const color = d3.schemeCategory10[idx % 10];
      const color2 = d3.schemeTableau10[idx % 10];

//      const highlightDevice = props.highlightDevice === key || hoveredDevice() === key;
        const highlightDevice = false

      renderTemperatureData(
        root,
        deviceData,
        "collection_time",
        "temperature_18_inch",
        scaleX,
        scaleY,
        props.renderPaths || highlightDevice,
        props.renderDots,
        props.renderAverage,
        color,
        props.useCelcius,
      );
      renderTemperatureData(
        root,
        deviceData,
        "collection_time",
        "temperature_36_inch",
        scaleX,
        scaleY,
        props.renderPaths || highlightDevice,
        props.renderDots,
        props.renderAverage,
        color2,
        props.useCelcius,
      );
      renderTemperatureData(
        root,
        deviceData,
        "collection_time",
        "ambient_temperature",
        scaleX,
        scaleY,
        props.renderAmbient || highlightDevice,
        false,
        false,
        color2,
        props.useCelcius,
      );
    });
//  });
}

const props = {
    renderPaths: true,
  renderDots: true,
  renderAmbient: true,
  renderAverage: true,
  useCelcius: true,
  /*
    export type Data = {
      devices: {
        [key: string]: DeviceState[];
      };
      limits: object;
    };
  */
  data: {
      devices: {
        // id: Measurements[],
      },
      limits: {},
  },
  highlightDevice: undefined,
  startDate: "2025-11-01",
  endDate: "2025-12-31",
  devicesOfInterest: []
}

fetch("/inspect/api/v1/test_data").then((res) => res.json()).then( (data) => {
    props.data.devices = data
    init(props)
})
}

sample_chart()