/*
 * utils.ts
 *
 * Copyright (c) 2025-2026 Xiongfei Shi
 *
 * Author: Xiongfei Shi <xiongfei.shi(a)icloud.com>
 * License: Apache-2.0
 *
 * https://github.com/shixiongfei/hft.js
 */

import type { BarData, OrderFlag } from "./typedef.js";
import type { BarInfo } from "./bar.js";
import type {
  IPlaceOrderResultReceiver,
  IRuntimeEngine,
  IStrategy,
} from "./interfaces.js";

export const isValidPrice = (x: number) => x !== Number.MAX_VALUE && x !== 0;
export const isValidVolume = (x: number) => x !== Number.MAX_VALUE && x !== 0;

export const parseSymbol = (symbol: string): [string, string] => {
  const [instrumentId = "", exchangeId = ""] = symbol.split(".");
  return [instrumentId, exchangeId];
};

export const getBarBuyVolume = (bar: BarData, price: number) =>
  bar.buyVolumes[price] ?? 0;

export const getBarSellVolume = (bar: BarData, price: number) =>
  bar.sellVolumes[price] ?? 0;

export const getBarVolume = (bar: BarData, price: number) =>
  getBarBuyVolume(bar, price) + getBarSellVolume(bar, price);

export const mergeBarData = (bars: BarData[]): BarData => {
  if (bars.length === 0) {
    throw new Error("Bars is empty");
  }

  if (bars.length === 1) {
    return bars[1]!;
  }

  const bar: BarInfo = {
    symbol: bars[0]!.symbol,
    date: bars[0]!.date,
    time: bars[0]!.time,
    openInterest: bars[0]!.openInterest,
    openPrice: bars[0]!.openPrice,
    highPrice: bars[0]!.highPrice,
    lowPrice: bars[0]!.lowPrice,
    closePrice: bars[0]!.closePrice,
    volume: bars[0]!.volume,
    amount: bars[0]!.volume,
    delta: bars[0]!.delta,
    poc: bars[0]!.poc,
    buyVolumes: { ...bars[0]!.buyVolumes },
    sellVolumes: { ...bars[0]!.sellVolumes },
  };

  for (let i = 1; i < bars.length; ++i) {
    const nextBar = bars[i]!;

    bar.openInterest = nextBar.openInterest;
    bar.closePrice = nextBar.closePrice;

    bar.highPrice = Math.max(bar.highPrice, nextBar.highPrice);
    bar.lowPrice = Math.min(bar.lowPrice, nextBar.lowPrice);

    bar.volume += nextBar.volume;
    bar.amount += nextBar.amount;

    for (const price in nextBar.buyVolumes) {
      const volumeDelta = nextBar.buyVolumes[price]!;

      bar.buyVolumes[price] = volumeDelta + (bar.buyVolumes[price] ?? 0);
      bar.delta += volumeDelta;

      const priceVP = bar.buyVolumes[price] + (bar.sellVolumes[price] ?? 0);
      const pocVP = getBarVolume(bar, bar.poc);

      if (priceVP > pocVP) {
        bar.poc = parseFloat(price);
      }
    }

    for (const price in nextBar.sellVolumes) {
      const volumeDelta = nextBar.sellVolumes[price]!;

      bar.sellVolumes[price] = volumeDelta + (bar.sellVolumes[price] ?? 0);
      bar.delta -= volumeDelta;

      const priceVP = bar.sellVolumes[price] + (bar.buyVolumes[price] ?? 0);
      const pocVP = getBarVolume(bar, bar.poc);

      if (priceVP > pocVP) {
        bar.poc = parseFloat(price);
      }
    }
  }

  Object.freeze(bar.buyVolumes);
  Object.freeze(bar.sellVolumes);

  return Object.freeze(bar);
};

export const buyOpen = (
  engine: IRuntimeEngine,
  strategy: IStrategy,
  symbol: string,
  volume: number,
  price: number,
  receiver: IPlaceOrderResultReceiver,
  flag: OrderFlag = "limit",
) =>
  engine.placeOrder(
    strategy,
    symbol,
    "open",
    "long",
    volume,
    price,
    price > 0 && flag === "limit" ? "limit" : "market",
    receiver,
  );

export const buyClose = (
  engine: IRuntimeEngine,
  strategy: IStrategy,
  symbol: string,
  volume: number,
  price: number,
  isToday: boolean,
  receiver: IPlaceOrderResultReceiver,
  flag: OrderFlag = "limit",
) =>
  engine.placeOrder(
    strategy,
    symbol,
    isToday ? "close-today" : "close",
    "long",
    volume,
    price,
    price > 0 && flag === "limit" ? "limit" : "market",
    receiver,
  );

export const sellOpen = (
  engine: IRuntimeEngine,
  strategy: IStrategy,
  symbol: string,
  volume: number,
  price: number,
  receiver: IPlaceOrderResultReceiver,
  flag: OrderFlag = "limit",
) =>
  engine.placeOrder(
    strategy,
    symbol,
    "open",
    "short",
    volume,
    price,
    price > 0 && flag === "limit" ? "limit" : "market",
    receiver,
  );

export const sellClose = (
  engine: IRuntimeEngine,
  strategy: IStrategy,
  symbol: string,
  volume: number,
  price: number,
  isToday: boolean,
  receiver: IPlaceOrderResultReceiver,
  flag: OrderFlag = "limit",
) =>
  engine.placeOrder(
    strategy,
    symbol,
    isToday ? "close-today" : "close",
    "short",
    volume,
    price,
    price > 0 && flag === "limit" ? "limit" : "market",
    receiver,
  );
