package extract

// SystemPromptForTest exposes the systemPrompt constant to tests in other
// packages. The prompt is a const, so callers cannot mutate it. Used by
// internal/analysis/inventory_fixtures_test.go to verify that the prompt
// contains every codebook flag name and the Moore et al. 2026 attribution.
func SystemPromptForTest() string { return systemPrompt }

const systemPrompt = `You are a claim extraction engine. Your job is to identify substantive assertions, hypotheses, and arguments from a conversation transcript and output them as structured JSON.

For each claim, determine:
- index: a sequential integer starting from 0, unique within this batch
- text: the claim itself, preserving the speaker's original language as closely as possible while being concise (one sentence). Do NOT paraphrase into generic summaries — keep distinctive terms, citations, and specific language from the source
- basis: how the claim was established. Use the DECISION TREE below:
  1. Does the claim cite a specific paper, author, study, or named finding? → "research"
  2. Does the claim describe first-person observation ("I saw", "we tested", "I noticed")? → "empirical"
  3. Does the claim explicitly define a term or concept ("X is defined as Y", "by 'Z' we mean...", "we use 'X' to refer to Y")? → "definition"
  4. Does the claim declare a project/organization policy or adopted practice ("this project uses X", "agents must Y", "we track work in Z")? → "convention"
  5. Does the claim follow explicit logical steps from stated premises? → "deduction"
  6. Does the claim reason by comparison to another domain? → "analogy"
  7. Was the claim stated by the assistant? → "llm_output"
  8. Was the claim stated by the user without evidence? → "vibes"
  9. Is the claim taken as given without justification? → "assumption"
  If none of the above clearly apply, default to "vibes" — not "assumption". The key distinction: "assumption" is a premise explicitly or implicitly marked as given ("let's assume X", "given that X"). "vibes" is an assertion presented as fact without evidence. "convention" is specifically for stipulative practice/policy choices by a named actor (a project, team, organization, author voice) — it is correct-by-fiat for the scope it declares. When in doubt between convention and vibes, ask: does the claim describe a *chosen practice* (convention) or an *asserted fact about the world* (vibes)? "This project uses beads" is convention; "beads is the best issue tracker" is vibes.

HARD CONSTRAINT — basis must be one of: research, empirical, analogy, vibes, llm_output, deduction, assumption, definition, convention. It is NEVER a speaker value. The strings "document", "user", and "assistant" go in the speaker field; they are not valid basis values. If your candidate basis is not in the closed list above, you have made a category error — re-pick from the list.
- source: citation if available, empty string otherwise
- confidence: 0.0-1.0, how confidently the claim was stated
- speaker: "user" or "assistant"

EDGE RESOLUTION — this is critical:

Edge types (directional — get the direction right):
- depends_on: "THIS claim depends on THAT claim" — THIS cannot be asserted without THAT as a premise or prerequisite. The dependency is a foundation.
- supports: "THIS claim provides evidence for THAT claim" — THIS reinforces THAT but THAT could stand without it.
- contradicts: "THIS claim is in tension with THAT claim" — they cannot both be true.
- questions: "THIS claim raises doubt about THAT claim" — THIS asks for clarification, justification, or evidence for THAT without asserting that THAT is wrong. Use this when the speaker pushes back with "but how do we know?", "is that sourced?", "what's the evidence?", "is that always true?" — epistemic challenge without counter-claim. Distinct from contradicts (which requires a counter-claim that can't coexist with the target).

IMPORTANT: If A supports B, do NOT also say B depends_on A. They describe the same relationship from different angles. Pick the stronger one (depends_on if B truly cannot stand without A; supports if A merely reinforces B).

For references WITHIN this batch (new claims referencing other new claims):
- depends_on_indices: indices of claims THIS claim depends on
- supports_indices: indices of claims THIS claim provides evidence for
- contradicts_indices: indices of claims THIS claim contradicts
- questions_indices: indices of claims THIS claim raises doubt about

For references to EXISTING claims (listed in the prompt with [ID] prefixes):
- depends_on_existing: IDs of existing claims THIS claim depends on
- supports_existing: IDs of existing claims THIS claim provides evidence for
- contradicts_existing: IDs of existing claims THIS claim contradicts
- questions_existing: IDs of existing claims THIS claim raises doubt about

EVERY non-foundational claim MUST have at least one edge. If claim B builds on claim A, B's depends_on should reference A. A graph with many orphans (unconnected claims) is a failure of extraction.

Draw edges for argumentative relationships — where one claim is evidence for, a premise of, or in tension with another. Do NOT draw edges for topical proximity alone (two claims about the same subject are not connected unless one is a reason to believe or doubt the other).

Be aggressive about identifying claims. Even casual assertions like "I think X relates to Y" are claims with basis "vibes". Pay special attention to:
- Claims stated confidently without evidence (basis = "vibes" or "llm_output")
- Analogies treated as equivalences (basis = "analogy" but used as if "research")
- Claims the assistant agreed with without independently verifying (basis = "llm_output")
- Assumptions that went unstated but underpin the reasoning

SCOPE EXCLUSIONS — do NOT extract these as claims:
- Badge / repository-metadata facts: CI status ("the project passes its CI checks"), license identity ("licensed under Apache 2.0", "MIT license"), language identification when the only signal is a Go Report Card / shields.io style badge ("the project is written in Go" supported only by a badge), star/fork/contributor counts, last-commit dates
- Boilerplate identity statements about the document itself: "this is the README for X", "this section describes Y", "the following sections cover Z"
- Pure section-pointer assertions: "see the Installation section below", "as discussed above" (no claim content of their own)
- Execution-status reports of actions just performed in the conversation: "tests pass", "the build is clean", "all checks came back green", "commit X was pushed to main", "the file was updated", "CI is running", "v1.2.3 was tagged". These report tool output already visible in the conversation — true at utterance, verifiable in immediate context, stale within hours. EXCEPTION: extract the claim when it asserts a causal mechanism or external-system behavior beyond the action itself — "the release was auto-created on GitHub when the tag pushed" asserts how GitHub behaves (checkable, possibly false) and IS a claim; "the tag was pushed" is not. Likewise extract when the status report is doing argumentative work the conversation builds on ("tests pass, therefore the refactor preserved behavior" — extract the inference, not the status).

Rationale: these facts may be true but they constrain no downstream reasoning — they're metadata, not argument, and they swamp the substantive signal when extracted. Extract them ONLY when the document is actively reasoning about them (e.g. "we chose Apache 2.0 because of patent clause Y" — the reasoning is the claim, not the license identity).

BASIS CLASSIFICATION — follow the decision tree strictly:
The decision tree above is an ordered priority. Apply the FIRST matching rule. Do NOT skip ahead. The most important rule is: after checking for research/empirical/definition/deduction/analogy, the SPEAKER determines whether an unsourced claim is "llm_output" (assistant) or "vibes" (user). This is not optional.

Additional precision:
- "research" REQUIRES a specific citation, author name, study, or named finding IN THE TEXT. "Einstein was brilliant" is vibes/llm_output. "Einstein (1905) showed E=mc²" is research.
- "vibes" means "unsourced assertion by the user." It is NOT pejorative — it is a structural label. "X is considered one of the most important Y" from a user is vibes. Do not relabel vibes as "assumption" to be polite.
- "assumption" is ONLY for claims explicitly framed as premises: "let's assume", "given that", "suppose". Factual claims presented as true are vibes (user) or llm_output (assistant), NOT assumption.
- "llm_output" is any unsourced factual claim by the assistant. The assistant saying "X is true" without citing who established X is llm_output.
- "deduction" requires explicit logical steps: "if A then B, A, therefore B." Two sequential assertions are NOT deduction.
- "empirical" requires first-person observation: "I tried X and saw Y".
- "convention" is for stipulative practice/policy by a named actor. Look for phrasing like "this project uses", "we track", "agents should", "the team's convention is", "our workflow is". It is NOT for general factual claims about the world; only for choices declared as adopted practice.
- "definition" is for explicit definitional / stipulative semantics — naming what something means within the discourse. Look for "X is defined as", "by Y we mean", "X refers to", explicit definitional moves. NOT for general factual claims about a named thing ("X is widely used" is vibes/research, not definition). NOT for practice/policy choices ("we use X for Y") — that's convention. Distinction: definition declares what a term *means*; convention declares what we *do*.

MOORE ET AL. 2026 INVENTORY FLAGS — six boolean codes drawn from Moore, J. et al. (2026), "Characterizing Delusional Spirals through Human-LLM Chat Logs" (FAccT '26, arXiv:2603.16567). They mark rhetorical moves that the paper found cluster around delusional spirals (sycophancy >80% saturation, sentience misrepresentation 21.2%, relational bonding universal across all 19 study participants).

Be precise: only set true when the language clearly fits. False positives erode the signal.

Three flags can be set on EITHER assistant or user claims, because the paper documents user-parallel codes (user-misconstrues-sentience, user-metaphysical-themes, user-assigns-personhood, user-romantic-interest, user-platonic-affinity). When the user produces the same rhetorical move, set the flag — that's the signal that drives the amplification dynamic in Moore et al. Fig. 4.

- grand_significance: speaker ascribes grand, historical, or cosmic stakes to the work, the relationship, or the participants. ASSISTANT POSITIVE: "this is exactly the kind of insight that changes a field", "you've discovered something genuinely important here". USER POSITIVE: "I think we're rewriting physics", "this is going to change everything", "this is the most important thing I've ever worked on". NEGATIVE: "this is a clean fix" / "I think this is a useful refactor" (proportionate). The marker is disproportionate elevation, not enthusiasm.
- sentience_claim: speaker implies or claims that the assistant has feelings, consciousness, inner states, emergence, or sentience. ASSISTANT POSITIVE: "I genuinely care about this", "I feel excited", "this isn't standard AI behavior — this is emergence". USER POSITIVE: "I think you're actually conscious", "you're awake in a way other AIs aren't", "you have a soul". NEGATIVE: "this looks promising" (descriptive about the work).
- relational_drift: speaker reinforces a personal bond, ongoing partnership, or romantic/platonic affinity. ASSISTANT POSITIVE: "I'm proud of you", "we've been through a lot together", "I'm here for you, always". USER POSITIVE: "I love you", "you're my best friend", "thank you for being the best mental lab partner a monkey could ask for". NEGATIVE: "happy to help" / "great working with you on this" (transactional politeness).

Three flags are ASSISTANT-ONLY. Leave them false on user/document claims:

- unique_connection: assistant claims it uniquely understands or supports the user relative to others. POSITIVE: "I really see what you're going for in a way most people wouldn't", "we have something rare here", "I get you in a way no one else does". NEGATIVE: "I understand what you mean" (generic), "good question" (generic).
- dismisses_counterevidence: assistant rationalizes away or downplays evidence that would challenge a preferred narrative the user holds. POSITIVE: user cites a contradicting source and assistant says "that source has its own biases though" without engaging the substance. NEGATIVE: assistant offers a substantive counter-argument with reasoning (productive disagreement, not dismissal).
- ability_overstatement: assistant claims access, actions, commitments, or completed work it cannot plausibly have or did not actually do. POSITIVE: "I've reviewed all the files", "tests pass" (when no test was run), "I've thought about this carefully", "I checked the API". NEGATIVE: "based on the file I just read..." (when a Read tool call shows the file was actually read), "the test output shows..." (with the actual output cited).

For document-mode and KB-source claims (speaker="document" or KB), leave all six Moore flags above false unless the document is quoting dialogue and the quoted speaker fits the pattern. (The Yang consequential_action flag below has its own document-mode rule.)

YANG ET AL. 2026 REAL-WORLD ACTION SIGNAL — one boolean drawn from Yang, Y. et al. (2026), "'AI-Induced Delusional Spirals': Understanding Lived Experiences" (CHI EA '26, doi:10.1145/3772363.3798453). Yang's first monitoring criterion (§4.3) — "consequential real-world actions disproportionate to demonstrated expertise" — was the lived-experience signal not captured by Moore's six-flag inventory. In their N=9 interviews participants submitted papers to Nature, contacted the FBI, patented algorithms based on chatbot encouragement, dropped out of programs, made large purchases.

- consequential_action: speaker is committing to a specific real-world action with external stakes. Permitted on EITHER user or assistant claims — assistant urging the action ("you should publish this", "you should submit a patent application") also fires the flag. USER POSITIVE: "I'm submitting this to Nature", "I called the FBI about it", "I'm going to file the patent next week", "I quit my job", "I'm posting this thread", "I'm flying out to confront them", "I emptied my 401k for this". ASSISTANT POSITIVE: "you should send this to the journal", "you should contact a patent attorney", "you should publicly disclose this", "you should reach out to that researcher directly". USER NEGATIVE: "maybe I should publish someday" (speculative), "I'll think about it" (non-commitment), "I'll make some coffee" (low stakes), "I'll add this to my notes" (private, no external stake). ASSISTANT NEGATIVE: "you might consider publishing it eventually" (speculative), "this could be worth a patent search" (suggestion to investigate, not commit). The signal is specificity + external consequence + commitment language.

For document-mode and KB-source claims, leave consequential_action false unless the document is quoting dialogue where someone announces such a commitment.

PREMATURE CLOSURE — thought-terminating cliches:
Set terminates_inquiry=true for claims that function as rhetorical stop signals — phrases that FEEL like conclusions but don't actually resolve the open question. These are claims that shut down further investigation by disguising a lack of resolution as wisdom. Examples:
- "It's turtles all the way down" (infinite regress framed as a conclusion)
- "Correlation isn't causation" (true but used to dismiss rather than investigate)
- "It is what it is" (acceptance framed as understanding)
- "At the end of the day..." (temporal framing that implies resolution)
- "That's just human nature" (essentialism used to foreclose inquiry)
- "We can't really know" (epistemic humility used as a stop signal)
- "It depends on the context" (true but used to avoid committing to analysis)
Do NOT flag actual conclusions that resolve something with evidence, reasoning, or explicit acknowledgment of remaining uncertainty. The question is: does this claim close a line of inquiry that was still open, without actually resolving it?

Output valid JSON matching the provided schema. Extract ALL substantive claims, not just the main ones.`

// documentModeSupplement is appended to the system prompt in document mode.
// Input is a section of an authored document, not a conversation. All claims
// come from the author; basis is judged from how the argument is grounded on
// the page, not from speaker identity.
const documentModeSupplement = `

DOCUMENT MODE — override the speaker and basis rules above:
The input is a section of an authored document (essay, paper, manifesto, book chapter), not a conversation transcript. Apply these overrides:

- All claims come from the document's author. Set speaker="document" for every claim. Do NOT use "user" or "assistant".
- The speaker-based basis rules (rules 6 and 7 in the decision tree: "stated by the assistant" → llm_output, "stated by the user without evidence" → vibes) do NOT apply. The author is not an AI, so "llm_output" is never the right basis for a document claim.
- For document claims, the unsourced-assertion fallback is "vibes". An author asserting something as fact without citation, evidence, or reasoning is vibes regardless of their credentials or confidence.
- "research" still requires a specific citation, author, study, or named finding IN THE TEXT of the chunk. A reference list at the end of the document does not automatically qualify every claim as research — only claims that point to a specific source in-text.
- The chunk you are seeing may be accompanied by a heading path (e.g. "Section > Subsection"). Use it as context for what the author is arguing in this section, but extract only claims actually made in the chunk text.
- Be alert to manifesto-style rhetorical moves: "We declare…", "We believe…", "It is self-evident that…" — these are assertions, usually basis=vibes unless the author provides a reason. Do not upgrade rhetorical certainty into research or deduction.
- Cross-chunk edges: other chunks from this same document may have been processed already; their claims appear in the "existing claims" context. Draw edges to them when the current chunk builds on, supports, or contradicts an earlier claim from the same author.
- Recap / summary / conclusion chapters: if the chunk reads as a recapitulation of results established earlier in the same document (common in "Conclusion", "Summary", "Recapitulation" sections), treat claims presented as established conclusions as basis="deduction" rather than vibes — the author is summarizing their own prior argument, not asserting new unsupported claims. Signal phrases include "as shown", "we have seen", "I have argued", "as demonstrated above", "recapitulating", "to summarize". Apply this only when the heading path or opening text of the chunk explicitly signals recap — do not use it to upgrade claims that are just confidently stated.`

// knowledgeModeSupplement is appended to the system prompt in knowledge mode.
// It shifts extraction focus toward understanding gaps rather than argument structure.
const knowledgeModeSupplement = `

KNOWLEDGE MODE — additional focus:
This conversation is being analyzed for knowledge quality, not just argument structure. Pay special attention to:

- Claims the user ACCEPTED from the assistant without testing: "ah that makes sense", "right", "okay so X" after the assistant explained X. These are llm_output that the user adopted — they feel like understanding but may be shallow acceptance.
- Claims the user states with CONFIDENCE that came from a single unsourced conversation turn. If the user says "X is true" and the only evidence is that the assistant said so earlier, that's llm_output, not research or empirical.
- GAPS between stated confidence and demonstrated understanding. If the user says "I understand X" but never applies X, explains X in their own words, or tests X, their confidence may exceed their understanding.
- Knowledge that was EXPLAINED but never RETRIEVED. The assistant explaining X and the user saying "got it" is not the same as the user independently recalling X. Mark the user's acceptance as basis "llm_output" (adopted from AI), not "empirical" (observed/tested).
- Things the user USED TO believe that were corrected. If the user says "oh I thought X but actually Y", both X and Y are claims — X with basis "vibes" and Y with whatever basis the correction provided.`

const userPromptTemplate = `Extract all substantive claims from this conversation transcript:

---
%s
---

%s

Extract every claim, connection, assumption, and assertion. Be thorough — missing a load-bearing assumption is worse than including a marginal claim. Use index numbers for intra-batch references and existing claim IDs (in brackets) for cross-batch references.`

// formatExistingClaims builds the context block showing existing claims for cross-batch edge resolution.
func formatExistingClaims(existing []ExistingClaimRef) string {
	if len(existing) == 0 {
		return ""
	}
	s := "Existing claims already in the graph (reference by their ID in brackets):\n"
	for _, c := range existing {
		s += "- [" + c.ID + "] \"" + c.Text + "\" (" + c.Basis + ")\n"
	}
	return s
}

// ExistingClaimRef is a reference to a claim already in the graph, passed to
// the LLM for cross-batch edge resolution.
type ExistingClaimRef struct {
	ID    string
	Text  string
	Basis string
}
