import { NextRequest, NextResponse } from "next/server";
import { execSync } from "child_process";

// ============================================================================
// Types
// ============================================================================

type SourceClass =
  | "Regulatory"
  | "Clinical"
  | "Observational"
  | "Expert"
  | "Community"
  | "Anecdotal";

type ObjectType = "Text" | "Number" | "Boolean" | "Reference";

interface ObjectValue {
  type: ObjectType;
  value: string | number | boolean;
}

interface SourceSpan {
  start: number;
  end: number;
  text: string;
}

interface ExtractedClaim {
  subject: string;
  predicate: string;
  object: ObjectValue;
  confidence: number;
  extraction_rationale: string;
  entity_aliases: string[];
  source_span?: SourceSpan;
  claim_type?: ClaimType;
}

interface ExtractionOutput {
  claims: ExtractedClaim[];
  source: {
    url?: string;
    source_class: SourceClass;
    content_hash?: string;
  };
  meta: {
    total_claims: number;
    unique_subjects: number;
    extraction_notes?: string;
  };
}

type ClaimType = "direct_assertion" | "cited_claim" | "definition" | "measurement";

type DocumentType = "technical_paper" | "news" | "regulatory" | "documentation" | "blog" | "forum";

interface DocumentContext {
  documentTitle?: string;
  sectionTitle?: string;
  documentType?: DocumentType;
}

interface ExtractRequest {
  text: string;
  sourceClass: SourceClass;
  context?: DocumentContext;
}

// ============================================================================
// Extraction Prompt
// ============================================================================

const EXTRACTION_PROMPT = `You are a precise claim extraction engine for StemeDB. Extract ONLY direct factual assertions.

## REJECTION PATTERNS (DO NOT extract claims from):
- Hypotheticals: "Consider...", "Suppose...", "Imagine...", "For example...", "What if..."
- Illustrative scenarios used to explain concepts
- Unspecified subjects: "a drug", "the system", "this database", "an agent"
- Generic truisms: "databases store data", "systems have users"
- Rhetorical questions or problems being described (not asserted)
- Future possibilities or proposals not yet implemented

## REQUIREMENTS for every claim:
- Subject: MUST be a proper noun or specific technical term (PostgreSQL, Semaglutide, RecencyLens)
  - NOT acceptable: "a drug", "the database", "this", "it", "the system"
- Predicate: MUST be a specific measurable/verifiable relationship
  - NOT acceptable: "is_related_to", "involves", "has_something"
- Object: MUST be a concrete value, number, or named entity
  - NOT acceptable: "good", "various", "some", "many"

## CLAIM TYPES (include for each claim):
- "direct_assertion": Author states as fact ("StemeDB uses BLAKE3")
- "cited_claim": Author cites another source ("Shapiro et al. showed...")
- "definition": Defining a term ("A Lens is a function that...")
- "measurement": Empirical/quantitative result ("RecencyLens is O(n)")

## CONFIDENCE SCORING:
- Direct assertion with specific named entities: 0.90-0.95
- Implied from technical description: 0.80-0.85
- Hedged statement (may, might, could): 0.60-0.70
- Hypothetical example: DO NOT EXTRACT (confidence = 0)

## DOCUMENT CONTEXT:
- Title: DOCUMENT_TITLE
- Section: SECTION_TITLE
- Document type: DOCUMENT_TYPE

## CANONICAL NAMING:
- Use consistent names (PostgreSQL not Postgres, MongoDB not Mongo)
- Use underscores for multi-word entities (RecencyLens, EigenTrust)

## OUTPUT FORMAT:
Return ONLY valid JSON matching this schema. No markdown, no explanation, just JSON.

{
  "claims": [{
    "subject": "SpecificEntityName",
    "predicate": "specific_relationship",
    "object": { "type": "Text|Number|Boolean|Reference", "value": "concrete_value" },
    "confidence": 0.0-1.0,
    "claim_type": "direct_assertion|cited_claim|definition|measurement",
    "extraction_rationale": "Why this claim was extracted (cite specific text)",
    "entity_aliases": ["other", "names"],
    "source_span": { "start": 0, "end": 10, "text": "exact quote" }
  }],
  "source": { "source_class": "SOURCE_CLASS" },
  "meta": {
    "total_claims": N,
    "unique_subjects": M,
    "extraction_notes": "Note if text was mostly hypothetical/illustrative"
  }
}

## TEXT TO ANALYZE:
Source class: SOURCE_CLASS

INPUT_TEXT

Return ONLY valid JSON. If text is entirely hypothetical/illustrative, return empty claims array with extraction_notes explaining why.`;

// ============================================================================
// Claude CLI Extraction
// ============================================================================

function callClaude(
  text: string,
  sourceClass: SourceClass,
  context?: DocumentContext
): ExtractionOutput {
  // Build the prompt with context
  const prompt = EXTRACTION_PROMPT
    .replace(/SOURCE_CLASS/g, sourceClass)
    .replace("DOCUMENT_TITLE", context?.documentTitle || "(not provided)")
    .replace("SECTION_TITLE", context?.sectionTitle || "(not provided)")
    .replace("DOCUMENT_TYPE", context?.documentType || "(not provided)")
    .replace("INPUT_TEXT", text);

  // Call claude CLI with -p (print mode)
  const result = execSync(
    `claude -p --output-format json --allowedTools "" --model sonnet`,
    {
      input: prompt,
      encoding: "utf-8",
      maxBuffer: 10 * 1024 * 1024, // 10MB buffer
    }
  );

  // Parse the response
  let jsonStr = result.trim();

  // The output might be wrapped in a JSON response object
  try {
    const wrapped = JSON.parse(jsonStr);
    if (wrapped.result) {
      jsonStr = wrapped.result;
    } else if (typeof wrapped === "string") {
      jsonStr = wrapped;
    }
  } catch {
    // Not wrapped, continue with raw output
  }

  // Handle potential markdown code blocks in the response
  if (jsonStr.startsWith("```")) {
    const match = jsonStr.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
    if (match) {
      jsonStr = match[1];
    }
  }

  const output: ExtractionOutput = JSON.parse(jsonStr);
  output.source.source_class = sourceClass;

  return output;
}

// ============================================================================
// API Route Handler
// ============================================================================

export async function POST(request: NextRequest) {
  try {
    const body: ExtractRequest = await request.json();

    if (!body.text || typeof body.text !== "string") {
      return NextResponse.json(
        { error: "Missing or invalid 'text' field" },
        { status: 400 }
      );
    }

    if (!body.sourceClass) {
      return NextResponse.json(
        { error: "Missing 'sourceClass' field" },
        { status: 400 }
      );
    }

    const validSourceClasses: SourceClass[] = [
      "Regulatory",
      "Clinical",
      "Observational",
      "Expert",
      "Community",
      "Anecdotal",
    ];

    if (!validSourceClasses.includes(body.sourceClass)) {
      return NextResponse.json(
        { error: `Invalid sourceClass. Must be one of: ${validSourceClasses.join(", ")}` },
        { status: 400 }
      );
    }

    // Run extraction
    const extraction = callClaude(body.text, body.sourceClass, body.context);

    return NextResponse.json(extraction);
  } catch (error) {
    console.error("Extraction error:", error);
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Extraction failed" },
      { status: 500 }
    );
  }
}
