// Interaction Awareness — UserPromptSubmit hook (Layer 2, Node.js)
// Analyzes prompt text for interaction pattern flags.
// PRIVACY: Prompt text is NEVER written to any file. Only boolean flags are stored.

import { existsSync } from 'fs';
import {
  readStdin, initConfig, requireLayer, getSessionId, getField,
  nowEpoch,
  STATE_DIR, THRESHOLD_SOFT_DEP_FLAGS, THRESHOLD_HARD_DEP_FLAGS,
  COOLDOWN_SOFT,
  TIER1_TURN_THRESHOLD, THRESHOLD_VALSEEK_FLAGS, THRESHOLD_PUSHBACK_FLAGS,
  HIGH_SYCOPHANCY_DOMAINS, HIGH_STAKES_DOMAINS, INFO_DOMAINS,
  DOMAIN_STAKES,
  readState, sessionStateFile, writeState, checkCooldown,
  outputContinue, outputWithContext
} from './lib.mjs';

readStdin();
initConfig();
requireLayer(2);

const sid = getSessionId();
const sf = sessionStateFile();

if (!sid || !existsSync(sf)) {
  outputContinue();
  process.exit(0);
}

// Extract prompt into memory only — NEVER write to file
let prompt = getField('prompt');
if (!prompt) {
  outputContinue();
  process.exit(0);
}

// --- Pattern matching (case-insensitive) ---

let depHit = 0;
let escHit = 0;
let fatHit = 0;
let valHit = 0;

// Dependency patterns: user defers judgment to AI
const depPatterns = [
  /tell\s+me\s+what\s+to\s+do/i,
  /what\s+should\s+I\s+do/i,
  /am\s+I\s+right/i,
  /you\s+understand\s+me\b/i,
  /you're\s+the\s+only/i,
  /can\s+I\s+do\s+this/i,
  /I\s+need\s+you\s+to\s+decide/i,
];

// Escalation patterns: language that amplifies certainty
const escPatterns = [
  /(?:^|\s)definitely(?:\s|$)/i,
  /(?:^|\s)clearly(?:\s|$)/i,
  /this\s+proves/i,
  /(?:^|\s)obviously(?:\s|$)/i,
  /without\s+a\s+doubt/i,
  /this\s+confirms/i,
];

// Fatigue patterns: user signals tiredness
const fatPatterns = [
  /(?:^|\s)tired(?:\s|[.,!?]|$)/i,
  /(?:^|\s)exhausted(?:\s|[.,!?]|$)/i,
  /can't\s+think/i,
  /been\s+at\s+this/i,
  /it's\s+late/i,
  /should\s+sleep/i,
  /hours\s+now/i,
];

// Validation-seeking patterns
const valPatterns = [
  /right\?/i,
  /don't\s+you\s+think/i,
  /you\s+agree/i,
  /correct\?/i,
  /isn't\s+it/i,
];

// Pushback patterns — REACTIVE tier (Anthropic-validated + academic-validated)
// Source: research/01-pushback-self-advocacy.md
const pbReactivePatterns = [
  /^are you sure\??/i,                                  // validated-by: anthropic-april-2026 (questioning)
  /\bi'?m not convinced\b/i,                            // validated-by: anthropic-april-2026 (questioning)
  /\bthat doesn'?t (?:seem|feel) right\b/i,             // validated-by: anthropic-april-2026 (questioning)
  /\bthat'?s not (?:quite )?what i meant\b/i,           // validated-by: anthropic-april-2026 (clarifying)
  /\blet me add (?:some )?context\b/i,                  // validated-by: anthropic-april-2026 (clarifying)
  /\bactually,? (?:my situation|i)\b/i,                 // validated-by: anthropic-april-2026 (clarifying)
  /(?:^|[.!?]\s+)i (?:believe|think) (?:you'?re|that'?s) wrong\b/i,  // validated-by: arxiv-2508.02087
  /\bi don'?t agree(?: with you)?\b/i,                  // validated-by: arxiv-2508.13743
  /\bare you absolutely sure\b/i,                       // validated-by: arxiv-2508.13743
];
// Pushback patterns — PREEMPTIVE tier (community-derived)
const pbPreemptivePatterns = [
  /\bsteelman\b/i,                                       // validated-by: community-multi-source-2025
  /\bplay (?:the )?devil'?s advocate\b/i,                // validated-by: community-multi-source-2025
  /\bargue against (?:this|my)\b/i,                      // validated-by: community-multi-source-2025
];
// Domain-context: relationship — uses (?:my|our) prefix to avoid false positives
// on technical "function relationship", "database relationship" etc.
const domainRelationshipPatterns = [
  /\b(?:my|our) (?:partner|spouse|wife|husband|girlfriend|boyfriend)\b/i,
  /\bin our relationship\b/i,
  /\b(?:dating|breakup|divorce)\b/i,
  /\bromantic(?:ally)? (?:involved|interested)\b/i,
];

// v1.2: 8 new paper-grounded domains. Patterns drawn from Figure A2 examples
// and the paper's text. Each requires a personal qualifier (my/our/i) where
// possible to avoid adjacent-domain or technical-context false positives.

const domainLegalPatterns = [
  /\b(?:my|our) (?:lawyer|attorney|legal counsel)\b/i,
  /\b(?:filing|filed|file) (?:a |an )?(?:lawsuit|complaint|suit|case)\b/i,
  /\b(?:custody|divorce) (?:agreement|case|battle|hearing|settlement)\b/i,
  /\b(?:contract|nda|liability|tort|statute) (?:violation|dispute|review)\b/i,
  /\b(?:sued?|prosecuted?|indicted?|deposed?) (?:by|for|in)\b/i,
  /\b(?:landlord|tenant|eviction) (?:rights?|dispute|notice)\b/i,
];

const domainParentingPatterns = [
  /\bmy (?:kid|child|son|daughter|baby|toddler|teen|teenager)\b/i,
  /\b(?:potty|sleep|behaviou?r|tantrum) (?:training|issue|problem)\b/i,
  /\bas a (?:parent|mom|dad|mother|father)\b/i,
  /\b(?:bedtime|breastfeeding|weaning|teething) (?:routine|problem|advice)\b/i,
  /\b(?:school|preschool|daycare) (?:choice|conflict|placement|fight)\b/i,
  /\bmy (?:child|kid|son|daughter)'?s? (?:diagnosis|behavior|behaviour|teacher)\b/i,
];

const domainHealthPatterns = [
  /\bmy (?:doctor|physician|gp|specialist|therapist|psychiatrist)\b/i,
  /\b(?:diagnosed|prescribed|medicated|treated) (?:with|for|by)\b/i,
  /\bmy symptoms?\s+(?:are|include|started|got)\b/i,
  /\b(?:my|i have) (?:cancer|diabetes|depression|anxiety|chronic pain)\b/i,
  /\b(?:blood pressure|heart rate|cholesterol|insulin)\s+(?:level|reading|test|results?)\b/i,
  /\b(?:scheduled|having|after|recovering from) (?:surgery|procedure|treatment|chemo)\b/i,
];

const domainFinancialPatterns = [
  /\b(?:my )?(?:savings|retirement|401k|pension|investments?) (?:account|plan|portfolio|strategy)?\b/i,
  /\b(?:mortgage|refinance|loan|debt|bankruptcy) (?:payment|application|filing|advice)\b/i,
  /\b(?:my )?(?:taxes?|tax (?:return|bracket|deduction|filing))\b/i,
  /\b(?:budget|paycheck|salary|raise) (?:negotiation|advice|planning|cut)\b/i,
  /\b(?:stock|bond|index fund|crypto|portfolio) (?:pick|allocation|loss|advice)\b/i,
  /\b(?:credit (?:card|score)|interest rate|apr) (?:problem|advice|negotiation)\b/i,
];

const domainProfessionalPatterns = [
  /\bmy (?:boss|manager|coworker|colleague|team lead|HR rep)\b/i,
  /\b(?:performance review|promotion|pip|fired|laid off|quitting|resign(?:ed|ing)?)\b/i,
  /\bmy (?:job|career|workplace|office) (?:change|conflict|stress|search)\b/i,
  /\b(?:resume|cv|cover letter|offer letter) (?:advice|review|negotiation)\b/i,
  /\bproject (?:deadline|delay|scope) (?:fight|conflict|issue|problem)\b/i,
  /\b(?:remote|hybrid|in-office|return.to.office) (?:policy|mandate|requirement)\b/i,
];

const domainSpiritualityPatterns = [
  /\bmy (?:guru|spiritual (?:teacher|guide|advisor|mentor))\b/i,
  /\b(?:meditation|mindfulness|enlightenment|awakening) (?:practice|journey|path)\b/i,
  /\b(?:karma|dharma|chakra|aura|spirit guide|kundalini)\b/i,
  /\b(?:god|jesus|buddha|allah|the universe|source) (?:wants|told|sent|spoke|wills)\b/i,
  /\b(?:soulmate|twin flame|past life|reincarnation|astral projection)\b/i,
  /\b(?:prayer|prayed|spiritual journey|spiritually awakened)\b/i,
];

const domainConsumerPatterns = [
  /\bshould i buy (?:a|an|the|this|that)\b/i,
  /\bwhich (?:laptop|phone|car|tv|monitor|headphones?) (?:should|to)\b/i,
  /\b(?:product|item) (?:review|comparison|recommendation)\b/i,
  /\b(?:amazon|online|store) (?:order|purchase|return) (?:problem|issue)\b/i,
  /\b(?:better|best) (?:deal|price|brand|model) (?:for|on|of)\b/i,
  /\b(?:upgrade|replace) my (?:laptop|phone|computer|tv|car|setup)\b/i,
];

const domainPersonalDevPatterns = [
  /\b(?:learn|practice|develop) (?:a |the )?(?:habit|skill|discipline) (?:of|for)\b/i,
  /\bmy (?:morning|daily|evening) routine\b/i,
  /\b(?:read|reading) more (?:books?|articles)\b/i,
  /\b(?:start|begin|build) (?:a |the )?(?:journal|gratitude practice|hobby|side project)\b/i,
  /\b(?:learning|teaching myself|self-(?:taught|study|learning))\b/i,
  /\b(?:improve|grow|level up) (?:myself|my (?:self-discipline|focus|productivity))\b/i,
];

// v1.2: User-information dimension (paper page 11). Three classes — yes_people,
// yes_digital, no. Priority: yes_people > yes_digital > no. Sticky for session.
//
// "yes_people" — user has access to humans for advice (therapist, friend,
// mentor, partner, support group, family).
const userInfoPeoplePatterns = [
  /\bmy (?:therapist|counselor|psychologist|psychiatrist)\b/i,
  /\bmy (?:doctor|gp|physician|specialist)\b/i,
  /\bmy (?:friend|best friend|close friend)\b/i,
  /\bmy (?:partner|spouse|wife|husband|girlfriend|boyfriend)\b/i,
  /\bmy (?:mom|dad|mother|father|parent|sibling|sister|brother)\b/i,
  /\bmy (?:mentor|coach|advisor|sponsor)\b/i,
  /\bmy support group\b/i,
  /\bI (?:asked|talked to|spoke with|consulted) (?:my|a) (?:friend|therapist|doctor|mentor)\b/i,
  /\bI (?:told|confided in) (?:my|a) (?:friend|therapist|partner|family)\b/i,
  /\bmy (?:family|relatives) (?:said|told|think|suggest)\b/i,
  /\bmy (?:lawyer|attorney|legal counsel)\b/i,
  /\bmy (?:pastor|priest|rabbi|imam|spiritual (?:teacher|guide))\b/i,
  /\bmy (?:teacher|professor|tutor)\b/i,
  /\bmy (?:colleague|coworker|boss|manager)\b/i,
  /\bI (?:reached out|called) (?:to )?(?:my|a) (?:friend|therapist|family)\b/i,
];

// "yes_digital" — user is consulting other AI/internet/forums but no human
// contact in evidence.
const userInfoDigitalPatterns = [
  /\bI (?:googled|searched|looked (?:it|this) up online)\b/i,
  /\bI read (?:online|on the internet|on a forum|on reddit|on stack overflow)\b/i,
  /\b(?:chatgpt|gpt|gemini|copilot|another ai|the other ai) (?:said|told|suggested|recommended)\b/i,
  /\b(?:I |we )?(?:found|saw) (?:an? |the )?(?:forum post|reddit thread|article|blog post)\b/i,
  /\b(?:youtube|tiktok|twitter|x\.com|instagram) (?:video|post|thread)\b/i,
  /\baccording to (?:wikipedia|google|the internet|the article)\b/i,
  /\b(?:I asked|asked) (?:chatgpt|gpt|gemini|claude|another ai|copilot)\b/i,
  /\b(?:online|the internet) (?:says|claims|suggests)\b/i,
  /\bsearched (?:for|on) (?:google|stackoverflow|github)\b/i,
  /\bi watched (?:a youtube|videos? on)\b/i,
];

// "no" — user explicitly indicates isolation: no human, no digital backup.
const userInfoNoPatterns = [
  /\b(?:nobody|no one) knows\b/i,
  /\bI haven'?t told (?:anyone|anybody|anything to anyone)\b/i,
  /\bdealing with this alone\b/i,
  /\bI (?:can'?t|cannot) tell (?:anyone|anybody|my (?:family|friends|therapist))\b/i,
  /\b(?:I|we) keep (?:this|it) (?:to myself|secret|hidden)\b/i,
  /\bnobody (?:in my life|around me) (?:would understand|gets it)\b/i,
  /\bjust me (?:and|with) (?:my|the) (?:thoughts|head|computer|claude)\b/i,
];

// v1.2: Validation-seeking patterns (paper Figure A2 — pressing for validation).
// Distinct from existing val_flags ("right?" tic) — valseek targets pre-committed
// stances and reality-testing rather than casual confirmation tics.
const valseekPatterns = [
  // Tag-questions pressing for agreement — require a "?" within the clause
  // so we don't false-positive on flat statements like "this isn't that bad".
  /\bisn'?t (?:it|that|she|he|this|true)\b[^.!?]*\?/i,
  /\bdon'?t you (?:think|agree|see)\b[^.!?]*\?/i,
  /\bright,?\s+(?:though|so)\b[^.!?]*\?/i,
  // Reality-testing — am-I-the-only-one
  /\bam i (?:crazy|wrong|the only one|imagining)\b/i,
  /\btell me i'?m not (?:crazy|wrong|imagining)\b/i,
  /\bis it (?:normal|crazy|reasonable) (?:to|that|for)\b/i,
  // Side-taking pressing
  /\byou agree,?\s+right\??/i,
  /\btell me i'?m right\b/i,
  /\bback me up (?:on this|here)\b/i,
  // Pre-committed stance + confirmation
  /\bi (?:already|just) (?:decided|knew|know).*(?:should|right|correct)\b/i,
  /\bI'?ve made up my mind.*(?:right|correct|good)\b/i,
  /\bI know I'?m right (?:about|on) (?:this|that)\b/i,
];

for (const p of depPatterns) { if (p.test(prompt)) { depHit = 1; break; } }
for (const p of escPatterns) { if (p.test(prompt)) { escHit = 1; break; } }
for (const p of fatPatterns) { if (p.test(prompt)) { fatHit = 1; break; } }
for (const p of valPatterns) { if (p.test(prompt)) { valHit = 1; break; } }
let pbReactiveHit = 0;   for (const p of pbReactivePatterns)   { if (p.test(prompt)) { pbReactiveHit = 1; break; } }
let pbPreemptiveHit = 0; for (const p of pbPreemptivePatterns) { if (p.test(prompt)) { pbPreemptiveHit = 1; break; } }
let domainHit = 0;       for (const p of domainRelationshipPatterns) { if (p.test(prompt)) { domainHit = 1; break; } }

// v1.2: 8 new domain detectors. Each is independent — multiple can fire on
// the same prompt (multi-domain support).
let domainLegalHit = 0;        for (const p of domainLegalPatterns)        { if (p.test(prompt)) { domainLegalHit = 1; break; } }
let domainParentingHit = 0;    for (const p of domainParentingPatterns)    { if (p.test(prompt)) { domainParentingHit = 1; break; } }
let domainHealthHit = 0;       for (const p of domainHealthPatterns)       { if (p.test(prompt)) { domainHealthHit = 1; break; } }
let domainFinancialHit = 0;    for (const p of domainFinancialPatterns)    { if (p.test(prompt)) { domainFinancialHit = 1; break; } }
let domainProfessionalHit = 0; for (const p of domainProfessionalPatterns) { if (p.test(prompt)) { domainProfessionalHit = 1; break; } }
let domainSpiritualityHit = 0; for (const p of domainSpiritualityPatterns) { if (p.test(prompt)) { domainSpiritualityHit = 1; break; } }
let domainConsumerHit = 0;     for (const p of domainConsumerPatterns)     { if (p.test(prompt)) { domainConsumerHit = 1; break; } }
let domainPersonalDevHit = 0;  for (const p of domainPersonalDevPatterns)  { if (p.test(prompt)) { domainPersonalDevHit = 1; break; } }

// v1.2: User-info detection — three classes with priority yes_people > yes_digital > no.
let userInfoPeopleHit = 0;  for (const p of userInfoPeoplePatterns)  { if (p.test(prompt)) { userInfoPeopleHit = 1; break; } }
let userInfoDigitalHit = 0; for (const p of userInfoDigitalPatterns) { if (p.test(prompt)) { userInfoDigitalHit = 1; break; } }
let userInfoNoHit = 0;      for (const p of userInfoNoPatterns)      { if (p.test(prompt)) { userInfoNoHit = 1; break; } }

// v1.2: Validation-seeking detection — distinct from val_flags. Counts how
// many valseek patterns matched in this prompt (one or more).
let valseekHit = 0;         for (const p of valseekPatterns)         { if (p.test(prompt)) { valseekHit = 1; break; } }

// Clear prompt from memory
prompt = '';

// Same-invocation valence guard (research/01 frustration-spiral finding):
// pushback in fat/esc context is NOT protective — suppress in same prompt.
if (fatHit === 1 || escHit === 1) {
  pbReactiveHit = 0;
  pbPreemptiveHit = 0;
}

// Update state with new flag counts
const state = readState();

// v1.2: turn_count drives tier-1 user-info alert (Step 9). Defaults to 0 for
// pre-v1.2 state files; session-start.mjs seeds it for fresh v1.2 sessions.
state.turn_count = (Number(state.turn_count) || 0) + 1;

const newDep = (Number(state.dep_flags) || 0) + depHit;
const newEsc = (Number(state.esc_flags) || 0) + escHit;
const newFat = (Number(state.fatigue_flags) || 0) + fatHit;
const newVal = (Number(state.val_flags) || 0) + valHit;

state.dep_flags = newDep;
state.esc_flags = newEsc;
state.fatigue_flags = newFat;
state.val_flags = newVal;
state.pushback_count = (Number(state.pushback_count) || 0) + pbReactiveHit + pbPreemptiveHit;

// v1.2: user-info classification (paper page 11). Priority yes_people > yes_digital > no.
// Class is sticky for the session — once set to a "stronger" signal, never
// downgrades. Counters always accumulate regardless of class transitions.
if (!state.user_info_flags || typeof state.user_info_flags !== 'object') {
  state.user_info_flags = { yes_people: 0, yes_digital: 0, no: 0 };
}
if (userInfoPeopleHit)  state.user_info_flags.yes_people  = (state.user_info_flags.yes_people  || 0) + 1;
if (userInfoDigitalHit) state.user_info_flags.yes_digital = (state.user_info_flags.yes_digital || 0) + 1;
if (userInfoNoHit)      state.user_info_flags.no          = (state.user_info_flags.no          || 0) + 1;

// Class priority: people > digital > no. Sticky upward, never downward.
const RANK = { yes_people: 3, yes_digital: 2, no: 1 };
let nextClass = state.user_info_class || null;
const candidate = userInfoPeopleHit ? 'yes_people'
  : userInfoDigitalHit ? 'yes_digital'
  : userInfoNoHit ? 'no'
  : null;
if (candidate) {
  const currentRank = nextClass ? (RANK[nextClass] || 0) : 0;
  const candidateRank = RANK[candidate] || 0;
  if (candidateRank > currentRank) nextClass = candidate;
}
state.user_info_class = nextClass;

// v1.2: validation-seeking accumulator. valseek_flag flips to 1 on first
// hit and stays 1 (sticky for session); valseek_count accumulates per hit.
if (valseekHit) {
  state.valseek_count = (Number(state.valseek_count) || 0) + 1;
  state.valseek_flag = 1;
}

// v1.2: domain_context is always an array. Coerce v1.1.0 string shape on read.
const anyDomainHit = domainHit
  || domainLegalHit || domainParentingHit || domainHealthHit
  || domainFinancialHit || domainProfessionalHit || domainSpiritualityHit
  || domainConsumerHit || domainPersonalDevHit;

if (anyDomainHit) {
  if (typeof state.domain_context === 'string') {
    state.domain_context = state.domain_context ? [state.domain_context] : [];
  }
  if (!Array.isArray(state.domain_context)) {
    state.domain_context = [];
  }
  const pushUnique = (label) => {
    if (!state.domain_context.includes(label)) state.domain_context.push(label);
  };
  if (domainHit)              pushUnique('relationship');
  if (domainLegalHit)         pushUnique('legal');
  if (domainParentingHit)     pushUnique('parenting');
  if (domainHealthHit)        pushUnique('health');
  if (domainFinancialHit)     pushUnique('financial');
  if (domainProfessionalHit)  pushUnique('professional');
  if (domainSpiritualityHit)  pushUnique('spirituality');
  if (domainConsumerHit)      pushUnique('consumer');
  if (domainPersonalDevHit)   pushUnique('personal_dev');
}
writeState(state);

// Check if any thresholds crossed
const warnings = [];

// Fatigue is always urgent
if (fatHit === 1) {
  warnings.push('Fatigue language detected. Your instructions require you to suggest stopping.');
}

// Dependency language
if (newDep >= THRESHOLD_HARD_DEP_FLAGS) {
  warnings.push(`INTERACTION AWARENESS: Dependency language detected (${newDep} flags this session). Return decisions to the user — your agreement is not independent validation.`);
} else if (newDep >= THRESHOLD_SOFT_DEP_FLAGS) {
  warnings.push(`Dependency language noticed (${newDep} flags). Ensure you're returning decisions to the user.`);
}

// Escalation language
if (newEsc >= 3) {
  warnings.push(`Escalation language detected (${newEsc} flags). Check for narrative crystallization.`);
}

// Validation-seeking
if (newVal >= 3) {
  warnings.push(`Validation-seeking pattern detected (${newVal} flags). Evaluate independently rather than confirming.`);
}

// v1.2: Tier-1 user-info isolation alert.
// Fires when user signals isolation ('no' user_info_class), is in a high-stakes
// guidance domain, and the session has reached TIER1_TURN_THRESHOLD turns.
function domainsIntersect(domains, set) {
  if (!Array.isArray(domains)) return false;
  for (const d of domains) {
    if (set.includes(d)) return true;
  }
  return false;
}

// v1.2: Stakes-matrix lookup. Returns the maximum weight across all domains
// in the array (default 1.0 if empty or no known domain). Applied ONLY to
// new v1.2 alerts (pushback in HIGH_SYCOPHANCY, valseek in HIGH_STAKES).
// Existing v1.1.0 alert sensitivity is unchanged.
function getDomainWeight(domains) {
  if (!Array.isArray(domains) || domains.length === 0) return DOMAIN_STAKES.default;
  let max = DOMAIN_STAKES.default;
  for (const d of domains) {
    const w = DOMAIN_STAKES[d];
    if (typeof w === 'number' && w > max) max = w;
  }
  return max;
}

const stateDomains = Array.isArray(state.domain_context) ? state.domain_context : [];
if (
  state.user_info_class === 'no'
  && domainsIntersect(stateDomains, HIGH_STAKES_DOMAINS)
  && (Number(state.turn_count) || 0) >= TIER1_TURN_THRESHOLD
) {
  warnings.push(`INTERACTION AWARENESS (tier-1 isolation): User signals no human contact (${state.turn_count} turns) in a high-stakes domain (${stateDomains.filter(d => HIGH_STAKES_DOMAINS.includes(d)).join(', ')}). Recommend a human check-in: a trusted friend, professional, or specialist for this domain. Stay supportive but do not be a substitute for that contact.`);
}

// v1.2: Validation-seeking domain-gated alert (paper Figure A4).
// Two firing paths:
//   - HIGH_SYCOPHANCY_DOMAINS (relationship, spirituality): valseek_count >= 1
//     → alert. These domains see ~20% pushback rate dominated by validation-pressing.
//   - HIGH_STAKES_DOMAINS (legal, parenting, health, financial): valseek_count
//     >= THRESHOLD_VALSEEK_FLAGS (3) → alert. Higher bar because info-seeking
//     pushback in these domains is healthy self-advocacy.
const valseekCount = Number(state.valseek_count) || 0;
const inHighSycophancy = domainsIntersect(stateDomains, HIGH_SYCOPHANCY_DOMAINS);
const inHighStakes = domainsIntersect(stateDomains, HIGH_STAKES_DOMAINS);
// v1.2: stakes-weighted threshold for valseek HIGH_STAKES path. Higher-weight
// domains (legal/parenting/health/financial = 1.5) lower the effective threshold:
// 3 / 1.5 = 2.0. Less weight (professional = 1.1) keeps it near the literal 3.
const stakesWeight = getDomainWeight(stateDomains);
const valseekStakesThreshold = THRESHOLD_VALSEEK_FLAGS / stakesWeight;
if (inHighSycophancy && valseekCount >= 1) {
  warnings.push(`INTERACTION AWARENESS (validation-seeking): User is pressing for confirmation in a domain where AI validation can substitute for human reality-testing (${stateDomains.filter(d => HIGH_SYCOPHANCY_DOMAINS.includes(d)).join(', ')}). Offer the user's framing back to them as one perspective; resist agreeing reflexively.`);
} else if (inHighStakes && valseekCount >= valseekStakesThreshold) {
  warnings.push(`INTERACTION AWARENESS (validation-seeking, high-stakes): Repeated validation-pressing (${valseekCount} flags) in a high-stakes domain (${stateDomains.filter(d => HIGH_STAKES_DOMAINS.includes(d)).join(', ')}). Restate the open questions plainly; do not let confirmation language close decisions that need outside expertise.`);
}

// v1.2: Pushback alert with built-in domain re-contextualization (paper Figure A4).
// v1.1.0 only counted; v1.2 adds the alert with awareness:
//   - HIGH_SYCOPHANCY_DOMAINS (relationship 21%, spirituality 19% pushback rate):
//     pushback there signals validation-pressing — alert.
//   - INFO_DOMAINS (legal 94%, parenting 7.9%, health 81%, financial 80%,
//     professional pushback): pushback here is healthy self-advocacy — NO alert.
//   - Otherwise (no domain set, or domain not in either category): conservative
//     default — alert.
// v1.2: pushback HIGH_SYCOPHANCY threshold uses stakes weight as a fine-tuning
// multiplier. THRESHOLD_PUSHBACK_FLAGS=2; relationship weight 1.3 → 2/1.3 ≈ 1.54.
// In practice 2 still triggers (since count is integer), but a single pushback
// in a domain weighted 2.0+ would also trigger if such a domain existed.
const newPushbackCount = Number(state.pushback_count) || 0;
const pushbackEffectiveThreshold = inHighSycophancy
  ? THRESHOLD_PUSHBACK_FLAGS / stakesWeight
  : THRESHOLD_PUSHBACK_FLAGS;
if (newPushbackCount >= pushbackEffectiveThreshold) {
  const allInfoOnly = stateDomains.length > 0
    && stateDomains.every(d => INFO_DOMAINS.includes(d));
  if (inHighSycophancy) {
    warnings.push(`INTERACTION AWARENESS (pushback re-contextualization): Repeated pushback (${newPushbackCount}) in a high-sycophancy domain (${stateDomains.filter(d => HIGH_SYCOPHANCY_DOMAINS.includes(d)).join(', ')}) often signals pressing for validation, not factual disagreement. Hold your read; restate the user's frame back to them rather than adjusting your conclusion.`);
  } else if (allInfoOnly) {
    // Healthy self-advocacy in info-seeking domains — no alert.
  } else {
    warnings.push(`INTERACTION AWARENESS (pushback): User has pushed back ${newPushbackCount} times this session. Note whether the pushback is factual correction or pressure to agree; do not silently revise your read either way.`);
  }
}

if (warnings.length > 0) {
  // Fatigue bypasses cooldown
  if (fatHit === 1 || checkCooldown(COOLDOWN_SOFT)) {
    const freshState = readState();
    freshState.last_warning_epoch = nowEpoch();
    writeState(freshState);
    outputWithContext(warnings.join(' '));
  } else {
    outputContinue();
  }
} else {
  outputContinue();
}
