import { t as createSubsystemLogger } from "./subsystem-qDUB67UD.js";
import { p as resolveDefaultModelForAgent } from "./model-selection-DCVjDyLz.js";
import "./defaults-COZq2n5p.js";
import { n as prepareSimpleCompletionModel } from "./simple-completion-runtime-BkSLbXlj.js";
import { t as FRAGMENT_TTL_MS } from "./fragment-types-B8q-zg6v.js";
import { randomUUID } from "node:crypto";
import { complete } from "@earendil-works/pi-ai";
//#region src/cognitive/insight/fragment-collector.ts
const log = createSubsystemLogger("cognitive/fragment-collector");
const VALID_FRAGMENT_KINDS = new Set([
	"assumption",
	"unresolved_tension",
	"methodological_habit",
	"knowledge_gap",
	"implicit_priority",
	"contradictory_positions"
]);
function createDefaultFragmentCollectorDeps() {
	return {
		complete,
		prepareModel: async (cfg, modelRef) => {
			const extractionModel = cfg.cognitive?.persona?.extractionModel;
			const explicit = modelRef ?? extractionModel;
			if (explicit) {
				const [provider, ...modelParts] = explicit.split("/");
				return prepareSimpleCompletionModel({
					cfg,
					provider,
					modelId: modelParts.join("/") || "glm-5-turbo"
				});
			}
			const resolved = resolveDefaultModelForAgent({ cfg });
			return prepareSimpleCompletionModel({
				cfg,
				provider: resolved.provider,
				modelId: resolved.model
			});
		}
	};
}
function shouldSkipTurn(userText) {
	const trimmed = userText.trim();
	if (trimmed.length < 20) return true;
	if (/^(好的|嗯|哦|收到|了解|谢谢|感谢|明白|知道|可以|行|对|是|不错|没问题)[！!。.？?]*$/.test(trimmed)) return true;
	if (/^[\p{P}\p{S}\s]+$/u.test(trimmed)) return true;
	return false;
}
/**
* Extract 0-2 thinking pattern fragments from a single conversation turn.
*
* This function **never throws** — all errors are caught and result in an
* empty array being returned.
*/
async function collectFragments(userText, assistantText, persona, config, deps) {
	try {
		if (shouldSkipTurn(userText)) {
			const reason = userText.trim().length < 20 ? "too_short" : "trivial";
			log.info("fragment collection: turn skipped", {
				reason,
				userLength: userText.length
			});
			return [];
		}
		const prompt = buildFragmentPrompt(userText, assistantText, persona);
		const modelRef = config.cognitive?.persona?.extractionModel;
		const prepared = await deps.prepareModel(config, modelRef);
		if ("error" in prepared) {
			log.warn("fragment-collector model preparation failed, skipping", { error: prepared.error });
			return [];
		}
		const messages = [{
			role: "user",
			content: prompt,
			timestamp: Date.now()
		}];
		const result = await deps.complete(prepared.model, { messages }, {
			apiKey: prepared.auth.apiKey,
			maxTokens: 800,
			temperature: .7,
			signal: AbortSignal.timeout(15e3)
		});
		const text = result.content.filter((block) => block.type === "text").map((block) => block.text).join("").trim();
		if (!text) {
			log.warn("fragment-collector LLM returned empty response, skipping", {
				contentBlocks: result.content.length,
				blockTypes: result.content.map((b) => b.type)
			});
			return [];
		}
		log.debug("fragment-collector LLM raw response", { rawLen: text.length });
		const fragments = parseFragments(text);
		if (fragments.length > 0) log.debug("fragments extracted", {
			count: fragments.length,
			kinds: fragments.map((f) => f.kind),
			domains: [...new Set(fragments.flatMap((f) => f.domains))]
		});
		else log.debug("fragment-collector parseFragments returned 0", { rawLen: text.length });
		return fragments;
	} catch (err) {
		const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
		log.warn(`fragment-collector ${isTimeout ? "timed out" : "failed"}: ${String(err)}, skipping`);
		return [];
	}
}
function buildFragmentPrompt(userText, assistantText, persona) {
	const domainNames = Object.entries(persona.domains).toSorted(([, a], [, b]) => b.lastMentioned - a.lastMentioned).slice(0, 20).map(([name]) => name);
	const truncatedUser = userText.length > 500 ? userText.slice(0, 500) + "…" : userText;
	const truncatedAssistant = assistantText.length > 500 ? assistantText.slice(0, 500) + "…" : assistantText;
	return `Analyze this conversation turn and detect STRUCTURAL thinking patterns — not topics, but how the user thinks.

User's known domains: ${domainNames.length > 0 ? domainNames.join(", ") : "(not yet established)"}

USER SAID:
${truncatedUser}

ASSISTANT REPLIED:
${truncatedAssistant}

Detect up to 2 of these thinking pattern types:
- assumption: unstated assumption the user is making
- unresolved_tension: contradictory positions or ambivalence the user hasn't resolved
- methodological_habit: recurring approach or mental model the user applies
- knowledge_gap: something the user doesn't know they don't know (meta-blindness)
- implicit_priority: a value judgment or priority revealed indirectly
- contradictory_positions: two incompatible views expressed in the same turn

CRITICAL: Do NOT extract topics. Extract what they DON'T know about their own thinking. Focus on structural cognitive patterns, not content.

Each fragment MUST include at least 2 domains (fields of knowledge). Look for connections between domains in the user's thinking. For example, if they discuss a programming concept, consider what broader domain it connects to (software architecture, distributed systems, etc.). Use the user's known domains above when possible.

Respond with ONLY a JSON array (no markdown, no code fences):
[
  {
    "kind": "assumption",
    "evidence": "brief quote or paraphrase from the conversation",
    "domains": ["domain-a", "domain-b"],
    "structuralTag": "one-word tag for the thinking pattern",
    "strength": 0.7
  }
]

Return [] if no clear thinking patterns are detectable. Maximum 2 items.`;
}
function parseFragments(text) {
	try {
		const cleaned = text.replace(/^```(?:json)?\s*/m, "").replace(/\s*```$/m, "").trim();
		const start = cleaned.indexOf("[");
		const end = cleaned.lastIndexOf("]");
		if (start === -1 || end === -1 || end <= start) return [];
		const jsonStr = cleaned.slice(start, end + 1);
		let parsed;
		try {
			parsed = JSON.parse(jsonStr);
		} catch {
			log.warn("parseFragments: JSON parse failed", { raw: jsonStr.slice(0, 200) });
			return [];
		}
		if (!Array.isArray(parsed)) return [];
		const now = Date.now();
		return parsed.filter((item) => typeof item === "object" && item !== null).filter((item) => {
			const kind = item.kind;
			return typeof kind === "string" && VALID_FRAGMENT_KINDS.has(kind);
		}).filter((item) => {
			const evidence = item.evidence;
			const structuralTag = item.structuralTag;
			return typeof evidence === "string" && evidence.length > 0 && typeof structuralTag === "string" && structuralTag.length > 0;
		}).slice(0, 2).map((item) => {
			const strength = typeof item.strength === "number" ? Math.max(0, Math.min(1, item.strength)) : .5;
			const domains = Array.isArray(item.domains) ? item.domains.filter((d) => typeof d === "string").map(String) : [];
			return {
				id: randomUUID(),
				userId: "",
				createdAt: now,
				expiresAt: now + FRAGMENT_TTL_MS,
				kind: item.kind,
				evidence: String(item.evidence).slice(0, 200),
				domains,
				structuralTag: String(item.structuralTag),
				strength,
				initialStrength: strength
			};
		});
	} catch (err) {
		log.warn("parseFragments: unexpected error", { error: String(err) });
		return [];
	}
}
//#endregion
export { collectFragments, createDefaultFragmentCollectorDeps };
