const BASE = '/api';

async function request<T>(path: string, options?: RequestInit): Promise<T> {
  const res = await fetch(`${BASE}${path}`, {
    headers: { 'Content-Type': 'application/json', ...options?.headers },
    ...options,
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({ detail: res.statusText }));
    throw new Error(err.detail || `HTTP ${res.status}`);
  }
  return res.json();
}

// --- Experiments ---
export interface ExperimentMeta {
  name: string;
  id: number | null;
  parent: string | null;
  version_family: string | null;
  generation: number | null;
  status: string;
  created_at: string | null;
  created_by: string | null;
  tags: string[];
  aliases: string[];
  description: string | null;
  changelog: string | null;
  has_journal_entry: boolean;
  sqs_score: number | null;
}

export interface ExperimentConfig {
  experiment_name: string;
  dataset_snapshot_id: string;
  base_config: string;
  description: string | null;
  overrides: Record<string, unknown>;
  strategy_engines: Record<string, unknown>[];
  splits: unknown[];
  tags: string[];
  notes: string | null;
  id: number | null;
  parent: string | null;
  status: string;
  generation: number | null;
  version_family: string | null;
  created_at: string | null;
  aliases: string[];
  changelog: string | null;
  performance_summary: Record<string, unknown> | null;
  baked_sleeves?: {
    parking?: string | null;
    idle_alpha?: string | null;
    form4_sleeve?: string | null;
    ownership_sleeve?: string | null;
    risk_off_sleeve?: string | null;
  };
}

export interface LineageNode {
  name: string;
  meta: Partial<ExperimentMeta>;
  depth: number;
  children: LineageNode[];
}

export const experimentsApi = {
  list: (params?: {
    status?: string; family?: string; tag?: string; pattern?: string;
    sort_by?: string; limit?: number; offset?: number; has_journal?: boolean;
    include_retired?: boolean;
  }) => {
    const q = new URLSearchParams();
    if (params?.status) q.set('status', params.status);
    if (params?.family) q.set('family', params.family);
    if (params?.tag) q.set('tag', params.tag);
    if (params?.pattern) q.set('pattern', params.pattern);
    if (params?.sort_by) q.set('sort_by', params.sort_by);
    if (params?.limit != null) q.set('limit', String(params.limit));
    if (params?.offset != null) q.set('offset', String(params.offset));
    if (params?.has_journal != null) q.set('has_journal', String(params.has_journal));
    if (params?.include_retired != null) q.set('include_retired', String(params.include_retired));
    return request<{ experiments: ExperimentMeta[]; total: number }>(`/experiments?${q}`);
  },

  get: (name: string) => request<ExperimentConfig>(`/experiments/${encodeURIComponent(name)}`),

  lineage: (name: string) =>
    request<{ tree: LineageNode; ancestor_chain: unknown[]; current: string }>(
      `/experiments/${encodeURIComponent(name)}/lineage`,
    ),

  diff: (a: string, b: string) =>
    request<Record<string, [unknown, unknown]>>(
      `/experiments/${encodeURIComponent(a)}/diff/${encodeURIComponent(b)}`,
    ),

  setStatus: (name: string, status: string, alias?: string) =>
    request<{ name: string; status: string }>(`/experiments/${encodeURIComponent(name)}/status`, {
      method: 'PUT',
      body: JSON.stringify({ status, alias }),
    }),

  create: (parent: string, name: string, changelog?: string) =>
    request<{ name: string; parent: string }>('/experiments', {
      method: 'POST',
      body: JSON.stringify({ parent, name, changelog }),
    }),

  compose: (body: {
    base: string; name: string; changelog?: string;
    parking?: string | null; idle_alpha?: string | null;
    form4_sleeve?: string | null; ownership_sleeve?: string | null;
    risk_off_sleeve?: string | null;
  }) =>
    request<{ name: string; parent: string }>('/experiments/compose', {
      method: 'POST',
      body: JSON.stringify(body),
    }),

  update: (name: string, config: ExperimentConfig) =>
    request<{ name: string; updated: boolean }>(`/experiments/${encodeURIComponent(name)}`, {
      method: 'PUT',
      body: JSON.stringify(config),
    }),

  delete: (name: string) =>
    request<{ name: string; deleted: boolean }>(`/experiments/${encodeURIComponent(name)}`, {
      method: 'DELETE',
    }),

  families: () => request<{ families: string[] }>('/experiments/families'),
  tags: () => request<{ tags: string[] }>('/experiments/tags'),
  rebuildIndex: () => request<{ rebuilt: boolean }>('/experiments/rebuild-index', { method: 'POST' }),
};

// --- Leaderboard ---
export interface LeaderboardEntry {
  rank: number;
  entry_id: string;
  config_id: number | null;
  experiment_name: string;
  strategy_family: string;
  is_retired: boolean;
  sqs_score: number | null;
  reset_common_window_score: number | null;
  rqs_score: number | null;
  wfqs_score: number | null;
  deployment_score: number | null;
  promotion_score: number | null;
  // test split
  total_return_pct: number | null;
  annualized_return_pct: number | null;
  win_rate: number | null;
  sharpe_ratio: number | null;
  max_drawdown_pct: number | null;
  trade_count: number;
  avg_gross_exposure_pct: number | null;
  days_in_market_pct: number | null;
  profit_factor: number | null;
  // train
  train_total_return_pct: number | null;
  // valid split
  valid_total_return_pct: number | null;
  timestamp: string;
}

export const leaderboardApi = {
  list: (params?: { sort_by?: string; include_retired?: boolean; top?: number; offset?: number }) => {
    const q = new URLSearchParams();
    if (params?.sort_by) q.set('sort_by', params.sort_by);
    if (params?.include_retired) q.set('include_retired', String(params.include_retired));
    if (params?.top != null) q.set('top', String(params.top));
    if (params?.offset != null) q.set('offset', String(params.offset));
    return request<{ entries: LeaderboardEntry[]; total: number; sort_by: string }>(`/leaderboard?${q}`);
  },

  getEntry: (entryId: string) =>
    request<Record<string, unknown>>(`/leaderboard/entry/${encodeURIComponent(entryId)}`),

  getExperimentEntry: (name: string) =>
    request<Record<string, unknown>>(`/leaderboard/experiment/${encodeURIComponent(name)}`),

  refresh: () => request<{ entries: number; rebuilt: boolean }>('/leaderboard/refresh', { method: 'POST' }),
};

// --- Runs ---
export const runsApi = {
  list: (params?: { experiment?: string; limit?: number }) => {
    const q = new URLSearchParams();
    if (params?.experiment) q.set('experiment', params.experiment);
    if (params?.limit != null) q.set('limit', String(params.limit));
    return request<{ runs: unknown[]; total: number }>(`/runs?${q}`);
  },
  metadata: (runId: string) => request<Record<string, unknown>>(`/runs/${runId}/metadata`),
  metrics: (runId: string) => request<Record<string, unknown>>(`/runs/${runId}/metrics`),
  equityCurve: (runId: string) => request<{ data: Record<string, unknown>[]; format: string }>(`/runs/${runId}/equity-curve`),
  tradeBlotter: (runId: string, params?: { limit?: number; offset?: number }) => {
    const q = new URLSearchParams();
    if (params?.limit != null) q.set('limit', String(params.limit));
    if (params?.offset != null) q.set('offset', String(params.offset));
    return request<{ trades: Record<string, unknown>[]; total: number }>(`/runs/${runId}/trade-blotter?${q}`);
  },
  perEngineMetrics: (runId: string) => request<Record<string, unknown>>(`/runs/${runId}/per-engine-metrics`),
};

// --- Backtest ---
export interface BacktestRequest {
  experiment_name: string;
  capital?: number;
  start?: string | null;    // YYYY-MM-DD or YYYY
  end?: string | null;      // YYYY-MM-DD
  year?: string | null;     // YYYY shorthand
  no_trades?: boolean;
  mode?: string;            // "cli" (default) or "direct" (in-process)
  parking?: string | null;  // cash parking preset name
  idle_alpha?: string | null;  // idle alpha sleeve preset name
  form4_sleeve?: string | null; // Form 4 residual-cash sleeve preset name
  ownership_sleeve?: string | null; // 13D/13G ownership sleeve preset name
  risk_off_sleeve?: string | null; // risk-off alpha sleeve preset name
  non_core_allocator_v2_mode?: 'shadow' | 'live' | null;
  snapshot_id?: string | null; // override dataset snapshot
  fixed_capital?: boolean;
}

export interface BacktestLastParams {
  capital?: number | null;
  start?: string | null;
  end?: string | null;
  year?: string | null;
}

export interface BacktestTask {
  task_id: string;
  experiment_name: string;
  capital: number;
  start_date: string | null;
  end_date: string | null;
  year: string | null;
  no_trades: boolean;
  status: 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
  created_at: string;
  started_at: string | null;
  finished_at: string | null;
  run_id: string | null;
  error: string | null;
  mode?: 'cli' | 'direct';
  has_direct_result?: boolean;
  fixed_capital?: boolean;
  parking?: string | null;
  idle_alpha?: string | null;
  form4_sleeve?: string | null;
  ownership_sleeve?: string | null;
  risk_off_sleeve?: string | null;
  non_core_allocator_v2_mode?: 'shadow' | 'live' | null;
  snapshot_id?: string | null;
  result_summary?: {
    return_pct: number | null;
    max_dd_pct: number | null;
    sharpe: number | null;
    win_rate: number | null;
    trade_count: number | null;
    snapshot_coverage_end_date?: string | null;
    snapshot_last_refresh_utc?: string | null;
  } | null;
}

export interface DirectResult {
  session_name: string;
  initial_equity: number;
  equity_curve: { date: string; equity: number }[];
  trades: Record<string, unknown>[];
  summary: {
    return_pct: number;
    final_equity: number;
    max_dd_pct: number;
    trade_count: number;
    win_rate: number;
    sharpe: number;
  };
  metrics_bundle: Record<string, unknown>;
  snapshot_coverage_end_date?: string | null;
  snapshot_last_refresh_utc?: string | null;
}

export const backtestApi = {
  submit: (req: BacktestRequest) =>
    request<BacktestTask>('/backtest/submit', { method: 'POST', body: JSON.stringify(req) }),

  submitBatch: (names: string[], params: Omit<BacktestRequest, 'experiment_name'>) =>
    request<{ tasks: BacktestTask[] }>('/backtest/submit-batch', {
      method: 'POST',
      body: JSON.stringify({ experiment_names: names, ...params }),
    }),

  tasks: () => request<{ tasks: BacktestTask[] }>('/backtest/tasks'),

  task: (id: string) => request<BacktestTask>(`/backtest/tasks/${id}`),

  cancel: (id: string) =>
    request<{ cancelled: boolean }>(`/backtest/tasks/${id}`, { method: 'DELETE' }),

  taskLog: (id: string) =>
    request<{ log: string; total_lines?: number }>(`/backtest/tasks/${id}/log`),

  lastParams: (name: string) =>
    request<BacktestLastParams>(`/backtest/last-params/${encodeURIComponent(name)}`),

  directResult: (taskId: string) =>
    request<DirectResult>(`/backtest/tasks/${taskId}/direct-result`),

  parkingPresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/parking-presets'),

  idleAlphaPresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/idle-alpha-presets'),

  form4CapturePresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/form4-capture-presets'),

  ownershipCapturePresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/ownership-capture-presets'),

  riskOffAlphaPresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/risk-off-alpha-presets'),

  snapshots: () =>
    request<{ snapshots: { id: string; start: string | null; end: string | null; rows: number | null }[] }>('/backtest/snapshots'),
};

// --- Intraday Backtest (ORB) ---

export interface IntradayTask {
  task_id: string;
  config: string;
  days: number;
  universe: string;
  universe_label?: string | null;
  year: string | null;
  start_date: string | null;
  end_date: string | null;
  compound_returns: boolean | null;
  daily_budget_reset?: boolean | null;
  status: 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
  created_at: string;
  started_at: string | null;
  finished_at: string | null;
  pid: number | null;
  error: string | null;
  result_file: string | null;
  result_summary: {
    return_pct: number | null;
    max_dd_pct: number | null;
    sharpe: number | null;
    win_rate: number | null;
    trade_count: number | null;
    final_equity: number | null;
    calmar: number | null;
    loss_containment_score?: number | null;
    avg_loss_day_pct?: number | null;
    tail_loss_20_pct?: number | null;
    worst_day_return_pct?: number | null;
  } | null;
}

export interface IntradayTrade {
  date: string;
  ticker: string;
  entry_price: number;
  exit_price: number;
  entry_time: string;
  exit_time: string;
  shares: number;
  pnl: number;
  pnl_pct: number;
  exit_reason: string;
  morning_gain_pct: number;
  slippage_cost: number;
  orb_direction: string | null;
  rvol: number | null;
  atr_at_entry: number | null;
  r_multiple_at_exit: number | null;
}

export interface IntradayResult {
  run_id: string;
  generated_at: string;
  config: Record<string, unknown>;
  metrics: {
    run_id: string;
    start_date: string;
    end_date: string;
    trading_days: number;
    days_with_activity?: number;
    days_with_trades?: number;
    total_trades: number;
    idle_sleeve_entry_days?: number;
    idle_sleeve_positions_opened?: number;
    win_rate: number | null;
    avg_win_pct: number | null;
    avg_loss_pct: number | null;
    profit_factor: number | null;
    total_return_pct: number | null;
    annualized_return_pct: number | null;
    max_drawdown_pct: number | null;
    sharpe_ratio: number | null;
    calmar_ratio: number | null;
    initial_capital: number;
    final_equity: number;
    avg_loss_day_pct?: number | null;
    tail_loss_20_pct?: number | null;
    worst_day_return_pct?: number | null;
    loss_containment_score?: number | null;
  };
  trades: IntradayTrade[];
  daily_summary: {
    date: string;
    daily_pnl: number;
    daily_return_pct: number;
    candidates_found: number;
    trades: number;
    activity_count?: number;
    idle_sleeve_positions_opened?: number;
    idle_sleeve_symbols_opened?: string[];
    idle_sleeve_labels_opened?: string[];
  }[];
}

export interface IntradayStrategy {
  slug: string;
  id?: number | null;
  name: string;
  description: string;
  days: number;
  universe: string;
  universe_label?: string | null;
  universe_symbols_file?: string | null;
  strategy_mode?: 'momentum' | 'orb' | 'adaptive';
  // Adaptive-mode-specific (also reusable by other modes)
  min_entry_dollar_volume?: number | null;
  atr_initial_multiplier?: number | null;
  atr_trail_multiplier?: number | null;
  position_sizing?: 'equal_weight' | 'inverse_atr' | string | null;
  phase1_min_gain?: number | null;
  phase1_max_per_day?: number | null;
  slippage_bps?: number | null;
  output_dir?: string;
  initial_capital: number;
  risk_per_trade_pct?: number | null;
  max_position_pct?: number | null;
  atr_stop_multiplier?: number | null;
  min_price?: number | null;
  min_avg_dollar_volume?: number | null;
  min_premarket_dollar_vol?: number | null;
  min_breakout_rel_vol?: number | null;
  min_rvol?: number | null;
  max_candidates?: number | null;
  daily_max_loss_pct?: number | null;
  max_stops_per_day?: number | null;
  breakeven_at_r?: number | null;
  trailing_at_r?: number | null;
  trailing_stop_atr_multiplier?: number | null;
  order_timeout_minutes?: number | null;
  settlement_days?: number | null;
  max_gap_pct?: number | null;
  sim_bar_minutes?: number;
  orb_minutes?: number;
  compound_returns?: boolean;
  daily_budget_reset?: boolean;
  single_trade_loss_cap_basis?: string | null;
  is_comp_equity_deployment?: boolean;
  deployment_kind?: string | null;
  deployment_source_slug?: string | null;
  entry_minutes_after_open?: number | null;
  exit_minutes_before_close?: number | null;
  top_n?: number | null;
  min_morning_gain_pct?: number | null;
  max_morning_gain_pct?: number | null;
  min_entry_volume?: number | null;
  stop_loss_pct?: number | null;
  trailing_stop_pct?: number | null;
  ticker_cooldown_days?: number | null;
  market_regime_spy_threshold?: number | null;
}

export type CreateStrategyPayload = Omit<IntradayStrategy, 'slug'>;
export type UpdateStrategyPayload = Partial<Omit<IntradayStrategy, 'slug'>>;

/** ORB strategy entry returned by GET /orb/strategies — always includes config_path. */
export interface OrbStrategyInfo {
  slug: string;
  name: string;
  description: string;
  builtin: boolean;
  config_path: string;
  orb_minutes?: number | null;
  sim_bar_minutes?: number | null;
  entry_direction?: string | null;
  risk_per_trade_pct?: number | null;
  atr_stop_multiplier?: number | null;
}

export const intradayApi = {
  submit: (req: {
    config: string;
    days: number;
    universe: string;
    year?: string | null;
    start_date?: string | null;
    end_date?: string | null;
    compound_returns?: boolean;
    daily_budget_reset?: boolean;
    initial_capital?: number;
  }) =>
    request<{ task_id: string; status: string }>('/intraday/backtest/submit', {
      method: 'POST',
      body: JSON.stringify(req),
    }),

  tasks: () => request<{ tasks: IntradayTask[] }>('/intraday/backtest/tasks'),

  task: (id: string) => request<IntradayTask>(`/intraday/backtest/tasks/${id}`),

  cancel: (id: string) =>
    request<{ task_id: string; status: string }>(`/intraday/backtest/tasks/${id}`, { method: 'DELETE' }),

  taskLog: (id: string) => request<{ log: string }>(`/intraday/backtest/tasks/${id}/log`),

  taskResult: (id: string) => request<IntradayResult>(`/intraday/backtest/tasks/${id}/result`),

  // Strategy CRUD
  strategies: () => request<{ strategies: IntradayStrategy[] }>('/intraday/strategies'),

  getStrategy: (slug: string) => request<IntradayStrategy>(`/intraday/strategies/${encodeURIComponent(slug)}`),

  createStrategy: (payload: CreateStrategyPayload) =>
    request<IntradayStrategy>('/intraday/strategies', {
      method: 'POST',
      body: JSON.stringify(payload),
    }),

  updateStrategy: (slug: string, payload: UpdateStrategyPayload) =>
    request<IntradayStrategy>(`/intraday/strategies/${encodeURIComponent(slug)}`, {
      method: 'PUT',
      body: JSON.stringify(payload),
    }),

  deleteStrategy: (slug: string) =>
    request<{ slug: string; deleted: boolean }>(`/intraday/strategies/${encodeURIComponent(slug)}`, {
      method: 'DELETE',
    }),

  copyStrategy: (slug: string) =>
    request<IntradayStrategy>(`/intraday/strategies/${encodeURIComponent(slug)}/copy`, {
      method: 'POST',
    }),

  createCompStrategy: (slug: string) =>
    request<IntradayStrategy>(`/intraday/strategies/${encodeURIComponent(slug)}/comp`, {
      method: 'POST',
    }),

  deleteTask: (id: string) =>
    request<{ task_id: string; deleted: boolean }>(`/intraday/backtest/tasks/${id}/delete`, { method: 'DELETE' }),
};

// --- Docs ---
export const docsApi = {
  get: (filename: string) =>
    request<{ content: string; filename: string }>(`/docs/${encodeURIComponent(filename)}`),
};

// --- Paper Trading ---

export interface PaperSession {
  session_id: string;
  session_name: string;
  config_path: string;
  initial_equity: number;
  created_at: string;
  status: 'active' | 'paused' | 'closed';
  parking_preset: string | null;
  idle_alpha_preset: string | null;
  form4_sleeve_preset: string | null;
  ownership_sleeve_preset: string | null;
  risk_off_alpha_sleeve_preset: string | null;
  current_equity: number;
  peak_equity: number;
  total_pnl: number;
  total_pnl_pct: number;
  drawdown_pct: number;
  trade_count: number;
  latest_date: string | null;
  kill_switch: boolean;
  cooldown_remaining: number;
  consecutive_losses: number;
  daily_new_risk_used: number;
  last_processed_date: string | null;
}

export interface PaperPosition {
  symbol: string;
  qty: number | null;
  avg_entry_price: number | null;
  current_price: number | null;
  unrealized_pl: number | null;
  unrealized_pl_pct: number | null;
  days_held: number | null;
  stop_price: number | null;
  target_price: number | null;
  entry_date: string | null;
  engine_id: string | null;
  trade_direction: string | null;
  _ghost?: boolean;
  _parking?: boolean;
}

export interface PaperTrade {
  trade_id: string;
  session_id: string;
  symbol: string;
  engine_id: string | null;
  trade_sleeve?: string | null;
  entry_date: string | null;
  exit_date: string;
  entry_price: number | null;
  exit_price: number;
  exit_reason: string;
  shares: number;
  entry_shares: number | null;
  net_pnl: number;
  r_multiple: number;
  holding_days: number;
}

export interface PaperSnapshot {
  session_id: string;
  date: string;
  equity: number;
  cash: number;
  market_value: number;
  daily_pnl: number | null;
  total_pnl: number | null;
  drawdown_pct: number | null;
  open_position_count: number | null;
}

export interface PaperTask {
  task_id: string;
  session_name: string;
  operation: string;
  status: 'running' | 'completed' | 'failed';
  created_at: string;
  started_at: string | null;
  finished_at: string | null;
  log?: string;
  error: string | null;
}

export interface PaperConfig {
  name: string;
  path: string;
  id: number | null;
  status: string;
  version_family: string | null;
  generation: number | null;
  parent: string | null;
}

export const paperApi = {
  // Sessions
  sessions: () => request<{ sessions: PaperSession[] }>('/paper/sessions'),

  createSession: (
    name: string,
    config: string,
    capital: number,
    parking?: string | null,
    idleAlpha?: string | null,
    form4Sleeve?: string | null,
    ownershipSleeve?: string | null,
    riskOffSleeve?: string | null,
  ) =>
    request<{ session_id: string; name: string; capital: number; config: string; parking?: string; idle_alpha?: string; form4_sleeve?: string; ownership_sleeve?: string; risk_off_sleeve?: string }>(
      '/paper/sessions',
      {
        method: 'POST',
        body: JSON.stringify({
          name,
          config,
          capital,
          parking: parking || null,
          idle_alpha: idleAlpha || null,
          form4_sleeve: form4Sleeve || null,
          ownership_sleeve: ownershipSleeve || null,
          risk_off_sleeve: riskOffSleeve || null,
        }),
      },
    ),

  parkingPresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/parking-presets'),

  idleAlphaPresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/idle-alpha-presets'),

  form4CapturePresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/form4-capture-presets'),

  ownershipCapturePresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/ownership-capture-presets'),

  riskOffAlphaPresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/risk-off-alpha-presets'),

  getSession: (sessionId: string) =>
    request<PaperSession>(`/paper/sessions/${encodeURIComponent(sessionId)}`),

  pauseSession: (sessionId: string) =>
    request<{ session_id: string; status: string }>(`/paper/sessions/${encodeURIComponent(sessionId)}/pause`, { method: 'POST' }),

  resumeSession: (sessionId: string) =>
    request<{ session_id: string; status: string }>(`/paper/sessions/${encodeURIComponent(sessionId)}/resume`, { method: 'POST' }),

  closeSession: (sessionId: string) =>
    request<{ deleted: boolean; positions_closed: number }>(`/paper/sessions/${encodeURIComponent(sessionId)}`, { method: 'DELETE' }),

  // Data
  positions: (sessionId: string) =>
    request<{ positions: PaperPosition[]; broker_available: boolean; broker_error?: string }>(
      `/paper/sessions/${encodeURIComponent(sessionId)}/positions`,
    ),

  trades: (sessionId: string, last?: number) => {
    const q = last != null ? `?last=${last}` : '';
    return request<{ trades: PaperTrade[]; total: number }>(
      `/paper/sessions/${encodeURIComponent(sessionId)}/trades${q}`,
    );
  },

  equity: (sessionId: string) =>
    request<{ snapshots: PaperSnapshot[]; initial_equity: number }>(
      `/paper/sessions/${encodeURIComponent(sessionId)}/equity`,
    ),

  // Run operations
  run: (sessionId: string, date?: string | null, force?: boolean) =>
    request<PaperTask>(`/paper/sessions/${encodeURIComponent(sessionId)}/run`, {
      method: 'POST',
      body: JSON.stringify({ date: date || null, force: force ?? false }),
    }),

  runClose: (sessionId: string, date?: string | null, force?: boolean) =>
    request<PaperTask>(`/paper/sessions/${encodeURIComponent(sessionId)}/run-close`, {
      method: 'POST',
      body: JSON.stringify({ date: date || null, force: force ?? false }),
    }),

  runOpen: (sessionId: string, date?: string | null, force?: boolean) =>
    request<PaperTask>(`/paper/sessions/${encodeURIComponent(sessionId)}/run-open`, {
      method: 'POST',
      body: JSON.stringify({ date: date || null, force: force ?? false }),
    }),

  runAll: () => request<{ tasks: PaperTask[] }>('/paper/run-all', { method: 'POST' }),

  // Tasks
  tasks: (sessionName?: string) => {
    const q = sessionName ? `?session_name=${encodeURIComponent(sessionName)}` : '';
    return request<{ tasks: PaperTask[] }>(`/paper/tasks${q}`);
  },

  task: (taskId: string) => request<PaperTask>(`/paper/tasks/${taskId}`),

  taskLog: (taskId: string) =>
    request<{ log: string; status: string }>(`/paper/tasks/${taskId}/log`),

  // Auto daemon
  autoStatus: () =>
    request<{
      running: boolean; pid: number | null;
      schedule: { name: string; label: string; et_time: string; et_iso: string; past: boolean; wait_secs: number }[];
      log_tail: string[];
    }>('/paper/auto'),

  autoStart: (sessions?: string[], dryRun?: boolean) =>
    request<{ started: boolean; pid: number; dry_run: boolean; sessions: string[] }>('/paper/auto/start', {
      method: 'POST',
      body: JSON.stringify({ sessions: sessions ?? [], dry_run: dryRun ?? false }),
    }),

  autoStop: () =>
    request<{ stopped: boolean; pid: number }>('/paper/auto/stop', { method: 'POST' }),

  autoLog: (lines?: number) =>
    request<{ log: string; lines: number; source?: string; tty?: boolean }>(`/paper/auto/log${lines ? `?lines=${lines}` : ''}`),

  // Configs
  configs: (params?: { status?: string; pattern?: string; limit?: number }) => {
    const q = new URLSearchParams();
    if (params?.status) q.set('status', params.status);
    if (params?.pattern) q.set('pattern', params.pattern);
    if (params?.limit != null) q.set('limit', String(params.limit));
    return request<{ configs: PaperConfig[]; total: number }>(`/paper/configs?${q}`);
  },
};

// --- ORB Paper Trading ---

export interface OrbSession {
  session_id: string;
  session_name: string;
  config_path: string;
  initial_equity: number;
  current_equity?: number;
  total_return_pct?: number;
  created_at: string;
  status: string;
  ran_today?: boolean;   // true if ORB detection already ran for today
}

export interface OrbPosition {
  ticker: string;
  direction: 'long' | 'short';
  entry_price: number;
  entry_time: string;
  shares: number;
  current_stop: number;
  peak_price: number;
  trailing_active: boolean;
  atr_at_entry: number;
  stop_distance: number;
  rvol: number | null;
  composite_score: number | null;
  current_price: number;
  bid: number | null;
  ask: number | null;
  change_pct: number | null;
  day_change_pct?: number | null;
  unrealized_pnl: number;
  r_multiple: number;
}

export interface OrbTrade {
  trade_id: string;
  session_id: string;
  date: string;
  ticker: string;
  direction: string;
  entry_price: number;
  exit_price: number;
  entry_time: string;
  exit_time: string;
  shares: number;
  pnl: number;
  r_multiple: number;
  exit_reason: string;
  atr_at_entry: number | null;
  rvol: number | null;
  composite_score: number | null;
}

export interface OrbSnapshot {
  session_id: string;
  date: string;
  equity: number;
  daily_pnl: number;
  total_pnl: number;
  trades_taken: number;
  stops_hit: number;
  drawdown_pct: number;
}

export interface OrbCandidate {
  id: number;
  session_id: string;
  date: string;
  ticker: string;
  direction: string;
  orb_high: number;
  orb_low: number;
  breakout_level: number;
  atr: number;
  rvol: number;
  gap_pct: number;
  composite_score: number;
  status: string;
}

export interface OrbScheduleEvent {
  name: string;
  kind: string;
  session: string;
  label: string;
  et_time: string;
  et_iso: string;
  past: boolean;
  done: boolean;
  wait_secs: number;
}

export const orbTradingApi = {
  // Sessions
  sessions: () => request<{ sessions: OrbSession[] }>('/orb/sessions'),

  createSession: (name: string, config: string, capital: number) =>
    request<{ session_id: string; session_name: string; initial_equity: number }>(
      '/orb/sessions',
      { method: 'POST', body: JSON.stringify({ name, config, capital }) },
    ),

  getSession: (id: string) => request<OrbSession>(`/orb/sessions/${encodeURIComponent(id)}`),

  pauseSession: (id: string) =>
    request<{ session_id: string; status: string }>(
      `/orb/sessions/${encodeURIComponent(id)}/pause`, { method: 'POST' }
    ),

  resumeSession: (id: string) =>
    request<{ session_id: string; status: string }>(
      `/orb/sessions/${encodeURIComponent(id)}/resume`, { method: 'POST' }
    ),

  closeSession: (id: string) =>
    request<{ deleted: boolean; positions_closed: number; close_errors: string[] }>(
      `/orb/sessions/${encodeURIComponent(id)}`, { method: 'DELETE' }
    ),

  runToday: (id: string) =>
    request<{ session_id: string; session_name: string; status: string; note: string }>(
      `/orb/sessions/${encodeURIComponent(id)}/run_today`, { method: 'POST' }
    ),

  // Data
  positions: (id: string) =>
    request<{ positions: OrbPosition[]; date: string }>(
      `/orb/sessions/${encodeURIComponent(id)}/positions`
    ),

  trades: (id: string, last?: number) =>
    request<{ trades: OrbTrade[]; total: number }>(
      `/orb/sessions/${encodeURIComponent(id)}/trades${last != null ? `?last=${last}` : ''}`
    ),

  equity: (id: string) =>
    request<{ snapshots: OrbSnapshot[]; initial_equity: number }>(
      `/orb/sessions/${encodeURIComponent(id)}/equity`
    ),

  candidates: (id: string) =>
    request<{ candidates: OrbCandidate[]; date: string }>(
      `/orb/sessions/${encodeURIComponent(id)}/candidates`
    ),

  // Auto scheduler
  autoStatus: () =>
    request<{
      running: boolean;
      sessions: string[];
      dry_run: boolean;
      schedule: OrbScheduleEvent[];
      log_tail: string[];
      log_line_count: number;
    }>('/orb/auto'),

  autoStart: (sessions?: string[], dryRun?: boolean) =>
    request<{ started: boolean; dry_run: boolean; sessions: string[] }>(
      '/orb/auto/start',
      { method: 'POST', body: JSON.stringify({ sessions: sessions ?? [], dry_run: dryRun ?? false }) }
    ),

  autoStop: () => request<{ stopped: boolean }>('/orb/auto/stop', { method: 'POST' }),

  clearLog: () => request<{ cleared: boolean }>('/orb/auto/clear-log', { method: 'POST' }),

  // Strategies
  strategies: () => request<{ strategies: OrbStrategyInfo[] }>('/orb/strategies'),
};

// --- SQS ---
export interface SqsPillars {
  rqs_score: number | null;
  wfqs_v2_score: number | null;
  regime_score: number | null;
  regime_source: string;
  core_score: number | null;
}

export interface SqsGates {
  deployment_gate_factor: number | null;
  robustness_gate_factor: number | null;
  activity_factor: number | null;
}

export interface SqsResult {
  experiment_name: string;
  entry_id: string;
  sqs_score: number | null;
  source: string | null;
  pending_reason: string | null;
  pillars: SqsPillars;
  gates: SqsGates;
  breakdown: Record<string, number>;
  stored_sqs_score: number | null;
  scenario_robustness_score: number | null;
  overfit_check_score: number | null;
  entries_updated?: number;
}

export const sqsApi = {
  compute: (name: string) =>
    request<SqsResult>(`/sqs/${encodeURIComponent(name)}`),

  rescore: (target: string) =>
    request<SqsResult>('/sqs/rescore', {
      method: 'POST',
      body: JSON.stringify({ target }),
    }),

  pipeline: (target: string) =>
    request<SqsResult & { pipeline_id?: string; pipeline_status: string; step?: string }>('/sqs/pipeline', {
      method: 'POST',
      body: JSON.stringify({ target }),
    }),

  pollPipeline: (pipelineId: string) =>
    request<SqsResult & { pipeline_id: string; pipeline_status: string; step?: string; error?: string }>(
      `/sqs/pipeline/${pipelineId}`
    ),
};

// --- Advisor (single-ticker BUY/SELL/HOLD) ---
export type AdvisorSignal = 'BUY' | 'SELL' | 'HOLD' | 'NO_SIGNAL' | 'ERROR';

export interface HeldPosition {
  strategy: 'pead' | 'orb';
  entry_date: string; // YYYY-MM-DD
  entry_price: number;
}

export interface AdvisorRequest {
  ticker: string;
  asof?: string | null; // YYYY-MM-DD; null = today ET
  held?: HeldPosition | null;
}

export interface StrategyVerdict {
  signal: AdvisorSignal;
  score: number | null;
  reason: string;
  details: Record<string, unknown>;
  warning: string | null;
}

export interface AdvisorResponse {
  ticker: string;
  asof: string;
  evaluated_at: string;
  pead: StrategyVerdict;
  orb: StrategyVerdict;
}

export const advisorApi = {
  evaluate: (req: AdvisorRequest) =>
    request<AdvisorResponse>('/advisor/evaluate', {
      method: 'POST',
      body: JSON.stringify(req),
    }),
  champions: () => request<{ pead: string; orb: string }>('/advisor/champions'),
};

// --- Events / Health ---

export interface TradeEvent {
  id: number;
  ts_utc: string;
  job_run_id: string | null;
  source: string;
  level: 'INFO' | 'WARN' | 'ERROR';
  category: string | null;
  event_name: string;
  session_id: string | null;
  ticker: string | null;
  message: string | null;
  details: string | null;
}

export interface EventsResponse {
  rows: TradeEvent[];
  total: number;
  offset: number;
  next_offset: number | null;
}

export interface RunSummary {
  job_run_id: string;
  started_at: string;
  ended_at: string;
  event_count: number;
  error_count: number;
  warn_count: number;
  sources: string | null;
  phase: string | null;
}

export interface HealthSummary {
  phases: Record<string, { phase: string; status: string; last_ts: string }>;
  fallbacks_today: Record<string, { warn: number; error: number }>;
  recent_errors: Array<{
    id: number;
    ts_utc: string;
    source: string;
    event_name: string;
    message: string | null;
    session_id: string | null;
    job_run_id: string | null;
  }>;
  snapshot_freshness: Array<{
    session_id: string | null;
    snapshot_id: string | null;
    last_update: string;
  }>;
  drop_count: number;
}

export interface EventsQuery {
  source?: string;
  level?: string;
  category?: string;
  session_id?: string;
  job_run_id?: string;
  event_name?: string;
  q?: string;
  since?: string;
  until?: string;
  limit?: number;
  offset?: number;
  system?: string;
}

export const eventsApi = {
  list: (params: EventsQuery = {}) => {
    const qs = new URLSearchParams();
    Object.entries(params).forEach(([k, v]) => {
      if (v !== undefined && v !== null && v !== '') qs.set(k, String(v));
    });
    const q = qs.toString();
    return request<EventsResponse>(`/events${q ? '?' + q : ''}`);
  },
  runs: (limit = 20) => request<RunSummary[]>(`/events/runs?limit=${limit}`),
  sources: () => request<string[]>('/events/sources'),
  health: () => request<HealthSummary>('/health'),
  purge: (before: string) =>
    request<{ deleted: number }>(`/events?before=${encodeURIComponent(before)}`, { method: 'DELETE' }),
};

// --- TGTC Trading ---

export interface TgtcSession {
  session_id: string;
  session_name: string;
  config_path: string;
  initial_equity: number;
  current_equity: number;
  total_return_pct: number;
  created_at: string;
  status: string;
  ran_today: boolean;
  phase: string;
}

export interface TgtcSnapshot {
  id?: number;
  session_id: string;
  date: string;
  captured_at: string;
  symbol: string;
  rank: number;
  price: number | null;
  pct_change: number | null;
  volume: number | null;
  market_cap: number | null;
}

export interface TgtcCandidate {
  id?: number;
  session_id: string;
  date: string;
  symbol: string;
  score: number | null;
  rank_persistence: number | null;
  rank_velocity: number | null;
  price_structure: number | null;
  volume_quality: number | null;
  relative_strength: number | null;
  pct_change_at_10: number | null;
  price_at_10: number | null;
  vwap_at_10: number | null;
  above_vwap: number | null;
  decided_at: string;
  status: string;
}

export interface TgtcPosition {
  id?: number;
  session_id: string;
  date: string;
  symbol: string;
  entry_signal: string;
  entry_price: number;
  stop_price: number;
  current_stop: number;
  shares: number;
  entered_at: string;
  peak_price: number;
  partial_taken: number;
  be_stop_active: number;
  exit_price: number | null;
  exit_reason: string | null;
  exited_at: string | null;
  pnl: number | null;
  r_multiple: number | null;
  is_dry_run: number;
  status: string;
}

export interface TgtcTrade {
  trade_id: string;
  session_id: string;
  date: string;
  symbol: string;
  entry_signal: string;
  entry_price: number;
  exit_price: number;
  entered_at: string;
  exited_at: string;
  shares: number;
  pnl: number;
  r_multiple: number;
  exit_reason: string;
  is_dry_run: number;
}

export interface TgtcStrategyInfo {
  config_path: string;
  name: string;
  id: string;
  status: string;
  live_readiness: string;
  description: string;
}

export interface TgtcAutoStatus {
  running: boolean;
  pid: number | null;
  sessions: string[];
  dry_run: boolean;
  db_path: string;
  log: string;
  log_lines: string[];
}

export interface TgtcBacktestTask {
  task_id: string;
  date: string;
  config_path: string;
  universe: string | null;
  status: 'pending' | 'running' | 'completed' | 'failed';
  created_at: string;
  result: TgtcBacktestResult | null;
  error: string | null;
}

export interface TgtcBacktestResult {
  // single-day fields
  date?: string;
  n_candidates?: number;
  equity_curve?: Array<{ ts_et: string; equity: number }>;
  candidates?: TgtcCandidate[];
  // multi-day fields
  type?: 'multiday';
  start_date?: string;
  end_date?: string;
  n_days?: number;
  per_day?: Array<{
    date: string;
    n_candidates: number;
    n_trades: number;
    pnl: number;
    return_pct: number;
    win_rate: number;
    equity: number;
  }>;
  // shared
  n_trades: number;
  total_pnl: number;
  total_return_pct: number;
  win_rate: number;
  initial_equity: number;
  final_equity: number;
  trades: Array<{
    date?: string;
    symbol: string;
    entry_price: number;
    exit_price: number;
    stop_price: number;
    shares: number;
    pnl: number;
    r_multiple: number;
    exit_reason: string;
    status: string;
  }>;
}

export const tgtcApi = {
  sessions: () => request<{ sessions: TgtcSession[] }>('/tgtc/sessions'),
  createSession: (name: string, config: string, capital: number) =>
    request<TgtcSession>('/tgtc/sessions', {
      method: 'POST',
      body: JSON.stringify({ name, config, capital }),
    }),
  deleteSession: (id: string) =>
    request<{ deleted: string }>(`/tgtc/sessions/${id}`, { method: 'DELETE' }),
  strategies: () => request<{ strategies: TgtcStrategyInfo[] }>('/tgtc/strategies'),
  snapshots: (id: string, date?: string) =>
    request<{ date: string; snapshots: TgtcSnapshot[]; count: number }>(
      `/tgtc/sessions/${id}/snapshots${date ? '?date=' + date : ''}`
    ),
  candidates: (id: string, date?: string) =>
    request<{ date: string; candidates: TgtcCandidate[] }>(
      `/tgtc/sessions/${id}/candidates${date ? '?date=' + date : ''}`
    ),
  positions: (id: string) =>
    request<{ positions: TgtcPosition[] }>(`/tgtc/sessions/${id}/positions`),
  trades: (id: string) =>
    request<{ trades: TgtcTrade[] }>(`/tgtc/sessions/${id}/trades`),
  equity: (id: string) =>
    request<{ initial_equity: number; current_equity: number; daily_snapshots: unknown[] }>(
      `/tgtc/sessions/${id}/equity`
    ),
  autoStatus: () => request<TgtcAutoStatus>('/tgtc/auto'),
  autoStart: (sessions: string[] = [], dry_run = true) =>
    request<{ started: boolean; dry_run: boolean }>('/tgtc/auto/start', {
      method: 'POST',
      body: JSON.stringify({ sessions, dry_run }),
    }),
  autoStop: () => request<{ stopped: boolean }>('/tgtc/auto/stop', { method: 'POST' }),
  clearLog: () => request<{ cleared: boolean }>('/tgtc/auto/clear-log', { method: 'POST' }),
  submitBacktest: (date: string, config: string, universe?: string, end_date?: string) =>
    request<{ task_id: string; status: string }>('/tgtc/backtest/submit', {
      method: 'POST',
      body: JSON.stringify({ date, config, universe, end_date }),
    }),
  backtestTasks: () => request<{ tasks: TgtcBacktestTask[] }>('/tgtc/backtest/tasks'),
  backtestTask: (id: string) => request<TgtcBacktestTask>(`/tgtc/backtest/tasks/${id}`),
  backtestResult: (id: string) => request<TgtcBacktestResult>(`/tgtc/backtest/tasks/${id}/result`),
};

// ── ORB Scanner ────────────────────────────────────────────────────────────────

export interface OrbFilterResult {
  name: string;
  passed: boolean;
  value: string | null;
  threshold: string | null;
  note: string | null;
}

export interface OrbCheckResponse {
  ticker: string;
  asof: string;
  evaluated_at: string;
  market_status: string;
  overall_signal: 'ENTRY' | 'NO_ENTRY' | 'NOT_YET' | 'MARKET_CLOSED' | 'ERROR';
  filters: OrbFilterResult[];
  entry_details: Record<string, unknown> | null;
  warning: string | null;
}

export interface OrbExitCheckResponse {
  ticker: string;
  current_stop: number;
  stop_phase: 'initial' | 'breakeven' | 'trailing' | 'trailing_tight' | 'force_exit';
  should_exit: boolean;
  reason: string;
  r_multiple: number;
  profit_r: number;
  time_status: string;
  current_price: number;
  peak_price: number;
  entry_time_used: string | null;
  entry_price_used: number | null;
  atr_at_entry_used: number | null;
}

export interface GainerResult {
  ticker: string;
  price: number | null;
  change_pct: number | null;
  signal: 'ENTRY' | 'SCALE_ENTRY' | 'NO_ENTRY';
  scale_factor: number | null;
  filter_summary: string;
  failure_reason: string | null;
}

export interface GainersScanResponse {
  scan_time: string;
  market_status: string;
  count_fetched: number;
  count_passed: number;
  results: GainerResult[];
}

export interface StrategyInfo {
  id: string;
  name: string;
}

export interface StrategiesResponse {
  strategies: StrategyInfo[];
  default: string;
}

export const orbScannerApi = {
  strategies: () =>
    request<StrategiesResponse>('/orb-scanner/strategies'),

  check: (req: { ticker: string; asof?: string | null; strategy?: string | null }) =>
    request<OrbCheckResponse>('/orb-scanner/check', {
      method: 'POST',
      body: JSON.stringify(req),
    }),

  exitCheck: (req: {
    ticker: string;
    entry_price?: number | null;
    atr_at_entry?: number | null;
    entry_time?: string | null;
    strategy?: string | null;
  }) =>
    request<OrbExitCheckResponse>('/orb-scanner/exit-check', {
      method: 'POST',
      body: JSON.stringify(req),
    }),

  gainersScan: (count = 200, strategy?: string | null) =>
    request<GainersScanResponse>(
      `/orb-scanner/gainers-scan?count=${count}${strategy ? `&strategy=${encodeURIComponent(strategy)}` : ''}`
    ),
};
