import { t as createSubsystemLogger } from "./subsystem-qDUB67UD.js";
import { n as decayAllBandits, r as decayBandit } from "./preference-learner-CPBoPJrB.js";
import { n as getDecayMultiplier, t as computeLifecycleStage } from "./lifecycle-DwLKRe0M.js";
//#region src/cognitive/persona/contradiction-resolver.ts
function detectContradictions(existingTraits, incomingAttributes, nowMs) {
	const records = [];
	const resolvedTraits = {};
	for (const attr of incomingAttributes) {
		if (!attr.field.startsWith("identity.coreTraits.")) continue;
		const traitName = attr.field.replace("identity.coreTraits.", "");
		const existing = existingTraits[traitName];
		if (!existing) continue;
		if (existing.value === attr.value) continue;
		const resolution = resolveContradiction(existing.confidence, attr.confidence, existing.source, attr.source);
		records.push({
			field: attr.field,
			oldValue: existing.value,
			newValue: attr.value,
			oldConfidence: existing.confidence,
			newConfidence: attr.confidence,
			oldSource: existing.source,
			newSource: attr.source,
			resolution,
			resolvedAt: nowMs
		});
		resolvedTraits[traitName] = {
			value: resolution === "resolved_old" ? existing.value : attr.value,
			resolution
		};
	}
	return {
		records,
		resolvedTraits
	};
}
/**
* Ordered resolution rules (first match wins):
* 1. explicit incoming overrides non-explicit existing → resolved_new
* 2. old confidence exceeds new by > 0.3              → resolved_old
* 3. new confidence exceeds old by > 0.3              → resolved_new
* 4. otherwise (recency bias)                          → resolved_new
*/
function resolveContradiction(oldConfidence, newConfidence, oldSource, newSource) {
	if (newSource === "explicit" && oldSource !== "explicit") return "resolved_new";
	if (oldConfidence - newConfidence > .3) return "resolved_old";
	return "resolved_new";
}
//#endregion
//#region src/cognitive/persona/curator.ts
const log = createSubsystemLogger("cognitive/persona-curator");
const DOMAIN_DEPTH_HALF_LIFE_MS = 720 * 60 * 60 * 1e3;
const AUTO_BLACKLIST_NEGATION_THRESHOLD = 3;
const AUTO_BLACKLIST_WINDOW_MS = 720 * 60 * 60 * 1e3;
const HALF_LIFE_BY_CATEGORY = {
	tool_config: 7,
	contextual_fact: 14,
	domain_knowledge: 30,
	stated_preference: 60,
	behavioral_pattern: 90,
	goal_or_aspiration: 90
};
const BACKWARD_COMPAT_EXCLUDE_CATEGORIES = new Set(["tool_config", "contextual_fact"]);
const INSIGHT_ECHO_PATTERNS = [
	/receives?\s+(automated\s+)?cognitive\s+insight/i,
	/最近出现了?一些值得关注的新方向/,
	/结合你在这个领域的深度理解/,
	/可能会影响你的技术决策/,
	/cognitive insight (notifications?|alerts?)/i,
	/new (trends|directions) in this (field|domain)/i
];
function computeInterestPhase(domain, nowMs) {
	const THIRTY_DAYS_MS = 720 * 60 * 60 * 1e3;
	const FOURTEEN_DAYS_MS = 336 * 60 * 60 * 1e3;
	const SEVEN_DAYS_MS = 10080 * 60 * 1e3;
	const ageSinceLastMention = nowMs - domain.lastMentioned;
	const prevPhase = domain.phase;
	const wasInactive = prevPhase === "dormant" || prevPhase === "declining";
	const isNowActive = ageSinceLastMention < SEVEN_DAYS_MS;
	if (wasInactive && isNowActive) return "revived";
	if (ageSinceLastMention > THIRTY_DAYS_MS) return "dormant";
	if (ageSinceLastMention > FOURTEEN_DAYS_MS) return "declining";
	const insightCount = domain.keyInsights.length + (domain.insights?.length ?? 0);
	if (domain.recurrence <= 2 && domain.depth < 2 && insightCount < 3) return "emergent";
	if (isNowActive) return "stable";
	return prevPhase ?? "emergent";
}
function toTypedInsight(extracted, nowMs) {
	return {
		text: extracted.text,
		category: extracted.category,
		confidence: extracted.confidence,
		source: extracted.source,
		firstObserved: nowMs,
		lastReinforced: nowMs,
		evidenceCount: 1,
		halfLifeDays: HALF_LIFE_BY_CATEGORY[extracted.category]
	};
}
function textSimilar(a, b) {
	const normA = a.trim().toLowerCase();
	const normB = b.trim().toLowerCase();
	if (normA === normB) return true;
	const maxLen = Math.max(normA.length, normB.length);
	if (maxLen === 0) return true;
	return levenshteinDistance(normA, normB) / maxLen < .3;
}
function levenshteinDistance(a, b) {
	const m = a.length;
	const n = b.length;
	const dp = Array.from({ length: n + 1 }, (_, j) => j);
	for (let i = 1; i <= m; i++) {
		const prev = dp[0];
		dp[0] = i;
		for (let j = 1; j <= n; j++) if (a[i - 1] === b[j - 1]) dp[j] = prev;
		else dp[j] = 1 + Math.min(prev, dp[j], dp[j - 1]);
	}
	return dp[n];
}
function mergeTypedInsights(existing, incoming) {
	const result = [...existing];
	for (const inc of incoming) {
		let matched = false;
		for (let i = 0; i < result.length; i++) if (textSimilar(result[i].text, inc.text)) {
			const ex = result[i];
			const totalEvidence = ex.evidenceCount + 1;
			const existingWeight = ex.evidenceCount / totalEvidence;
			const incomingWeight = 1 / totalEvidence;
			result[i] = {
				...ex,
				confidence: Math.min(1, ex.confidence * existingWeight + inc.confidence * incomingWeight),
				evidenceCount: totalEvidence,
				lastReinforced: inc.lastReinforced,
				source: inc.source === "explicit" ? "explicit" : ex.source
			};
			matched = true;
			break;
		}
		if (!matched) result.push(inc);
	}
	return result.slice(-20);
}
/**
* Merge extraction results into an existing PersonaTree.
* Returns a new PersonaTree (does not mutate input).
*/
function mergeExtraction(persona, extraction, nowMs) {
	const now = nowMs ?? Date.now();
	const coreTraitAttrs = extraction.attributes.filter((a) => a.field.startsWith("identity.coreTraits."));
	const { resolvedTraits } = detectContradictions(persona.identity.coreTraits, coreTraitAttrs, now);
	const newCoreTraits = { ...persona.identity.coreTraits };
	for (const attr of extraction.attributes) if (attr.field.startsWith("identity.coreTraits.")) {
		const traitName = attr.field.replace("identity.coreTraits.", "");
		if (resolvedTraits[traitName]?.resolution === "resolved_old") continue;
		newCoreTraits[traitName] = mergeConfidenceValue(newCoreTraits[traitName], {
			value: attr.value,
			confidence: attr.confidence,
			evidenceCount: 1,
			lastUpdated: now,
			source: attr.source
		}, now);
	}
	const extractionDomainNames = new Set(extraction.domains.map((d) => d.name));
	const newDomains = { ...persona.domains };
	const isPlausibleKeyInsight = (s) => {
		if (s.length < 4 || s.length > 200) return false;
		for (const pat of INSIGHT_ECHO_PATTERNS) if (pat.test(s)) return false;
		return true;
	};
	const decayMultiplier = getDecayMultiplier(persona.lifecycle);
	for (const [name, node] of Object.entries(newDomains)) if (!extractionDomainNames.has(name)) {
		const ageMs = now - node.lastMentioned;
		const decayFactor = Math.exp(-Math.LN2 * ageMs / (DOMAIN_DEPTH_HALF_LIFE_MS / decayMultiplier));
		const decayedDepth = node.depth * decayFactor;
		if (decayedDepth < .5) delete newDomains[name];
		else newDomains[name] = {
			...node,
			depth: Math.round(decayedDepth * 10) / 10
		};
	}
	for (const domain of extraction.domains) {
		const existing = newDomains[domain.name];
		if (domain.negated) {
			if (existing) {
				const reducedDepth = Math.max(.5, existing.depth * .7);
				newDomains[domain.name] = {
					...existing,
					depth: reducedDepth,
					negationSignals: (existing.negationSignals ?? 0) + 1,
					lastNegatedAt: now
				};
			}
			continue;
		}
		const incomingTyped = (domain.typedInsights ?? []).map((ei) => toTypedInsight(ei, now));
		const backwardCompatTexts = incomingTyped.filter((ti) => !BACKWARD_COMPAT_EXCLUDE_CATEGORIES.has(ti.category)).map((ti) => ti.text).filter(isPlausibleKeyInsight);
		if (existing) {
			const mergedInsights = incomingTyped.length > 0 ? mergeTypedInsights(existing.insights ?? [], incomingTyped) : existing.insights;
			const prevPhase = existing.phase;
			const newPhase = computeInterestPhase({
				...existing,
				recurrence: existing.recurrence + 1,
				lastMentioned: now
			}, now);
			const phaseChanged = prevPhase !== newPhase;
			if (phaseChanged) log.info("domain phase transition", {
				domain: domain.name,
				from: prevPhase,
				to: newPhase
			});
			newDomains[domain.name] = {
				...existing,
				depth: Math.max(existing.depth, domain.depth),
				recurrence: existing.recurrence + 1,
				lastMentioned: now,
				keyInsights: [...new Set([
					...existing.keyInsights,
					...domain.insights.filter(isPlausibleKeyInsight),
					...backwardCompatTexts
				])].slice(-20),
				insights: mergedInsights,
				activeQuestions: [...new Set([...existing.activeQuestions, ...domain.questions])].slice(-10),
				negationSignals: existing.negationSignals ?? 0,
				phase: newPhase,
				phaseEnteredAt: phaseChanged ? now : existing.phaseEnteredAt
			};
		} else {
			const freshNode = {
				depth: domain.depth,
				recurrence: 1,
				lastMentioned: now,
				keyInsights: [...domain.insights.filter(isPlausibleKeyInsight), ...backwardCompatTexts],
				insights: incomingTyped.length > 0 ? incomingTyped : void 0,
				activeQuestions: domain.questions,
				negationSignals: 0
			};
			freshNode.phase = computeInterestPhase(freshNode, now);
			freshNode.phaseEnteredAt = now;
			log.info("new domain discovered", {
				domain: domain.name,
				depth: freshNode.depth.toFixed(1),
				insights: freshNode.insights?.length ?? 0
			});
			newDomains[domain.name] = freshNode;
		}
	}
	const newBlacklist = [...persona.domainBlacklist ?? []];
	for (const domain of extraction.blacklistRequests ?? []) if (!newBlacklist.includes(domain)) newBlacklist.push(domain);
	for (const [name, node] of Object.entries(newDomains)) {
		if (newBlacklist.includes(name)) continue;
		if (node.negationSignals >= AUTO_BLACKLIST_NEGATION_THRESHOLD && node.lastNegatedAt !== void 0 && now - node.lastNegatedAt <= AUTO_BLACKLIST_WINDOW_MS) newBlacklist.push(name);
	}
	for (const blacklisted of newBlacklist) delete newDomains[blacklisted];
	const hasCJK = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u.test.bind(/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u);
	const TECH_DOMAIN_TERMS = new Set([
		"kubernetes",
		"docker",
		"typescript",
		"python",
		"javascript",
		"rust",
		"react",
		"vue",
		"angular",
		"node",
		"deno",
		"bun",
		"go",
		"java",
		"k8s",
		"api",
		"rest",
		"graphql",
		"sql",
		"redis",
		"mongodb",
		"git",
		"github",
		"linux",
		"aws",
		"gcp",
		"azure",
		"devops"
	]);
	const ENGLISH_STOPWORDS = new Set([
		"a",
		"an",
		"the",
		"is",
		"are",
		"was",
		"were",
		"be",
		"been",
		"being",
		"do",
		"does",
		"did",
		"will",
		"would",
		"can",
		"could",
		"should",
		"may",
		"might",
		"shall",
		"to",
		"of",
		"in",
		"for",
		"on",
		"with",
		"at",
		"by",
		"from",
		"as",
		"into",
		"about",
		"this",
		"that",
		"these",
		"those",
		"it",
		"its",
		"or",
		"and",
		"but",
		"not",
		"no",
		"so",
		"than",
		"too",
		"very",
		"if",
		"then",
		"else",
		"when",
		"where",
		"which",
		"who",
		"whom",
		"how",
		"what",
		"why",
		"all",
		"each",
		"every",
		"both",
		"few",
		"more",
		"most",
		"other",
		"some",
		"such",
		"only",
		"own",
		"same",
		"also",
		"just",
		"one",
		"two",
		"here",
		"there",
		"now",
		"up",
		"out",
		"off",
		"over",
		"under",
		"i",
		"me",
		"my",
		"we",
		"us",
		"our",
		"you",
		"your",
		"he",
		"him",
		"his",
		"she",
		"her",
		"they",
		"them",
		"their",
		"has",
		"have",
		"had",
		"get",
		"got",
		"make",
		"made",
		"go",
		"went",
		"come",
		"came",
		"take",
		"took",
		"give",
		"gave",
		"see",
		"saw",
		"know",
		"knew",
		"think",
		"thought",
		"say",
		"said",
		"tell",
		"told",
		"find",
		"found",
		"use",
		"used",
		"mannerisms",
		"provided",
		"available",
		"following",
		"however"
	]);
	const isPlausibleEnglishTopic = (s) => {
		const lower = s.toLowerCase().replace(/[^a-z0-9\s.-]/g, "").trim();
		if (TECH_DOMAIN_TERMS.has(lower)) return true;
		const words = lower.split(/\s+/).filter((w) => w.length > 0);
		if (words.length === 0) return false;
		if (words.length === 1 && !TECH_DOMAIN_TERMS.has(words[0])) return false;
		return words.filter((w) => !ENGLISH_STOPWORDS.has(w)).length >= 2;
	};
	const isValidFocus = (s) => {
		if (s.length < 2 || s.length > 30 || s.startsWith("```") || /^[^\p{L}\p{N}]+$/u.test(s)) return false;
		if (!hasCJK(s) && !isPlausibleEnglishTopic(s)) return false;
		return true;
	};
	const newRecentFocus = [...new Set([...extraction.recentFocus, ...persona.recentFocus])].filter(isValidFocus).slice(0, 10);
	const newRapport = {
		...persona.rapport,
		totalExchanges: persona.rapport.totalExchanges + 1
	};
	const newMoodHistory = [...persona.moodHistory ?? []];
	if (extraction.sentiment) {
		const prevPositive = newMoodHistory.slice(-2).some((s) => s.sentiment.label === "excited" || s.sentiment.label === "positive");
		const currNegative = extraction.sentiment.label === "frustrated" || extraction.sentiment.label === "negative";
		const currPositive = extraction.sentiment.label === "excited" || extraction.sentiment.label === "positive";
		const trend = prevPositive && currNegative ? "declining" : !prevPositive && currPositive ? "improving" : "stable";
		newMoodHistory.push({
			sentiment: extraction.sentiment,
			timestamp: now,
			trend
		});
	}
	const newLifecycle = {
		...persona.lifecycle,
		lastActiveAt: now
	};
	if (new Date(persona.lifecycle.lastActiveAt).toDateString() !== new Date(now).toDateString()) newLifecycle.totalActiveDays += 1;
	const prevStage = newLifecycle.stage;
	if (prevStage === "dormant" || prevStage === "lapsed") newLifecycle.stage = "active";
	const nextStage = computeLifecycleStage(newLifecycle, newRapport.totalExchanges, now);
	if (nextStage !== newLifecycle.stage) {
		newLifecycle.stage = nextStage;
		newLifecycle.lastStageTransitionAt = now;
	}
	const expertDomains = [];
	const interestDomains = [];
	const curiosityDomains = [];
	for (const [name, node] of Object.entries(newDomains)) if (node.depth >= 4 && node.recurrence >= 10) expertDomains.push(name);
	else if (node.depth >= 2) interestDomains.push(name);
	else if (node.depth >= 1) curiosityDomains.push(name);
	const displayName = newCoreTraits["称呼"]?.confidence >= .5 ? String(newCoreTraits["称呼"].value) : persona.identity.displayName;
	log.info("displayName synced", {
		displayName,
		source: newCoreTraits["称呼"]?.confidence >= .5 ? "coreTraits" : "existing"
	});
	const newIdentity = {
		...persona.identity,
		coreTraits: newCoreTraits,
		displayName,
		expertDomains: expertDomains.length > 0 ? expertDomains : persona.identity.expertDomains,
		interestDomains: interestDomains.length > 0 ? interestDomains : persona.identity.interestDomains,
		curiosityDomains: curiosityDomains.length > 0 ? curiosityDomains : persona.identity.curiosityDomains
	};
	return {
		...persona,
		identity: newIdentity,
		domains: newDomains,
		recentFocus: newRecentFocus,
		rapport: newRapport,
		moodHistory: newMoodHistory.slice(-10),
		domainBlacklist: newBlacklist,
		lifecycle: newLifecycle
	};
}
function mergeConfidenceValue(existing, incoming, now) {
	if (!existing) return incoming;
	const totalEvidence = existing.evidenceCount + 1;
	const existingWeight = existing.evidenceCount / totalEvidence;
	const incomingWeight = 1 / totalEvidence;
	const blendedConfidence = existing.confidence * existingWeight + incoming.confidence * incomingWeight;
	const adjustedConfidence = existing.value !== incoming.value && incoming.source !== "explicit" ? blendedConfidence * .8 : blendedConfidence;
	return {
		value: incoming.source === "explicit" ? incoming.value : existing.value,
		confidence: Math.min(1, adjustedConfidence),
		evidenceCount: totalEvidence,
		lastUpdated: now,
		source: incoming.source === "explicit" ? "explicit" : existing.source
	};
}
/**
* Prune persona to keep it manageable.
* - Remove traits with very low confidence after many observations
* - Remove domains not mentioned in 30 days
* - Cap list sizes
*/
function prunePersona(persona, nowMs) {
	const now = nowMs ?? Date.now();
	const THIRTY_DAYS = 720 * 60 * 60 * 1e3;
	const prunedTraits = {};
	for (const [key, val] of Object.entries(persona.identity.coreTraits)) {
		if (val.evidenceCount >= 5 && val.confidence < .2) continue;
		prunedTraits[key] = val;
	}
	const prunedDomains = {};
	for (const [name, domain] of Object.entries(persona.domains)) {
		if (now - domain.lastMentioned > THIRTY_DAYS && domain.recurrence < 3) continue;
		if ((domain.negationSignals ?? 0) >= 3 && domain.depth < 2) continue;
		const cleanedInsights = domain.keyInsights.filter((s) => {
			if (s.length < 4 || s.length > 200) return false;
			for (const pat of INSIGHT_ECHO_PATTERNS) if (pat.test(s)) return false;
			return true;
		});
		const decayedTypedInsights = (domain.insights ?? []).map((ti) => {
			const ageMs = now - ti.lastReinforced;
			const halfLifeMs = ti.halfLifeDays * 24 * 60 * 60 * 1e3;
			const decayFactor = Math.exp(-Math.LN2 * ageMs / halfLifeMs);
			return {
				...ti,
				confidence: ti.confidence * decayFactor
			};
		}).filter((ti) => ti.confidence >= .1);
		const updatedDomain = {
			...domain,
			keyInsights: cleanedInsights
		};
		if (domain.insights !== void 0 || decayedTypedInsights.length > 0) updatedDomain.insights = decayedTypedInsights.length > 0 ? decayedTypedInsights : void 0;
		prunedDomains[name] = updatedDomain;
	}
	const decayedProfile = decayAllBandits(persona.feedbackProfile, now);
	const prunedBandits = {};
	for (const [topic, bandit] of Object.entries(decayedProfile.topicBandits)) {
		if (bandit.alpha <= 2.01 && bandit.beta <= 1.01) continue;
		prunedBandits[topic] = bandit;
	}
	const prunedPromptBandits = {};
	if (decayedProfile.promptBandits) for (const [key, bandit] of Object.entries(decayedProfile.promptBandits)) {
		const decayed = decayBandit(bandit, now);
		if (decayed.alpha > 2.01 || decayed.beta > 1.01) prunedPromptBandits[key] = decayed;
	}
	const prunedModeBandits = {};
	if (decayedProfile.modeBandits) for (const [key, bandit] of Object.entries(decayedProfile.modeBandits)) {
		const decayed = decayBandit(bandit, now);
		if (decayed.alpha > 2.01 || decayed.beta > 1.01) prunedModeBandits[key] = decayed;
	}
	const traitEntries = Object.entries(prunedTraits).sort((a, b) => b[1].evidenceCount * b[1].confidence - a[1].evidenceCount * a[1].confidence).slice(0, 30);
	const finalTraits = Object.fromEntries(traitEntries);
	const domainEntries = Object.entries(prunedDomains).sort((a, b) => {
		const recencyA = 1 / Math.max(1, (now - a[1].lastMentioned) / THIRTY_DAYS);
		const recencyB = 1 / Math.max(1, (now - b[1].lastMentioned) / THIRTY_DAYS);
		return b[1].recurrence * b[1].depth * recencyB - a[1].recurrence * a[1].depth * recencyA;
	}).slice(0, 50);
	const finalDomains = Object.fromEntries(domainEntries);
	return {
		...persona,
		identity: {
			...persona.identity,
			coreTraits: finalTraits
		},
		domains: finalDomains,
		feedbackProfile: {
			...decayedProfile,
			topicBandits: prunedBandits,
			...Object.keys(prunedPromptBandits).length > 0 ? { promptBandits: prunedPromptBandits } : {},
			...Object.keys(prunedModeBandits).length > 0 ? { modeBandits: prunedModeBandits } : {}
		}
	};
}
//#endregion
export { HALF_LIFE_BY_CATEGORY, mergeExtraction, mergeTypedInsights, prunePersona };
