import type { SymbolMetrics } from "./types";

export interface ScorerOptions {
  spreadWeight?: number;
  depthWeight?: number;
  volumeWeight?: number;
  fundingWeight?: number;
  maxSpreadBps?: number;
  minDepthUsd?: number;
  minFundingCorrelation?: number;
  freshnessThresholdMs?: number;
  enableThreshold?: number;
}

const DEFAULTS: Required<ScorerOptions> = {
  spreadWeight: 0.25,
  depthWeight: 0.25,
  volumeWeight: 0.25,
  fundingWeight: 0.25,
  maxSpreadBps: 200,
  minDepthUsd: 50_000,
  minFundingCorrelation: 0.75,
  freshnessThresholdMs: 5_000,
  enableThreshold: 0.55
};

export class SymbolScorer {
  private readonly opts: Required<ScorerOptions>;

  constructor(options: ScorerOptions = {}) {
    this.opts = { ...DEFAULTS, ...options };
  }

  score(metrics: SymbolMetrics): number {
    const spreadScore = this.normalizeSpread(metrics.spreadBps);
    const depthScore = this.normalizeDepth(metrics.topDepthUsd);
    const volumeScore = this.normalizeVolume(metrics.volumePerMin);
    const fundingScore = this.normalizeFunding(metrics.fundingCorrelation);
    const freshnessPenalty = this.computeFreshnessPenalty(metrics.dataLatencyMs);

    const weighted =
      spreadScore * this.opts.spreadWeight +
      depthScore * this.opts.depthWeight +
      volumeScore * this.opts.volumeWeight +
      fundingScore * this.opts.fundingWeight -
      freshnessPenalty;

    return clamp(weighted, 0, 1);
  }

  shouldEnable(metrics: SymbolMetrics): boolean {
    if (metrics.spreadBps > this.opts.maxSpreadBps) return false;
    if (metrics.topDepthUsd < this.opts.minDepthUsd) return false;
    if (metrics.fundingCorrelation < this.opts.minFundingCorrelation) return false;
    if (
      metrics.dataLatencyMs !== undefined &&
      metrics.dataLatencyMs > this.opts.freshnessThresholdMs * 3
    ) {
      return false;
    }
    return this.score(metrics) >= this.opts.enableThreshold;
  }

  private normalizeSpread(spreadBps: number): number {
    if (spreadBps <= 0) return 1;
    if (spreadBps >= this.opts.maxSpreadBps) return 0;
    return 1 - spreadBps / this.opts.maxSpreadBps;
  }

  private normalizeDepth(depthUsd: number): number {
    if (depthUsd <= 0) return 0;
    const normalized = Math.log10(depthUsd / this.opts.minDepthUsd);
    return clamp(normalized / 2, 0, 1);
  }

  private normalizeVolume(volumePerMin: number): number {
    if (volumePerMin <= 0) return 0;
    const normalized = Math.log10(volumePerMin + 1) / 4;
    return clamp(normalized, 0, 1);
  }

  private normalizeFunding(correlation: number): number {
    return clamp((correlation + 1) / 2, 0, 1);
  }

  private computeFreshnessPenalty(latencyMs?: number): number {
    if (latencyMs === undefined) return 0;
    if (latencyMs <= this.opts.freshnessThresholdMs) return 0;
    const exceeded = latencyMs - this.opts.freshnessThresholdMs;
    const penalty = exceeded / (this.opts.freshnessThresholdMs * 10);
    return clamp(penalty, 0, 0.3);
  }
}

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