import type { Order, Fill, Side } from '../../domain/src/types';
import type { OrderRouter } from '../../execution/src/orderRouter';
import type { HedgeEngine } from '../../hedge/src/hedgeEngine';
import type { ShadowBook } from '../../utils/src/shadowBook';
import { VolatilityEstimator } from '../../utils/src/volatilityEstimator';
import { FillRateMonitor } from '../../utils/src/fillRateMonitor';
import { FillRateController, type FillRateControllerConfig } from '../../utils/src/fillRateController';
import pino, { type Logger } from 'pino';
import { observeFillRateMetrics, observeGridMetrics } from '../../telemetry/src/gridMetrics';
const PLACE_RETRY_ATTEMPTS = 3;
const PLACE_RETRY_BASE_DELAY_MS = 200;
const HEDGE_PENDING_TIMEOUT_MS = 30_000;

export interface GridConfig {
  symbol: string;
  gridStepBps: number;      // 网格间距（bps）
  gridRangeBps: number;     // 网格范围（bps）
  baseClipUsd: number;      // 单层订单大小（USD）
  maxLayers: number;        // 最大层数
  hedgeThresholdBase: number; // 对冲阈值（base asset）
  accountId?: string;
  tickSize?: number;
  lotSize?: number;
  incrementalMode?: boolean;
}

export interface GridLevel {
  index: number;            // 网格索引（正=卖单，负=买单）
  side: Side;
  px: number;
  sz: number;
  orderId?: string;
  clientId?: string;
  filled: boolean;
  timestamp: number;
}

type TargetLevel = {
  index: number;
  side: Side;
  px: number;
  sz: number;
};

export class GridMaker {
  private grids: Map<number, GridLevel> = new Map();
  private currentDelta: number = 0;
  private gridCenter: number = 0;
  private isInitialized: boolean = false;
  private readonly accountId: string;
  private readonly tickSize: number;
  private readonly lotSize: number;
  private readonly pendingHedges = new Map<string, { qty: number; ts: number }>();
  private readonly adaptiveConfig?: AdaptiveGridConfig;
  private readonly volatilityEstimator?: VolatilityEstimator;
  private readonly fillRateMonitor?: FillRateMonitor;
  private readonly fillRateController?: FillRateController;
  private readonly fillRateControlEnabled: boolean;
  private readonly cancelAllOrders: (symbol: string) => Promise<void>;
  private readonly releaseOrder: (orderId: string, clientOrderId?: string) => void;
  private readonly logger: Logger;
  private currentGridStepBps: number;
  private baseClipUsd: number;
  private readonly incrementalMode: boolean;
  private lastRecenterTs = 0;
  private resetting = false;
  private consecutivePlaceFailures = 0;
  private needsReinit = false;
  private readonly pendingCancellations = new Set<string>();

  constructor(
    private config: GridConfig,
    private router: OrderRouter,
    private hedgeEngine: HedgeEngine,
    private shadowBook: ShadowBook,
    logger?: Logger,
    adaptiveConfig?: AdaptiveGridConfig,
    cancelAllOrders?: (symbol: string) => Promise<void>,
    releaseOrder?: (orderId: string, clientOrderId?: string) => void,
    fillRateControlConfig?: FillRateControllerConfig
  ) {
    this.accountId = config.accountId ?? "maker";
    this.tickSize = config.tickSize ?? 1;
    this.lotSize = config.lotSize ?? 0.00001;
    this.baseClipUsd = config.baseClipUsd;
    this.currentGridStepBps = config.gridStepBps;
    this.incrementalMode = config.incrementalMode ?? false;
    this.cancelAllOrders = cancelAllOrders ?? (async () => {});
    this.releaseOrder = releaseOrder ?? (() => {});
    const baseLogger = logger ?? pino({ name: 'GridMaker' });
    this.logger = baseLogger.child({ component: 'GridMaker' });

    // 成交率闭环控制（优先级高于价差/波动率自适应）
    this.fillRateControlEnabled = !!fillRateControlConfig;
    if (fillRateControlConfig) {
      this.fillRateMonitor = new FillRateMonitor({
        windowSeconds: 300,  // 5 分钟窗口
        minSamples: 2        // 降低到2次成交即可启动（快速响应）
      });
      this.fillRateController = new FillRateController(fillRateControlConfig);
      this.logger.info({ fillRateControlConfig }, 'Fill rate closed-loop control enabled (hybrid mode with spread-based fallback)');
    } else if (adaptiveConfig?.enabled) {
      // 如果没有成交率控制，使用传统的自适应逻辑
      this.adaptiveConfig = adaptiveConfig;
      this.volatilityEstimator = new VolatilityEstimator({
        windowMinutes: adaptiveConfig.volatilityWindowMinutes,
        minSamples: adaptiveConfig.minSamples,
        maxCadenceMs: adaptiveConfig.maxCadenceMs
      });
      this.logger.info({ adaptiveConfig }, 'Adaptive grid enabled (spread/volatility based)');
    }
  }

  /**
   * 处理订单状态更新（从 WebSocket 推送）
   */
  handleOrderUpdate(update: any): void {
    const orderId = update?.order_id ?? update?.orderId;
    if (!orderId) return;

    const statusRaw = (update?.status ?? update?.state ?? '').toString().toLowerCase();

    // 如果订单被拒绝或取消，从网格中移除
    if (statusRaw === 'rejected' || statusRaw === 'canceled' || statusRaw === 'cancelled' || statusRaw === 'expired') {
      // 查找该订单对应的网格层
      for (const [index, level] of this.grids.entries()) {
        if (level.orderId === orderId) {
          this.logger.warn({
            index,
            orderId,
            status: statusRaw,
            price: level.px,
            side: level.side
          }, 'Order rejected/cancelled by exchange - removing from grid');

          // 从网格中删除这个层级
          this.grids.delete(index);
          this.pendingCancellations.delete(orderId);
          // 标记需要重新初始化（在下次 tick 时补单）
          this.needsReinit = true;
          return;
        }
      }
      if (this.pendingCancellations.delete(orderId)) {
        this.logger.debug({ orderId }, 'Cancellation confirmed for order not present in grid (likely pre-removed)');
      }
      return;
    }
  }

  /**
   * 初始化网格：在当前 mid 周围布置双边网格
   */
  async initialize(): Promise<void> {
    const mid = this.shadowBook.mid(this.config.symbol);
    if (!mid) {
      throw new Error('Cannot initialize grid: no mid price available');
    }

    this.gridCenter = mid;
    if (this.volatilityEstimator) {
      this.volatilityEstimator.update(mid);
    }

    // 获取盘口数据用于诊断
    const book = this.shadowBook.snapshot(this.config.symbol);
    const bestBid = book?.bids?.[0]?.px;
    const bestAsk = book?.asks?.[0]?.px;
    let marketContext: any = { mid, currentGridStepBps: this.currentGridStepBps };

    if (bestBid && bestAsk) {
      const spread = bestAsk - bestBid;
      const spreadBps = (spread / mid) * 10_000;
      marketContext = {
        ...marketContext,
        bestBid,
        bestAsk,
        spread: spread.toFixed(2),
        spreadBps: spreadBps.toFixed(2),
        bidDepth: book.bids?.slice(0, 3).map(l => ({ px: l.px, sz: l.sz })),
        askDepth: book.asks?.slice(0, 3).map(l => ({ px: l.px, sz: l.sz }))
      };
    }

    this.logger.info({ ...marketContext, config: this.config }, 'Initializing grid with market context');

    const { gridRangeBps, maxLayers } = this.config;
    const minLayers = this.adaptiveConfig?.minLayers;
    const minGridStepBps = this.adaptiveConfig?.minGridStepBps ?? 0;
    const maxGridStepBps = this.adaptiveConfig?.maxGridStepBps ?? Number.POSITIVE_INFINITY;

    if (minLayers && minLayers > 0 && maxLayers < minLayers) {
      this.logger.warn({
        configuredMaxLayers: maxLayers,
        requestedMinLayers: minLayers
      }, 'Configured maxLayers is lower than adaptive minLayers; min layers target cannot be fully satisfied');
    }

    if (minLayers && minLayers > 0) {
      const maxStepForMinLayers = gridRangeBps / minLayers;
      if (this.currentGridStepBps > maxStepForMinLayers) {
        const adjustedStep = clamp(maxStepForMinLayers, minGridStepBps, maxGridStepBps);
        if (adjustedStep < this.currentGridStepBps - 1e-6) {
          this.logger.info({
            previousStepBps: this.currentGridStepBps,
            adjustedStepBps: adjustedStep,
            minLayers
          }, 'Reducing grid step to preserve minimum layer count');
          this.currentGridStepBps = adjustedStep;
        }
      }
    }

    const stepRatio = this.currentGridStepBps / 1e4;
    const rangeRatio = gridRangeBps / 1e4;

    // 计算实际层数（不超过 maxLayers）
    const calculatedLayers = Math.floor(rangeRatio / stepRatio + 1e-9);
    const minLayerTarget = minLayers ? Math.min(minLayers, maxLayers) : 0;
    const actualLayers = Math.max(Math.min(calculatedLayers, maxLayers), minLayerTarget);

    // 计算单层订单大小（base asset）
    const baseSz = this.baseClipUsd / mid;
    const normalizedBaseSz = this.normalizeSize(baseSz);

    if (normalizedBaseSz <= 0) {
      throw new Error(`Grid base size ${baseSz} rounded to zero with lot size ${this.lotSize}`);
    }

    this.logger.info({
      actualLayers,
      baseSz: normalizedBaseSz,
      baseClipUsd: this.baseClipUsd,
      gridStepBps: this.currentGridStepBps,
      minLayerTarget: minLayers
    }, 'Grid parameters calculated');

    // 重置连续失败计数
    this.consecutivePlaceFailures = 0;

    const targetLevels = this.buildTargetLevels(mid, stepRatio, actualLayers, normalizedBaseSz);
    const expectedOrders = targetLevels.length;

    // 诊断：输出第1层订单离盘口的距离
    if (bestBid && bestAsk && targetLevels.length > 0) {
      const buyOrders = targetLevels.filter(l => l.side === 'buy').sort((a, b) => b.px - a.px);
      const sellOrders = targetLevels.filter(l => l.side === 'sell').sort((a, b) => a.px - b.px);

      if (buyOrders.length > 0 && sellOrders.length > 0) {
        const topBuyPx = buyOrders[0].px;
        const topSellPx = sellOrders[0].px;
        const buyDistanceBps = ((bestBid - topBuyPx) / bestBid) * 10_000;
        const sellDistanceBps = ((topSellPx - bestAsk) / bestAsk) * 10_000;

        this.logger.info({
          topBuyOrder: { px: topBuyPx, distanceFromBestBid: (bestBid - topBuyPx).toFixed(2), distanceBps: buyDistanceBps.toFixed(2) },
          topSellOrder: { px: topSellPx, distanceFromBestAsk: (topSellPx - bestAsk).toFixed(2), distanceBps: sellDistanceBps.toFixed(2) },
          totalLayers: actualLayers,
          buyOrderRange: { highest: buyOrders[0].px, lowest: buyOrders[buyOrders.length - 1].px },
          sellOrderRange: { lowest: sellOrders[0].px, highest: sellOrders[sellOrders.length - 1].px }
        }, 'Grid order placement analysis');
      }
    }

    if (this.incrementalMode && this.isInitialized) {
      try {
        const stats = await this.reconcileGrid(targetLevels);
        this.needsReinit = false;
        this.isInitialized = true;
        this.logger.info({
          mode: 'incremental',
          totalTarget: expectedOrders,
          placed: stats.placed,
          cancelled: stats.cancelled,
          reused: stats.reused,
          elapsedMs: stats.elapsedMs
        }, 'Incremental grid reconcile completed');
        return;
      } catch (error) {
        this.logger.warn({ error: normalizeError(error) }, 'Incremental reconcile failed, falling back to full rebuild');
        await this.cancelActiveOrders();
      }
    }

    const successfulOrders = await this.placeAllLevels(targetLevels);

    if (this.needsReinit) {
      this.logger.info({ action: 'schedule_reinit', currentGridStepBps: this.currentGridStepBps }, 'Reinitialization scheduled due to placement failures');
      return;
    }

    this.needsReinit = false;
    this.isInitialized = true;
    this.logger.info({
      totalGrids: this.grids.size,
      expectedLayers: expectedOrders,
      successRate: expectedOrders > 0 ? ((successfulOrders / expectedOrders) * 100).toFixed(1) + '%' : 'N/A'
    }, 'Grid initialization completed');
  }

  private buildTargetLevels(mid: number, stepRatio: number, actualLayers: number, normalizedBaseSz: number): TargetLevel[] {
    const levels: TargetLevel[] = [];
    for (let i = 1; i <= actualLayers; i++) {
      const buyPx = mid * (1 - i * stepRatio);
      levels.push({ index: -i, side: 'buy', px: buyPx, sz: normalizedBaseSz });
      const sellPx = mid * (1 + i * stepRatio);
      levels.push({ index: i, side: 'sell', px: sellPx, sz: normalizedBaseSz });
    }
    return levels;
  }

  private async placeAllLevels(levels: TargetLevel[]): Promise<number> {
    if (typeof (this.router as any).enableBulkInitMode === 'function') {
      (this.router as any).enableBulkInitMode();
      this.logger.info('Enabled bulk init mode for faster grid placement');
    }

    const startTs = Date.now();
    let successCount = 0;
    let failedCount = 0;
    let rateLimitEncountered = false;
    const maxConcurrency = Math.max(1, Math.floor(this.adaptiveConfig?.maxPlacementConcurrency ?? 4));
    const batchDelayMs = this.adaptiveConfig?.placementBatchDelayMs ?? 200;
    const rateLimitBackoffMs = this.adaptiveConfig?.rateLimitBackoffMs ?? Math.max(batchDelayMs * 2, 500);

    this.logger.info({ totalOrders: levels.length, maxConcurrency }, 'Placing grid orders in batches');

    for (let offset = 0; offset < levels.length; offset += maxConcurrency) {
      const batch = levels.slice(offset, offset + maxConcurrency);
      let batchHitRateLimit = false;

      await Promise.allSettled(batch.map(async level => {
        try {
          await this.placeGridOrder(level.index, level.side, level.px, level.sz);
          successCount += 1;
        } catch (err) {
          failedCount += 1;
          const normalized = normalizeError(err);
          const message = normalized?.message?.toLowerCase() ?? '';
          if (message.includes('ratelimiter') || normalized?.code === 'rate_limit') {
            batchHitRateLimit = true;
          }
          this.logger.warn(
            { index: level.index, side: level.side, px: level.px, error: normalized },
            'Skipping failed grid level'
          );
        }
      }));

      if (batchHitRateLimit) {
        rateLimitEncountered = true;
        this.logger.warn(
          { rateLimitBackoffMs, processedLevels: offset + batch.length },
          'Encountered rate limiter while placing batch, backing off before continuing'
        );
        await sleep(rateLimitBackoffMs);
      } else if (batchDelayMs > 0 && offset + batch.length < levels.length) {
        await sleep(batchDelayMs);
      }
    }

    const elapsedMs = Date.now() - startTs;
    const totalAttempts = levels.length;
    const avg = totalAttempts > 0 ? (elapsedMs / totalAttempts).toFixed(0) : '0';

    if (typeof (this.router as any).disableBulkInitMode === 'function') {
      (this.router as any).disableBulkInitMode();
      this.logger.debug('Disabled bulk init mode, returning to normal throttling');
    }

    this.logger.info({
      totalOrders: levels.length,
      successfulOrders: successCount,
      failedOrders: failedCount,
      elapsedMs,
      averageMs: avg,
      rateLimitEncountered
    }, 'Parallel grid placement completed');

    if (this.consecutivePlaceFailures >= 5) {
      const oldStep = this.currentGridStepBps;
      this.currentGridStepBps = Math.min(oldStep * 1.5, this.adaptiveConfig?.maxGridStepBps ?? 100);
      this.logger.warn({
        consecutivePlaceFailures: this.consecutivePlaceFailures,
        oldGridStepBps: oldStep,
        newGridStepBps: this.currentGridStepBps,
        action: 'auto_fallback_schedule'
      }, 'Too many consecutive place failures, increasing grid step; will re-initialize on next tick');
      this.needsReinit = true;
      this.isInitialized = false;
    }

    return successCount;
  }

  private async reconcileGrid(levels: TargetLevel[]): Promise<{ placed: number; reused: number; cancelled: number; elapsedMs: number }> {
    const start = Date.now();
    let placed = 0;
    let reused = 0;
    let cancelled = 0;

    this.logger.info({ targetLevels: levels.length }, 'Starting incremental grid reconciliation');

    const targetMap = new Map<number, TargetLevel>();
    for (const level of levels) {
      targetMap.set(level.index, level);
    }

    const tolerancePx = this.tickSize > 0 ? this.tickSize : levels.length > 0 ? Math.abs(levels[0].px) * 1e-6 : 1e-6;
    const sizeToleranceBase = this.lotSize > 0 ? this.lotSize : 1e-8;

    const activeLevels = Array.from(this.grids.values()).filter(
      level =>
        level.orderId &&
        !level.filled &&
        !this.pendingCancellations.has(level.orderId)
    );

    for (const level of activeLevels) {
      const target = targetMap.get(level.index);
      if (!target) {
        this.logger.info({ index: level.index, reason: 'not_in_target' }, 'Cancelling order - not in target map');
        try {
          await this.cancelGridLevel(level);
          cancelled += 1;
        } catch (error) {
          this.logger.error({ index: level.index, error: error instanceof Error ? error.message : String(error) }, 'Failed to cancel order in reconcile (not in target) - continuing');
        }
        continue;
      }
      const pxDiff = Math.abs(level.px - target.px);
      const sizeDiff = Math.abs(level.sz - target.sz);
      const sizeTolerance = Math.max(sizeToleranceBase, target.sz * 0.001);
      if (pxDiff <= tolerancePx && sizeDiff <= sizeTolerance) {
        this.logger.debug({ index: level.index, oldPx: level.px, newPx: target.px, pxDiff, tolerancePx }, 'Reusing order - within tolerance');
        reused += 1;
        targetMap.delete(level.index);
      } else {
        this.logger.info({ index: level.index, oldPx: level.px, newPx: target.px, pxDiff, tolerancePx, reason: 'price_changed' }, 'Cancelling order - price/size changed');
        try {
          await this.cancelGridLevel(level);
          cancelled += 1;
        } catch (error) {
          this.logger.error({ index: level.index, error: error instanceof Error ? error.message : String(error) }, 'Failed to cancel order in reconcile (price changed) - continuing');
        }
      }
    }

    this.logger.info({ remainingTargets: targetMap.size, reused, cancelled }, 'Reconcile phase 1 complete - now placing new orders');

    if (targetMap.size > 0) {
      const latestMid = this.shadowBook.mid(this.config.symbol);
      if (latestMid) {
        const stepRatio = this.currentGridStepBps / 10_000;
        const recalculatedBaseSz = this.normalizeSize(this.baseClipUsd / latestMid);
        if (recalculatedBaseSz > 0) {
          for (const target of targetMap.values()) {
            const distance = Math.abs(target.index) * stepRatio;
            const rawPx = target.side === 'buy'
              ? latestMid * (1 - distance)
              : latestMid * (1 + distance);
            target.px = rawPx;
            target.sz = recalculatedBaseSz;
          }
          this.gridCenter = latestMid;
          this.logger.info({
            latestMid,
            recalculatedBaseSz,
            targetCount: targetMap.size
          }, 'Updated placement targets with latest mid before re-hanging orders');
        } else {
          this.logger.warn({
            latestMid,
            recalculatedBaseSz
          }, 'Recalculated base size invalid, keeping previous placement targets');
        }
      } else {
        this.logger.warn('No latest mid price available during reconcile placement; using previously computed targets');
      }
    }

    // 并行放置所有新订单（与初始化相同的方式）
    const placementPromises = Array.from(targetMap.values()).map(async (target) => {
      this.logger.info({ index: target.index, side: target.side, px: target.px, sz: target.sz }, 'Placing new grid order in reconcile');
      try {
        await this.placeGridOrder(target.index, target.side, target.px, target.sz);
        return { success: true, index: target.index };
      } catch (error) {
        this.logger.error({ index: target.index, error: error instanceof Error ? error.message : String(error) }, 'Failed to place order in reconcile - continuing');
        return { success: false, index: target.index };
      }
    });

    const results = await Promise.all(placementPromises);
    placed = results.filter(r => r.success).length;

    const elapsedMs = Date.now() - start;
    this.consecutivePlaceFailures = Math.max(0, this.consecutivePlaceFailures);
    this.logger.info({ placed, reused, cancelled, elapsedMs, totalTargets: targetMap.size, successRate: `${(placed / targetMap.size * 100).toFixed(1)}%` }, 'Reconcile phase 2 complete - all orders placed');
    return { placed, reused, cancelled, elapsedMs };
  }

  private async cancelActiveOrders(): Promise<void> {
    const activeLevels = Array.from(this.grids.values()).filter(level => level.orderId && !level.filled);
    for (const level of activeLevels) {
      try {
        await this.cancelGridLevel(level);
      } catch (error) {
        this.logger.warn({ index: level.index, side: level.side, error: normalizeError(error) }, 'Failed to cancel level during fallback');
      }
    }
  }

  /**
   * Fill 回调：成交后挂对手单，更新 Delta，检查对冲阈值
   */
  async onFill(fill: Fill): Promise<void> {
    this.logger.info({ fill, isInitialized: this.isInitialized }, 'GridMaker.onFill called');

    if (!this.isInitialized) {
      this.logger.warn({ fill }, 'Grid not initialized, ignoring fill');
      return;
    }

    const gridLevel = this.findGridLevel(fill.orderId);
    if (!gridLevel) {
      this.logger.warn({ orderId: fill.orderId, fillSymbol: fill.symbol, gridSymbol: this.config.symbol }, 'Fill not from grid order, ignoring');
      return;
    }

    // 记录成交事件到成交率监控器
    if (this.fillRateMonitor) {
      this.fillRateMonitor.recordFill({
        isMaker: fill.liquidity === 'maker',
        isSelfTrade: false,  // TODO: 实现自成交检测
        symbol: fill.symbol,
        ts: fill.ts
      });
    }

    this.logger.info({
      gridIndex: gridLevel.index,
      side: fill.side,
      px: fill.px,
      sz: fill.sz,
      fee: fill.fee,
      liquidity: fill.liquidity
    }, 'Grid order filled');

    // 标记该层已成交
    gridLevel.filled = true;
    gridLevel.timestamp = Date.now();

    // 更新 Delta
    this.updateDelta(fill);

    // 挂对手单
    const oppositeSide: Side = fill.side === 'buy' ? 'sell' : 'buy';
    const stepRatio = this.currentGridStepBps / 1e4;
    const oppositePx = fill.side === 'buy'
      ? fill.px * (1 + stepRatio)
      : fill.px * (1 - stepRatio);

    try {
      const oppositeOrderId = await this.placeGridOrder(
        gridLevel.index,
        oppositeSide,
        oppositePx,
        fill.sz
      );

      this.logger.info({
        gridIndex: gridLevel.index,
        oppositeSide,
        oppositePx,
        orderId: oppositeOrderId
      }, 'Placed opposite grid order');
    } catch (error) {
      this.logger.error({ error, gridIndex: gridLevel.index }, 'Failed to place opposite order');
    }

    // 检查是否需要对冲
    await this.maybeHedge();
  }

  /**
   * Hedger 账户成交回调：用实际成交修正 Delta
   */
  async onHedgeFill(fill: Fill): Promise<void> {
    if (!this.isInitialized) {
      this.logger.warn('Grid not initialized, ignoring hedge fill');
      return;
    }
    if (fill.symbol !== this.config.symbol) {
      return;
    }

    const tracked = this.pendingHedges.get(fill.orderId);
    if (tracked) {
      this.pendingHedges.delete(fill.orderId);
    }

    const correction = fill.side === 'sell' ? -fill.sz : fill.sz;
    const beforeDelta = this.currentDelta;
    this.currentDelta += correction;

    this.logger.info({
      orderId: fill.orderId,
      fillSide: fill.side,
      fillSz: fill.sz,
      correction,
      beforeDelta,
      afterDelta: this.currentDelta,
      wasTracked: Boolean(tracked)
    }, 'Hedge fill received, delta adjusted');
  }

  /**
   * 重置网格：撤销所有旧订单，重新布置
   */
  async reset(): Promise<void> {
    if (this.resetting) return;
    this.resetting = true;
    this.needsReinit = false;
    this.logger.info({ gridSize: this.grids.size, pendingOrders: Array.from(this.grids.values()).filter(g => g.orderId && !g.filled).length }, 'Resetting grid');

    // 尝试批量撤单
    let cancelAllSuccess = false;
    try {
      this.logger.info({ action: 'cancelAll_start', symbol: this.config.symbol }, 'Starting cancelAll');
      await this.cancelAllOrders(this.config.symbol);
      this.logger.info({ action: 'cancelAll_complete' }, 'cancelAll completed successfully');
      cancelAllSuccess = true;

      // 等待 500ms 让交易所处理撤单
      await sleep(500);

      // 直接清理本地状态
      for (const level of this.grids.values()) {
        if (level.orderId) {
          this.releaseOrder(level.orderId, level.clientId);
        }
      }
    } catch (error) {
      this.logger.error({ error: normalizeError(error), action: 'cancelAll_failed' }, 'cancelAll failed, will try individual cancels');
    }

    // 如果 cancelAll 失败，才逐个撤单
    if (!cancelAllSuccess) {
      const pendingLevels = Array.from(this.grids.values()).filter(level => level.orderId && !level.filled);
      this.logger.info({ pendingCount: pendingLevels.length }, 'Attempting individual cancels');

      for (const level of pendingLevels) {
        await this.cancelGridLevel(level).catch(err =>
          this.logger.warn({ orderId: level.orderId, clientId: level.clientId, error: normalizeError(err) }, 'Individual cancel failed')
        );
      }
    }

    // 清空网格
    this.grids.clear();
    this.isInitialized = false;
    this.pendingHedges.clear();

    // 重新初始化
    try {
      this.logger.info({ action: 'initialize_start' }, 'Re-initializing grid after reset');
      await this.initialize();
      const gridReady = this.isInitialized && !this.needsReinit && this.grids.size > 0;
      if (gridReady) {
        this.logger.info({ action: 'reset_complete', gridSize: this.grids.size }, 'Grid reset and re-initialized successfully');
      } else {
        this.needsReinit = true;
        this.logger.warn({
          action: 'reset_incomplete',
          gridSize: this.grids.size,
          isInitialized: this.isInitialized,
          needsReinit: this.needsReinit
        }, 'Grid reset completed without restoring full grid; will retry on next tick');
      }
    } catch (error) {
      this.needsReinit = true;
      this.logger.error({ error: normalizeError(error), action: 'initialize_failed', gridSize: this.grids.size }, 'Failed to re-initialize grid after reset');
      // 不抛出异常，允许下次 onTick 重试
    } finally {
      this.resetting = false;
    }
  }

  /**
   * 获取当前状态（用于监控）
   */
  getStatus() {
    const adaptiveStatus = this.volatilityEstimator
      ? {
          currentGridStepBps: this.currentGridStepBps,
          volatility: this.volatilityEstimator.getStatus()
        }
      : undefined;

    const expectedLayers = this.currentGridStepBps > 0
      ? Math.min(this.config.maxLayers, Math.floor(this.config.gridRangeBps / this.currentGridStepBps))
      : 0;
    const expectedOrders = expectedLayers * 2;
    const effectiveOrderRatio = expectedOrders > 0 ? this.grids.size / expectedOrders : undefined;

    const status = {
      isInitialized: this.isInitialized,
      gridCenter: this.gridCenter,
      currentDelta: this.currentDelta,
      totalGrids: this.grids.size,
      filledGrids: Array.from(this.grids.values()).filter(g => g.filled).length,
      activeOrders: Array.from(this.grids.values()).filter(g => g.orderId && !g.filled).length,
      pendingHedges: this.pendingHedges.size,
      expectedOrders,
      effectiveOrderRatio,
      baseClipUsd: this.baseClipUsd,
      adaptive: adaptiveStatus
    };
    observeGridMetrics(this.config.symbol, {
      stepBps: this.currentGridStepBps,
      pendingHedges: this.pendingHedges.size,
      currentDelta: this.currentDelta,
      hourlyVolatilityBps: adaptiveStatus?.volatility?.hourlyVolBps
    });
    return status;
  }

  // ==================== 私有方法 ====================

  /**
   * 下单并记录网格
   */
  private async placeGridOrder(index: number, side: Side, px: number, sz: number): Promise<string> {
    const normalizedPx = this.normalizePrice(side, px);
    const normalizedSz = this.normalizeSize(sz);
    if (normalizedSz <= 0) {
      throw new Error(`Grid size ${sz} normalized to zero with lot size ${this.lotSize}`);
    }
    let attempt = 0;
    while (attempt < PLACE_RETRY_ATTEMPTS) {
      const clientId = `grid-${this.config.symbol}-${index}-${Date.now()}-${attempt}-${Math.random().toString(36).slice(2, 8)}`;
      const order: Order = {
        clientId,
        symbol: this.config.symbol,
        side,
        px: normalizedPx,
        sz: normalizedSz,
        tif: 'GTC',
        postOnly: true,
        accountId: this.accountId
      };
      try {
        this.logger.debug({ action: 'place_grid_order', index, side, px: normalizedPx, sz: normalizedSz, clientId, attempt }, 'Placing grid order');
        const orderId = await this.router.sendLimitChild(order);
        const trackedClientId = order.clientId;
        this.grids.set(index, {
          index,
          side,
          px: normalizedPx,
          sz: normalizedSz,
          orderId,
          clientId: trackedClientId,
          filled: false,
          timestamp: Date.now()
        });
        this.logger.debug({ index, side, px: normalizedPx, sz: normalizedSz, orderId, clientId: trackedClientId }, 'Grid order placed successfully');
        this.consecutivePlaceFailures = 0;
        return orderId;
      } catch (error) {
        attempt += 1;
        const normalizedError = normalizeError(error);

        // 增强日志：记录盘口、mid、spread 等详细信息
        const book = this.shadowBook.snapshot(this.config.symbol);
        const enhancedLog: any = {
          error: normalizedError,
          index,
          side,
          px: normalizedPx,
          sz: normalizedSz,
          attempt,
          currentGridStepBps: this.currentGridStepBps
        };
        if (book?.bids?.[0] && book?.asks?.[0]) {
          const mid = (book.bids[0].px + book.asks[0].px) / 2;
          const spread = book.asks[0].px - book.bids[0].px;
          const spreadBps = (spread / mid) * 10_000;
          enhancedLog.marketData = {
            mid: mid.toFixed(2),
            bestBid: book.bids[0].px,
            bestAsk: book.asks[0].px,
            spreadBps: spreadBps.toFixed(2),
            pxDeviationFromMid: ((normalizedPx - mid) / mid * 10_000).toFixed(2) + 'bps'
          };
        }

        if (attempt >= PLACE_RETRY_ATTEMPTS) {
          // PostOnly 拒单最后一次尝试：重新读取盘口，调整价格后再试
          const isPostOnlyError = normalizedError.message?.toLowerCase().includes('post') ||
                                   normalizedError.message?.toLowerCase().includes('cross');
          const latestBook = this.shadowBook.snapshot(this.config.symbol);
          const bestBid = latestBook?.bids?.[0]?.px;
          const bestAsk = latestBook?.asks?.[0]?.px;
          if (isPostOnlyError && bestBid && bestAsk) {
            this.logger.info({
              ...enhancedLog,
              action: 'postonly_retry_with_adjusted_price'
            }, 'PostOnly rejection detected, retrying with adjusted price based on current orderbook');

            try {
              const freshMid = (bestBid + bestAsk) / 2;
              const cushionBps = this.adaptiveConfig?.postOnlyCushionBps ?? 5;
              const cushionRatio = cushionBps / 10_000;

              let adjustedPx: number;
              if (side === 'buy') {
                // 买单：在 bestBid 下方留出缓冲
                adjustedPx = bestBid * (1 - cushionRatio);
              } else {
                // 卖单：在 bestAsk 上方留出缓冲
                adjustedPx = bestAsk * (1 + cushionRatio);
              }

              const adjustedNormalizedPx = this.normalizePrice(side, adjustedPx);
              const retryClientId = `grid-${this.config.symbol}-${index}-${Date.now()}-postonly-retry`;
              const retryOrder: Order = {
                clientId: retryClientId,
                symbol: this.config.symbol,
                side,
                px: adjustedNormalizedPx,
                sz: normalizedSz,
                tif: 'GTC',
                postOnly: true,
                accountId: this.accountId
              };

              this.logger.debug({
                originalPx: normalizedPx,
                adjustedPx: adjustedNormalizedPx,
                freshMid,
                cushionBps,
                side,
                index
              }, 'Retrying with adjusted price');

              const orderId = await this.router.sendLimitChild(retryOrder);
              const trackedClientId = retryOrder.clientId;
              this.grids.set(index, {
                index,
                side,
                px: adjustedNormalizedPx,
                sz: normalizedSz,
                orderId,
                clientId: trackedClientId,
                filled: false,
                timestamp: Date.now()
              });
              this.logger.info({
                index,
                side,
                originalPx: normalizedPx,
                adjustedPx: adjustedNormalizedPx,
                orderId,
                clientId: trackedClientId
              }, 'PostOnly retry succeeded with adjusted price');
              this.consecutivePlaceFailures = 0;
              return orderId;
            } catch (retryError) {
              this.logger.warn({
                index,
                side,
                retryError: normalizeError(retryError)
              }, 'PostOnly retry with adjusted price also failed');
              // 继续抛出原始错误
            }
          }

          this.consecutivePlaceFailures += 1;
          this.logger.error({
            ...enhancedLog,
            attempts: attempt,
            consecutivePlaceFailures: this.consecutivePlaceFailures
          }, 'Failed to place grid order after all retries');
          throw error;
        }
        const delay = PLACE_RETRY_BASE_DELAY_MS * attempt;
        this.logger.warn({ ...enhancedLog, retryDelayMs: delay }, 'Grid order placement failed, retrying');
        await sleep(delay);
      }
    }
    throw new Error('unreachable');
  }

  private async cancelGridLevel(level: GridLevel): Promise<void> {
    const orderId = level.orderId;
    if (!orderId) return;
    if (this.pendingCancellations.has(orderId)) {
      this.logger.debug({ orderId }, 'Cancel request skipped because it is already pending');
      return;
    }
    this.logger.debug({ orderId }, 'Canceling grid order');
    let cancelled = false;
    const snapshot = { ...level };
    this.pendingCancellations.add(orderId);
    this.grids.delete(level.index);
    try {
      await this.router.cancel(orderId);
      cancelled = true;
    } catch (error) {
      const normalized = normalizeError(error);
      if (isIgnorableCancelError(normalized)) {
        this.logger.info({ orderId, error: normalized }, 'Treating order cancel as already completed');
        cancelled = true;
      } else {
        this.logger.warn({ orderId, error: normalized }, 'Grid cancel by orderId failed');
        if (level.clientId) {
          try {
            await this.router.cancelByClientId(level.clientId);
            cancelled = true;
          } catch (fallbackError) {
            const fallbackNormalized = normalizeError(fallbackError);
            if (isIgnorableCancelError(fallbackNormalized)) {
              this.logger.info({ orderId, clientId: level.clientId, error: fallbackNormalized }, 'Cancel by clientId treated as success');
              cancelled = true;
            } else {
              this.logger.error(
                { orderId, clientId: level.clientId, error: fallbackNormalized },
                'Grid cancel by clientId failed'
              );
            }
          }
        }
      }
      if (!cancelled && isIgnorableCancelError(normalized)) {
        this.logger.info({ orderId, error: normalized }, 'Order cancel considered complete');
        cancelled = true;
      }
      if (!cancelled) {
        // 还原本地状态，等待下次重试
        this.grids.set(snapshot.index, snapshot);
        throw error;
      }
    } finally {
      this.pendingCancellations.delete(orderId);
      if (cancelled) {
        this.releaseOrder(orderId, level.clientId);
        this.logger.debug({ orderId }, 'Grid order cancel confirmed');
      }
    }
  }

  private normalizePrice(side: Side, rawPx: number): number {
    if (this.tickSize <= 0) return rawPx;
    const factor = rawPx / this.tickSize;
    const adjusted = side === 'buy' ? Math.floor(factor) : Math.ceil(factor);
    const normalized = adjusted * this.tickSize;
    if (!Number.isFinite(normalized) || normalized <= 0) {
      throw new Error(`Normalized price ${normalized} invalid for tick size ${this.tickSize}`);
    }
    return normalized;
  }

  private normalizeSize(rawSz: number): number {
    if (this.lotSize <= 0) return rawSz;
    const factor = rawSz / this.lotSize;
    const adjusted = Math.floor(factor);
    const normalized = adjusted * this.lotSize;
    return Number(normalized.toFixed(10));
  }

  async shutdown(): Promise<void> {
    this.logger.info({ gridSize: this.grids.size, action: 'shutdown_start' }, 'Starting GridMaker shutdown');
    try {
      await this.cancelAllOrders(this.config.symbol);
      this.logger.info({ action: 'shutdown_cancelAll_complete' }, 'cancelAll during shutdown completed');
    } catch (error) {
      this.logger.warn({ error: normalizeError(error), action: 'shutdown_cancelAll_failed' }, 'cancel_all during shutdown failed');
    }
    const levels = Array.from(this.grids.values());
    await Promise.all(levels.map(level =>
      level.orderId
        ? this.cancelGridLevel(level).catch(error =>
            this.logger.error({ orderId: level.orderId, clientId: level.clientId, error: normalizeError(error) }, 'Failed to cancel grid order during shutdown')
          )
        : Promise.resolve()
    ));
    this.grids.clear();
    this.isInitialized = false;
    this.pendingHedges.clear();
    this.logger.info({ action: 'shutdown_complete' }, 'GridMaker shutdown completed');
  }

  updateBaseClipUsd(newClipUsd: number): void {
    if (!Number.isFinite(newClipUsd) || newClipUsd <= 0) {
      this.logger.warn({ newClipUsd }, 'Ignored invalid base clip update');
      return;
    }
    if (Math.abs(newClipUsd - this.baseClipUsd) < 1e-6) return;
    this.baseClipUsd = newClipUsd;
    this.logger.info({ baseClipUsd: this.baseClipUsd }, 'Updated base clip size from equity ratio');
  }

  /**
   * 根据 orderId 查找网格层
   */
  private findGridLevel(orderId: string): GridLevel | undefined {
    for (const grid of this.grids.values()) {
      if (grid.orderId === orderId) {
        return grid;
      }
    }
    return undefined;
  }

  /**
   * 更新 Delta
   */
  private updateDelta(fill: Fill): void {
    const deltaChange = fill.side === 'buy' ? fill.sz : -fill.sz;
    this.currentDelta += deltaChange;

    this.logger.info({
      fillSide: fill.side,
      fillSz: fill.sz,
      deltaChange,
      currentDelta: this.currentDelta
    }, 'Delta updated');
  }

  /**
   * 检查是否需要对冲
   */
  private async maybeHedge(): Promise<void> {
    const absDelta = Math.abs(this.currentDelta);

    if (absDelta >= this.config.hedgeThresholdBase) {
      this.logger.info({
        currentDelta: this.currentDelta,
        threshold: this.config.hedgeThresholdBase,
        pendingHedges: this.pendingHedges.size
      }, 'Hedge threshold reached, triggering hedge');

      try {
        const result = await this.hedgeEngine.maybeHedge(this.config.symbol, this.currentDelta);
        if (Math.abs(result.hedged) > 1e-8) {
          const key = result.orderId ?? result.clientId;
          if (key) {
            this.pendingHedges.set(key, { qty: result.hedged, ts: Date.now() });
          }
          this.logger.info({
            hedged: result.hedged,
            orderId: result.orderId,
            clientId: result.clientId,
            pendingHedges: this.pendingHedges.size,
            currentDelta: this.currentDelta
          }, 'Hedge order placed, awaiting fill for delta correction');
        }
      } catch (error) {
        this.logger.error({ error, currentDelta: this.currentDelta }, 'Hedge failed');
      }
    }
  }

  async onTick(): Promise<void> {
    if (this.needsReinit && !this.resetting) {
      this.logger.info({ action: 'onTick_reinit', currentGridStepBps: this.currentGridStepBps }, 'Triggering grid reset due to scheduled reinitialization');
      await this.reset();
      return;
    }
    if (!this.isInitialized) return;
    const mid = this.shadowBook.mid(this.config.symbol);
    if (!mid) return;
    if (this.volatilityEstimator) {
      this.volatilityEstimator.update(mid);
    }
    this.prunePendingHedges(Date.now());
    await this.maybeAdjustGridStep();
    await this.maybeRecenterGrid(mid);
  }

  private async maybeAdjustGridStep(): Promise<void> {
    // 优先使用成交率闭环控制
    if (this.fillRateControlEnabled && this.fillRateMonitor && this.fillRateController) {
      const metrics = this.fillRateMonitor.getMetrics(this.config.symbol);

      // 如果没有足够成交数据，回退到价差自适应（混合模式）
      if (!metrics) {
        this.logger.debug({}, 'Insufficient fill rate data, falling back to spread-based adaptive');
        // 继续执行后面的价差自适应逻辑（不return）
      } else {
        // 有成交数据，使用成交率控制
        return await this.executeFillRateControl(metrics);
      }
    }

    // 价差自适应（作为后备或冷启动策略）
    await this.executeSpreadBasedAdaptive();
  }

  private async executeFillRateControl(metrics: any): Promise<void> {
    if (!this.fillRateController) return;

    observeFillRateMetrics(this.config.symbol, {
      fillsPerMinute: metrics.fillsPerMinute,
      makerRatio: metrics.makerRatio,
      selfTradeRatio: metrics.selfTradeRatio
    });

    // 使用 PI 控制器计算目标参数
    const controlOutput = this.fillRateController.compute(metrics);

    // 检查是否需要调整
    const stepChangeRatio = Math.abs(controlOutput.gridStepBps - this.currentGridStepBps) / this.currentGridStepBps;
    const clipChangeRatio = Math.abs(controlOutput.clipUsd - this.baseClipUsd) / this.baseClipUsd;

    if (stepChangeRatio > 0.05 || clipChangeRatio > 0.05 || controlOutput.emergencyMode) {
      this.logger.info({
        metrics: {
          fillsPerMinute: metrics.fillsPerMinute.toFixed(2),
          makerRatio: (metrics.makerRatio * 100).toFixed(1) + '%',
          selfTradeRatio: (metrics.selfTradeRatio * 100).toFixed(1) + '%'
        },
        before: {
          gridStepBps: this.currentGridStepBps.toFixed(2),
          clipUsd: this.baseClipUsd.toFixed(1)
        },
        after: {
          gridStepBps: controlOutput.gridStepBps.toFixed(2),
          clipUsd: controlOutput.clipUsd.toFixed(1)
        },
        emergencyMode: controlOutput.emergencyMode,
        reason: controlOutput.reason
      }, 'Adjusting grid parameters based on fill rate KPI');

      this.currentGridStepBps = controlOutput.gridStepBps;
      this.baseClipUsd = controlOutput.clipUsd;

      // 重新初始化网格
      if (this.incrementalMode && this.isInitialized && !this.resetting) {
        await this.initialize();
      } else {
        await this.reset();
      }
    } else {
      this.logger.debug({
        metrics: {
          fillsPerMinute: metrics.fillsPerMinute.toFixed(2),
          makerRatio: (metrics.makerRatio * 100).toFixed(1) + '%'
        },
        stepChangeRatio: stepChangeRatio.toFixed(3),
        clipChangeRatio: clipChangeRatio.toFixed(3)
      }, 'Fill rate control - no adjustment needed');
    }
  }

  private async executeSpreadBasedAdaptive(): Promise<void> {
    if (!this.adaptiveConfig?.enabled) {
      return;
    }

    const {
      minGridStepBps,
      maxGridStepBps,
      minStepChangeRatio,
      minLayers
    } = this.adaptiveConfig;

    // 基于价差的自适应：读取当前盘口价差，按固定比例调整网格间距
    const book = this.shadowBook.snapshot(this.config.symbol);
    if (!book?.bids?.[0] || !book?.asks?.[0]) {
      this.logger.debug({}, 'No book data available for spread-based adaptive');
      return;
    }

    const bestBid = book.bids[0].px;
    const bestAsk = book.asks[0].px;
    const mid = (bestBid + bestAsk) / 2;
    const spread = bestAsk - bestBid;
    const spreadBps = (spread / mid) * 10_000;

    // 目标网格间距 = 价差 × 固定比例（0.4-0.6x spread 适合微网格做市）
    const spreadRatio = 0.5;  // 50% of spread
    const targetStepFromSpread = spreadBps * spreadRatio;

    // 应用配置的上下限
    let finalTargetStep = clamp(targetStepFromSpread, minGridStepBps, maxGridStepBps);

    // 保证最小层数约束
    if (minLayers && minLayers > 0) {
      const maxStepForMinLayers = this.config.gridRangeBps / minLayers;
      if (finalTargetStep > maxStepForMinLayers) {
        const adjusted = Math.max(minGridStepBps, Math.min(maxStepForMinLayers, maxGridStepBps));
        if (adjusted < finalTargetStep - 1e-6) {
          this.logger.info({
            requestedStepBps: finalTargetStep.toFixed(2),
            adjustedStepBps: adjusted,
            minLayers,
            gridRangeBps: this.config.gridRangeBps
          }, 'Limiting grid step to preserve minimum layer count');
        }
        finalTargetStep = adjusted;
      }
    }

    // 计算变化幅度，避免频繁微调
    const changeRatio = Math.abs(finalTargetStep - this.currentGridStepBps) / this.currentGridStepBps;
    if (changeRatio < (minStepChangeRatio ?? 0.2)) {
      this.logger.debug({
        spreadBps: spreadBps.toFixed(2),
        spreadRatio,
        currentGridStepBps: this.currentGridStepBps,
        targetGridStepBps: finalTargetStep.toFixed(2),
        changeRatio: changeRatio.toFixed(3),
        minStepChangeRatio,
        reason: 'change_ratio_below_threshold'
      }, 'Skipping grid step adjustment - change too small (spread-based)');
      return;
    }

    this.logger.info({
      spreadBps: spreadBps.toFixed(2),
      spreadRatio,
      targetStepFromSpread: targetStepFromSpread.toFixed(2),
      currentGridStepBps: this.currentGridStepBps,
      targetGridStepBps: finalTargetStep.toFixed(2),
      changeRatio: changeRatio.toFixed(3)
    }, 'Adjusting grid step based on spread (micro-grid adaptive)');

    this.currentGridStepBps = finalTargetStep;
    if (this.incrementalMode && this.isInitialized && !this.resetting) {
      await this.initialize();  // initialize() 内部会调用 reconcileGrid 进行增量更新
    } else {
      await this.reset();
    }
  }

  private async maybeRecenterGrid(mid: number): Promise<void> {
    if (!this.adaptiveConfig?.recenterEnabled) return;
    const deviationBps = Math.abs(mid - this.gridCenter) / this.gridCenter * 10_000;
    if (deviationBps <= this.adaptiveConfig.recenterThresholdBps) return;

    const now = Date.now();
    const cooldown = this.adaptiveConfig.recenterCooldownMs ?? 300_000;
    if (now - this.lastRecenterTs < cooldown) {
      this.logger.debug({
        deviationBps,
        cooldownRemainingMs: cooldown - (now - this.lastRecenterTs)
      }, 'Recenter cooldown active');
      return;
    }

    this.logger.warn({
      deviationBps,
      previousCenter: this.gridCenter,
      newCenter: mid
    }, 'Grid center drift detected, resetting grid');

    this.lastRecenterTs = now;
    if (this.incrementalMode && this.isInitialized && !this.resetting) {
      this.gridCenter = mid;
      await this.initialize();  // initialize() 内部会调用 reconcileGrid 进行增量更新
    } else {
      await this.reset();
    }
    return;
  }

  private prunePendingHedges(now: number): void {
    const timeout =
      this.adaptiveConfig?.hedgePendingTimeoutMs ?? HEDGE_PENDING_TIMEOUT_MS;
    for (const [orderId, entry] of this.pendingHedges) {
      if (now - entry.ts > timeout) {
        this.pendingHedges.delete(orderId);
        this.logger.warn({
          orderId,
          ageMs: now - entry.ts,
          timeoutMs: timeout
        }, 'Pending hedge expired without fill');
      }
    }
  }
}

export interface AdaptiveGridConfig {
  enabled: boolean;
  volatilityWindowMinutes: number;
  minVolatilityBps: number;
  maxVolatilityBps: number;
  minGridStepBps: number;
  maxGridStepBps: number;
  recenterEnabled: boolean;
  recenterThresholdBps: number;
  recenterCooldownMs: number;
  minStepChangeRatio?: number;
  minSamples?: number;
  maxCadenceMs?: number;
  hedgePendingTimeoutMs?: number;
  postOnlyCushionBps?: number;
  minLayers?: number;
  maxPlacementConcurrency?: number;
  placementBatchDelayMs?: number;
  rateLimitBackoffMs?: number;
}

function clamp(value: number, min: number, max: number): number {
  return Math.min(Math.max(value, min), max);
}

function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function normalizeError(error: unknown): { message?: string; name?: string; status?: number; code?: string } | undefined {
  if (!error) return undefined;
  if (error instanceof Error) {
    const normalized: { message: string; name: string; status?: number; code?: string } = {
      message: error.message,
      name: error.name
    };
    const anyErr = error as any;
    if (typeof anyErr.status === 'number') {
      normalized.status = anyErr.status;
    }
    if (anyErr.code !== undefined) {
      normalized.code = String(anyErr.code);
    }
    return normalized;
  }
  if (typeof error === 'object') {
    return error as any;
  }
  return { message: String(error) };
}

function isIgnorableCancelError(error?: { status?: number; code?: string; name?: string; message?: string }): boolean {
  if (!error) return false;
  if (error.status === 404) return true;
  if (error.status === 422 && (error.code === '5' || error.code === 'order_not_active')) {
    return true;
  }
  if (error.status === 400 && (error.name === 'PacificaValidationError' || error.message?.toLowerCase().includes('invalid message'))) {
    return true;
  }
  return false;
}
