import { parseStreamingJson as nativeParseStreamingJson } from "@singularity-forge/native";
import {
	hasXmlParameterTags,
	hasYamlBulletLists,
	repairToolJsonWithReport,
} from "./repair-tool-json.js";

/**
 * Attempts to parse potentially incomplete JSON during streaming.
 * Always returns a valid object, even if the JSON is incomplete.
 *
 * Uses the native Rust streaming JSON parser for performance.
 * Falls back to YAML bullet-list repair when the native parser
 * returns an empty object from input that contains YAML-style
 * bullet lists copied from template formatting (#2660).
 *
 * @param partialJson The partial JSON string from streaming
 * @returns Parsed object or empty object if parsing fails
 */
export function parseStreamingJson<T = any>(
	partialJson: string | undefined,
): T {
	if (!partialJson || partialJson.trim() === "") {
		return {} as T;
	}
	if (looksLikeIncompleteObjectValue(partialJson)) {
		return {} as T;
	}

	// Fast path: try native streaming parser first
	const result = nativeParseStreamingJson<T>(partialJson);

	// XML parameter tags can be trapped inside otherwise valid JSON strings,
	// so run repair before trusting the native parse result.
	if (hasXmlParameterTags(partialJson)) {
		try {
			return JSON.parse(repairToolJsonWithReport(partialJson).output) as T;
		} catch {
			// Fall through to the native parser result on incomplete partials
		}
	}

	// If the native parser returned a non-empty result, use it.
	// Only attempt repair when the result is empty AND the input looks like a
	// complete malformed object or YAML-shaped map. This avoids inventing
	// values for ordinary incomplete streaming chunks.
	if (
		result &&
		typeof result === "object" &&
		Object.keys(result as object).length === 0 &&
		shouldAttemptRepair(partialJson)
	) {
		try {
			return JSON.parse(repairToolJsonWithReport(partialJson).output) as T;
		} catch {
			// Repair failed — return the empty object from native parser
		}
	}

	return result;
}

function looksLikeIncompleteObjectValue(input: string): boolean {
	const trimmed = input.trim();
	return trimmed.startsWith("{") && /:\s*$/.test(trimmed);
}

function shouldAttemptRepair(input: string): boolean {
	if (hasXmlParameterTags(input) || hasYamlBulletLists(input)) return true;

	const trimmed = input.trim();
	if (!trimmed) return false;
	if (
		(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
		(trimmed.startsWith("[") && trimmed.endsWith("]"))
	) {
		return true;
	}

	// Full YAML map/list tool arguments from weaker models. Require a newline
	// so normal prose with a colon does not get parsed as a scalar/map.
	return (
		/^[A-Za-z_][A-Za-z0-9_-]*\s*:/m.test(trimmed) && trimmed.includes("\n")
	);
}
