import {CupItem} from "../hooks/rustWsServer";
import {MessagePort} from "worker_threads";

let cup: {[key: number]: CupItem} = {};
let isSubscribed = false;
let pair = "";
let aggregation = 10;
let publisherTimeoutInMs = 40;
let publisherIntervalId: any = null;
let wsConnection: WebSocket|null = null;
let bestBidPrice = 0;
let bestAskPrice = 0;
let pricePrecision = 1;
let priceStep = 1;
let quantityPrecision = 1;
let maxVolume = 1;
let fixedMaxVolume = 0;
let volumeIsFixed = false;
let volumeAsDollars = false;
let cameras: {[key: string]: number} = {};
let cameraStep = 4;
let cameraIsBlocked = false;
let unblockCameraTimerId: any = null;
let port: MessagePort|null = null;
let rowsCount: number = 60;
let quantityDivider = 1;
let priceDivider = 1;

const connectToWs = (callback: () => void) => {
  if (wsConnection) {
    callback();

    return;
  }

  wsConnection = new WebSocket(`${process.env.NEXT_PUBLIC_RUST_WS_SERVER}`);

  wsConnection.onopen = () => {
    if (null !== wsConnection) {
      callback();
    }
  };

  wsConnection.onmessage = async(message: MessageEvent) => {
    if (!message.data) return;

    const data = JSON.parse(message.data);

    // console.log(data);

    if (!data?.commands || data.commands.length === 0) return;

    const exchangeInitial = data?.commands?.find((item: any) => "ExchangeInitial" in item);
    const update = data.commands.find((item: any) => "undefined" !== typeof item.Update);

    if (exchangeInitial) {
      cup = exchangeInitial.ExchangeInitial.rows;
      pricePrecision = exchangeInitial.ExchangeInitial.params.pricePrecision;
      priceStep = exchangeInitial.ExchangeInitial.params.priceStep;
      quantityPrecision = exchangeInitial.ExchangeInitial.params.quantityPrecision;
      quantityDivider = Math.pow(10, quantityPrecision);
      priceDivider = Math.pow(10, pricePrecision);
    }

    if (update) {
      const futuresUpdate = update.Update.find((item: any) => item?.pair?.substring(0, 2) === "FT");

      if (pair === futuresUpdate.pair.substring(3)) {
        bestBidPrice = futuresUpdate.spread.bid;
        bestAskPrice = futuresUpdate.spread.ask;

        if (cameras[pair] === 0) {
          cameras[pair] = bestAskPrice;
        }

        Object
          .keys(futuresUpdate.rows)
          // @ts-ignore
          .forEach((key: number) => {
            cup[key] = futuresUpdate.rows[key];
          });
      }
    }
  };
};

const publish = () => {
  if (!isSubscribed) return;

  if (!cameras[pair]) {
    cameras[pair] = 0;
  }

  const zoomedTickSize = priceStep * aggregation;
  const startMicroPrice = cameras[pair] + rowsCount / 2 * zoomedTickSize - zoomedTickSize;
  const centerCupPosition = parseInt((Math.ceil(bestAskPrice / zoomedTickSize) * zoomedTickSize).toFixed(0));
  const rows: {[key: number]: CupItem} = {};
  const timeModifier = parseInt((publisherTimeoutInMs / 40).toFixed(0));
  const diffModifier = Math.max(
    2,
    parseInt(((centerCupPosition - cameras[pair] / zoomedTickSize) / rowsCount).toFixed(0))
  );

  if (centerCupPosition !== cameras[pair] && cameras[pair] !== 0 && !cameraIsBlocked) {
    cameras[pair] = cameras[pair] > centerCupPosition
      ? Math.max(centerCupPosition, cameras[pair] - zoomedTickSize * timeModifier * diffModifier)
      : Math.min(centerCupPosition, cameras[pair] + zoomedTickSize * timeModifier * diffModifier);
  }

  for (let index = 0; index <= rowsCount; index++) {
    const microPrice = startMicroPrice - index * zoomedTickSize;

    if (microPrice < 0) continue;

    rows[microPrice] = cup[microPrice] || {};
    const bid = rows[microPrice]?.bid;
    const ask = rows[microPrice]?.ask;

    maxVolume = volumeAsDollars
      ? Math.max(maxVolume, (ask || bid || 0) / quantityDivider * (microPrice / priceDivider))
      : Math.max(maxVolume, (ask || bid || 0) / quantityDivider);
  }

  port?.postMessage({type: "set_camera", value: cameras[pair]});

  postMessage({
    type: "update_cup",
    cup: rows,
    camera: cameras[pair],
    aggregation,
    bestBidPrice,
    bestAskPrice,
    pricePrecision,
    priceStep,
    quantityPrecision,
    rowsCount,
    maxVolume: volumeIsFixed
      ? fixedMaxVolume
      : maxVolume,
    volumeAsDollars,
  });
};

const publisherStart = () => {
  if (publisherIntervalId) {
    clearInterval(publisherIntervalId);
  }

  publisherIntervalId = setInterval(publish, publisherTimeoutInMs);
};

const publisherStop = () => {
  if (publisherIntervalId) {
    clearInterval(publisherIntervalId);
  }
};

onmessage = (event: MessageEvent<any>) => {
  const data = "string" === typeof event.data ? JSON.parse(`${event.data}`) : event.data;

  if (data && data?.type === "subscribe") {
    pair = data.pair;
    aggregation = data.zoom;
    isSubscribed = true;
    cameras[pair] = 0;
    maxVolume = 0;
    cup = {};
    publisherStart();

    if (wsConnection?.readyState === 3) {
      wsConnection = null;
    }

    connectToWs(() => {
      if (null === wsConnection) return;

      wsConnection.send(JSON.stringify({
        "commands": [
          {
            commandType: "SUBSCRIBE_SYMBOL",
            exchange: `FT:${pair}`,
            aggregation: aggregation,
          },
        ],
      }));
    });
  }

  if (data && data?.type === "unsubscribe") {
    isSubscribed = false;
    cameras[pair] = 0;
    pair = "";
    cup = {};
    publisherStop();

    if (null !== wsConnection) {
      wsConnection.send(JSON.stringify({
        "commands": [
          {
            commandType: "UNSUBSCRIBE_SYMBOL",
            exchange: `FT:${data.pair}`,
          },
        ],
      }));
    }
  }

  if (data && data?.type === "change_publisher_timeout") {
    publisherTimeoutInMs = data.value;
    publisherStart();
  }

  if (data && data?.type === "get_publisher_timeout") {
    postMessage({
      type: "publisher_timeout_response",
      value: publisherTimeoutInMs,
    });
  }

  if (data && data?.type === "change_max_volume") {
    volumeIsFixed = data.isFixed;
    fixedMaxVolume = data.maxVolume;
    volumeAsDollars = data.volumeAsDollars;
    maxVolume = 1;
  }

  if (data && data?.type === "change_rows_count") {
    rowsCount = data.value;
  }

  if (data && data?.type === "get_max_volume") {
    postMessage({
      type: "max_volume_response",
      isFixed: volumeIsFixed,
      maxVolume: fixedMaxVolume,
    });
  }

  if (data && data?.type === "camera_up") {
    if (!cameras[pair]) cameras[pair] = 0;

    if (cameras[pair] === 0) return;

    if (unblockCameraTimerId) {
      clearTimeout(unblockCameraTimerId);
    }

    cameraIsBlocked = true;
    cameras[pair] += priceStep * aggregation * cameraStep;
    unblockCameraTimerId = setTimeout(() => cameraIsBlocked = false, 3000);
  }

  if (data && data?.type === "camera_down") {
    if (!cameras[pair]) cameras[pair] = 0;

    if (cameras[pair] === 0) return;

    if (unblockCameraTimerId) {
      clearTimeout(unblockCameraTimerId);
    }

    cameraIsBlocked = true;
    cameras[pair] -= priceStep * aggregation * cameraStep;
    unblockCameraTimerId = setTimeout(() => cameraIsBlocked = false, 3000);
  }

  if (data && data?.type === "set_port") {
    port = data.port;
  }
};

export {};

Есть центровка стакана. при сжатии больше 1, возможно расчет идет неправильный на центровку стакана.
Раньше центр перескакивал сразу на bestBid. А нужно, чтобы он плавно скролился до нее.
Есть проблема. ПРОАНАЛИЗИРУЙ КОД, НАЙДИ ОШИБКУ