/**
 * gas_apex_alpha_watch.gs
 * ────────────────────────────────────────────────────────────────────────────
 * APEX 행위기반 커버리지 하네스 — 핵심 계산 엔진 (Impl)
 * [2026-05-30] BCH-V1 대응을 위해 분리된 순수 함수들
 */

/**
 * PA2: ANTI_LATE_ENTRY_GATE_V2
 * [Python py_anti_late_entry_gate_v2 미러와 100% 동일 로직]
 * 
 * @param {Array}  holdings  asResult.holdings
 * @param {Object} dfMap     종목별 데이터 피드
 * @return {Array} anti_late_entry_json
 */
function calcAntiLateEntryGateV2Impl_(holdings, dfMap) {
  var results = [];
  for (var i = 0; i < holdings.length; i++) {
    var h = holdings[i];
    var ticker = h.ticker || '';
    var df = dfMap[ticker] || {};
    
    var close = Number(h.close || df.close || 0);
    var prevClose = Number(df.prevClose || 0);
    var ma20 = Number(df.ma20 || 0);
    var rsi14 = Number(df.rsi14 != null ? df.rsi14 : 50);
    var flowCredit = Number(df.flowCredit != null ? df.flowCredit : 0);
    var volume = Number(df.volume || 0);
    var avgVol5d = Number(df.avgVolume5d || 0);
    var frg5d = Number(df.frg5d || 0);
    var inst5d = Number(df.inst5d || 0);
    var ret5d = Number(df.ret5d || 0);
    var acGate = String(df.acGate || '');

    var v1d = prevClose > 0 ? (close - prevClose) / prevClose * 100 : 0.0;
    var v5d = ret5d;

    var distWs = 0.0;
    if (frg5d < 0) distWs += 2.0;
    if (inst5d < 0) distWs += 2.0;
    if (avgVol5d > 0 && volume > avgVol5d * 1.3) distWs += 1.5;
    if (prevClose > 0 && close < prevClose) distWs += 1.5;
    if (rsi14 > 70) distWs += 1.0;
    if (acGate === 'BLOCK') distWs += 1.0;

    var gate1 = 'PASS';
    if (v1d >= 3.0) gate1 = 'BLOCK_CHASE';
    else if (v1d >= 1.5) gate1 = 'PULLBACK_WAIT';

    var gate2 = 'PASS';
    if (v5d >= 8.0) gate2 = 'BLOCK_CHASE_5D';
    else if (v5d >= 5.0) gate2 = 'PULLBACK_WAIT_5D';

    var gate3 = 'PASS';
    if (distWs >= 3.0) gate3 = 'BLOCK_DISTRIBUTION';
    else if (distWs >= 2.0) gate3 = 'PULLBACK_WAIT_DIST';

    var hasBlock = (gate1 === 'BLOCK_CHASE' || gate2 === 'BLOCK_CHASE_5D' || gate3 === 'BLOCK_DISTRIBUTION');
    var hasPullback = (gate1 === 'PULLBACK_WAIT' || gate2 === 'PULLBACK_WAIT_5D' || gate3 === 'PULLBACK_WAIT_DIST');

    var finalGate = 'PASS';
    if (hasBlock) finalGate = 'BLOCK';
    else if (hasPullback) finalGate = 'PULLBACK_WAIT';

    var grade = 'B';
    if (finalGate === 'BLOCK') {
      grade = 'F';
    } else if (v1d < 0.5 && ma20 > 0 && close >= ma20 && close <= ma20 * 1.02 && flowCredit >= 0.55) {
      grade = 'A';
    } else if (v1d < 1.5 && ma20 > 0 && Math.abs(close - ma20) / ma20 <= 0.05) {
      grade = 'B';
    } else if (finalGate === 'PULLBACK_WAIT') {
      grade = 'C';
    } else if (v5d > 5.0) {
      grade = 'D';
    }

    results.push({
      ticker: ticker,
      gate1_status: gate1,
      gate2_status: gate2,
      gate3_status: gate3,
      final_gate_status: finalGate,
      anti_late_entry_status: finalGate,
      entry_grade: grade,
      velocity_1d: Math.round(v1d * 100) / 100,
      velocity_5d: Math.round(v5d * 100) / 100,
      dist_weighted_sum: Math.round(distWs * 10) / 10
    });
  }
  return results;
}

/**
 * PA5: CONSISTENCY_VALIDATOR_V2
 * [P0 GAP 해소 - 데이터 정합성 검증]
 */
function calcConsistencyValidatorV2Impl_(hApex, asResult, cashFloorInfo, capturedAtIso, now) {
  var checks = [];
  var passed = [];
  var failed = [];
  var gapList = [];
  
  // CV_01: sell_priority 방향 일관성
  var sellCandidates = hApex.sell_candidates_json || [];
  var tierOk = true;
  for (var i = 1; i < sellCandidates.length; i++) {
    if (sellCandidates[i].tier < sellCandidates[i-1].tier) {
      tierOk = false;
      break;
    }
  }
  if (tierOk) passed.push('CV_01'); else failed.push({check_id: 'CV_01', reason: 'tier_reversal'});

  // CV_02: 가격 순서 검증
  var prices = hApex.prices_json || [];
  var priceOk = true;
  for (var i = 0; i < prices.length; i++) {
    var p = prices[i];
    if (p.stop_price && p.current_price && p.stop_price >= p.current_price) priceOk = false;
  }
  if (priceOk) passed.push('CV_02'); else failed.push({check_id: 'CV_02', reason: 'price_hierarchy_violation'});

  // CV_06: 수량 정수 검증
  var qtyOk = true;
  var bqi = hApex.buy_qty_inputs_json || [];
  for (var i = 0; i < bqi.length; i++) {
    if (bqi[i].final_qty && bqi[i].final_qty % 1 !== 0) qtyOk = false;
  }
  if (qtyOk) passed.push('CV_06'); else failed.push({check_id: 'CV_06', reason: 'float_quantity'});

  // CV_08: 현금 계산 경로
  if (hApex.cash_ledger_basis === 'D2_ONLY') passed.push('CV_08');
  else failed.push({check_id: 'CV_08', reason: 'invalid_cash_basis'});

  // Score 계산
  var score = Math.floor((passed.length / 12) * 100);
  var status = score >= 90 ? (score === 100 ? 'PASS' : 'WARNING') : 'BLOCK';

  return {
    formula_id: 'CONSISTENCY_VALIDATOR_V2',
    consistency_score: score,
    cv_verdict: status === 'BLOCK' ? 'ABORT' : 'PASS',
    block_status: status,
    passed: passed,
    failed: failed,
    gap_list: gapList,
    consistency_report_json: { score: score, passed: passed, failed: failed }
  };
}

/**
 * PA4: MACRO_EVENT_SYNCHRONIZER_V1
 */
function calcMacroEventSynchronizerV1Impl_(macroJson, eventRows) {
  var usdKrw = Number(macroJson.usd_krw || 0);
  var foreignSellDays = Number(macroJson.foreign_sell_consecutive_days || 0);
  
  var score = 0;
  if (usdKrw > 1500) score += 20;
  else if (usdKrw > 1480) score += 15;
  
  if (foreignSellDays >= 10) score += 20;
  else if (foreignSellDays >= 5) score += 15;
  
  var regime = 'MACRO_NEUTRAL';
  var heatAdj = 0;
  if (score >= 60) { regime = 'MACRO_CRITICAL'; heatAdj = -3; }
  else if (score >= 40) { regime = 'MACRO_ELEVATED'; heatAdj = -1; }
  else if (score < 20) { regime = 'MACRO_FAVORABLE'; heatAdj = 1; }

  return {
    formula_id: 'MACRO_EVENT_SYNCHRONIZER_V1',
    macro_risk_score: score,
    macro_risk_regime: regime,
    effective_heat_gate_adjustment: heatAdj,
    mega_sell_alert: false,
    macro_event_json: { score: score, regime: regime, heat_gate_adj: heatAdj }
  };
}

/**
 * PA1: PREDICTIVE_ALPHA_ENGINE_V1
 */
function calcPredictiveAlphaEngineV1Impl_(holdings, dfMap, macroJson, mesResult, weightOverrides) {
  var results = [];
  for (var i = 0; i < holdings.length; i++) {
    var h = holdings[i];
    var ticker = h.ticker;
    var df = dfMap[ticker] || {};
    
    var thesis = 0;
    if (df.close > df.ma20 && df.close < df.ma20 * 1.03) thesis += 20;
    if (df.flowCredit >= 0.55) thesis += 20;
    
    var antithesis = 0;
    var v1d = df.prevClose > 0 ? (df.close - df.prevClose) / df.prevClose * 100 : 0;
    if (v1d >= 3.0) antithesis += 25;
    
    var confidence = thesis - antithesis;
    var verdict = 'HOLD_NEUTRAL';
    if (confidence >= 40) verdict = 'STRONG_BUY_SIGNAL';
    else if (confidence >= 20) verdict = 'MODERATE_BUY_SIGNAL';
    else if (confidence < -30) verdict = 'EXIT_SIGNAL';
    else if (confidence < -10) verdict = 'TRIM_SIGNAL';

    results.push({
      ticker: ticker,
      direction_confidence: confidence,
      thesis_score: thesis,
      antithesis_score: antithesis,
      synthesis_verdict: verdict,
      predictive_alpha_json: { confidence: confidence, verdict: verdict }
    });
  }
  return results;
}

/**
 * MACRO_REGIME_ADAPTIVE_GATE_V2
 */
function calcMacroRegimeAdaptiveGateV2Impl_(macroJson, mesResult, hApex) {
  var totalScore = mesResult.macro_risk_score || 0;
  var regime = 'MODERATE_RISK';
  var heatThreshold = 10.0;
  var sizeScale = 1.0;

  if (totalScore >= 75) { regime = 'EXTREME_RISK'; heatThreshold = 5.0; sizeScale = 0.25; }
  else if (totalScore >= 50) { regime = 'HIGH_RISK'; heatThreshold = 7.0; sizeScale = 0.50; }
  else if (totalScore < 25) { regime = 'LOW_RISK'; heatThreshold = 12.0; sizeScale = 1.10; }

  return {
    formula_id: 'MACRO_REGIME_ADAPTIVE_GATE_V2',
    total_mrag_score: totalScore,
    regime_label: regime,
    effective_heat_gate_threshold: heatThreshold,
    effective_position_size_scale: sizeScale,
    mrag_v2_json: { score: totalScore, regime: regime }
  };
}

/**
 * applyAlegGate4And5Impl_
 */
function applyAlegGate4And5Impl_(alegRows, paeRows, hApex) {
  var results = [];
  var paeMap = {};
  for (var i = 0; i < paeRows.length; i++) paeMap[paeRows[i].ticker] = paeRows[i];

  for (var i = 0; i < alegRows.length; i++) {
    var row = alegRows[i];
    var pae = paeMap[row.ticker] || {};
    
    if (pae.synthesis_verdict === 'EXIT_SIGNAL' || pae.synthesis_verdict === 'TRIM_SIGNAL') {
      row.gate4_status = 'BLOCK_PAE';
      row.final_gate_status = 'BLOCK';
      row.anti_late_entry_status = 'BLOCK';
    } else {
      row.gate4_status = 'PASS';
    }
    results.push(row);
  }
  return results;
}

/**
 * Suite Aggregators
 */
function applyApexMacroAlphaSuiteImpl_(holdings, dfMap, hApex) {
  // Placeholder for macro alpha suite
  return hApex;
}

function applyApexMacroEventSuiteImpl_(hApex) {
  // Placeholder for macro event suite
  return hApex;
}

function applyApexPredictiveAlphaSuiteImpl_(holdings, dfMap, hApex) {
  var macroJson = hApex.macro_event_json || {};
  var mesResult = hApex.macro_event_json || {};
  var paeRows = calcPredictiveAlphaEngineV1Impl_(holdings, dfMap, macroJson, mesResult, null);
  hApex.predictive_alpha_json = paeRows;

  // portfolio_alpha_confidence: mean direction_confidence across all holdings
  var sum = 0, n = 0;
  (paeRows || []).forEach(function(r) {
    if (typeof r.direction_confidence === 'number') { sum += r.direction_confidence; n++; }
  });
  hApex.portfolio_alpha_confidence = n > 0 ? Math.round(sum / n * 100) / 100 : 0;

  return hApex;
}

function applyApexWatchBreakoutSuiteImpl_(holdings, dfMap, hApex) {
  var slgRows = hApex.satellite_lifecycle_gate_json || [];
  var aleRows = hApex.anti_late_entry_json || [];
  hApex.watch_breakout_candidates_json = calcWatchBreakoutRealtimeGateV1_(holdings, dfMap, slgRows, aleRows);
  return hApex;
}

// ---- TASK-006: ANTI_LATE_ENTRY_GATE_V2_CALIBRATED ----
// [GAS_STUB_ONLY: requires Google Sheets deployment]
function calibrateAntiLateEntryV2_(proposalHistory, captureDate) {
  // RC 수정: velocity 버킷별 T+5 승률 계산 (실측 표본 >= 30 충족 후 활성화)
  var buckets = { LOW: {n:0,wins:0}, MID: {n:0,wins:0}, HIGH: {n:0,wins:0} };
  var totalBuys = 0, chaseBuys = 0;
  (proposalHistory || []).forEach(function(p) {
    if (p.origin === 'REPLAY' || p.action !== 'BUY') return;
    if (p.realized_return_pct_t5 === undefined) return; // 미채움 제외
    totalBuys++;
    var v = parseFloat(p.velocity_1d || 0);
    var win = parseFloat(p.realized_return_pct_t5 || 0) > 0;
    var bucket = v < 1.0 ? 'LOW' : v < 3.0 ? 'MID' : 'HIGH';
    buckets[bucket].n++;
    if (win) buckets[bucket].wins++;
    if (v >= 3.0) chaseBuys++;
  });
  var minSamples = 30;
  var validated = Object.keys(buckets).every(function(k) { return buckets[k].n >= minSamples; });
  return {
    formula_id: 'ANTI_LATE_ENTRY_GATE_V2_CALIBRATED',
    validated: validated,
    unvalidated_label: validated ? null : '[UNVALIDATED_LIVE: n<30 per bucket]',
    chase_entry_rate_pct: totalBuys > 0 ? (chaseBuys / totalBuys * 100).toFixed(1) : null,
    buckets: buckets,
    threshold_source: validated ? 'DYNAMIC' : 'EXPERT_PRIOR',
    velocity_1d_block_pct: 3.0
  };
}

// ---- TASK-007: DISTRIBUTION_BLOCK_EFFECTIVENESS_V1 ----
// [GAS_STUB_ONLY: requires Google Sheets deployment]
function trackDistributionBlockEffectiveness_(proposalHistory) {
  var blocked = (proposalHistory || []).filter(function(p) {
    return p.blocked_reason === 'DISTRIBUTION_CONFIRMED' || p.blocked_reason === 'DISTRIBUTION_BLOCK';
  });
  var avoidedLoss = blocked.filter(function(p) {
    return p.t5_return_if_not_blocked !== undefined && parseFloat(p.t5_return_if_not_blocked) < 0;
  });
  var blockedN = blocked.length;
  var avoidedLossRate = blockedN > 0 ? (avoidedLoss.length / blockedN) : null;
  return {
    formula_id: 'DISTRIBUTION_BLOCK_EFFECTIVENESS_V1',
    blocked_sample_count: blockedN,
    avoided_loss_rate: avoidedLossRate,
    target_avoided_loss_rate: 0.60,
    effectiveness_label: blockedN < 30
      ? '[UNVALIDATED_LOW_N: n=' + blockedN + ' < 30]'
      : (avoidedLossRate >= 0.60 ? 'EFFECTIVE' : 'REVIEW_THRESHOLD')
  };
}

// ---- TASK-010: SMART_MONEY_LIQUIDITY_OUTCOME_LINK_V1 ----
// [GAS_STUB_ONLY: requires Google Sheets deployment]
function linkSmartMoneyOutcome_(proposalHistory) {
  var buckets = {};
  (proposalHistory || []).forEach(function(p) {
    if (p.origin === 'REPLAY' || !p.liquidity_label) return;
    var lbl = p.liquidity_label;
    if (!buckets[lbl]) buckets[lbl] = {returns:[], slippages:[]};
    if (p.realized_return_pct_t5 !== undefined) buckets[lbl].returns.push(parseFloat(p.realized_return_pct_t5));
    if (p.slippage_pct !== undefined) buckets[lbl].slippages.push(parseFloat(p.slippage_pct));
  });
  var table = Object.keys(buckets).map(function(lbl) {
    var d = buckets[lbl];
    var n = d.returns.length;
    var wins = d.returns.filter(function(r){return r>0;}).length;
    return {
      liquidity_label: lbl,
      sample_count: n,
      t5_avg_return_pct: n > 0 ? d.returns.reduce(function(a,b){return a+b;},0)/n : null,
      t5_win_rate: n > 0 ? wins/n : null,
      label: n < 30 ? '[UNVALIDATED: n=' + n + ' < 30]' : 'VALIDATED'
    };
  });
  return {formula_id: 'SMART_MONEY_LIQUIDITY_OUTCOME_LINK_V1', table: table};
}
