/**
 * Qsirch query builder.
 *
 * We build a Qsirch "q" string using documented operators like:
 * - path:"/Public"
 * - modified:"YYYY-MM-DD"
 * - modified:"YYYY-MM-DD..YYYY-MM-DD"
 * - comparison operators: modified:>=YYYY-MM-DD
 * - extension:"pdf"
 *
 * Note:
 * - Qsirch operator syntax must not include spaces between operator and value.
 *   (Example: name:"QNAP" is correct, name: QNAP is incorrect.)
 *
 * Security:
 * - We treat user input as plain search terms.
 * - We strip characters that could turn user input into Qsirch operators.
 */

/**
 * Normalize and sanitize user query so it cannot inject Qsirch operators.
 *
 * @param {string|null} raw
 * @returns {string|null}
 */
export function sanitizeUserQuery(raw) {
	if (typeof raw !== "string") return null;

	let s = raw.trim();
	if (!s) return null;

	// Prevent operator injection:
	// - ":" is used by Qsirch operators (path:, modified:, extension:, ...)
	// - quotes can shape operator values
	s = s.replace(/[:"]/g, " ");

	// Prevent the user query from interfering with our own OR chaining:
	// We only remove the standalone token "OR" (case-sensitive),
	// so normal words like "order" or German "oder" remain unaffected.
	s = s.replace(/\bOR\b/g, " ");

	// Normalize whitespace
	s = s.replace(/\s+/g, " ").trim();

	return s || null;
}

function normalizePathPrefix(prefix) {
	let p = String(prefix || "").trim();
	if (!p) return "/";

	// Ensure leading slash and no trailing slash (unless it's just "/").
	if (!p.startsWith("/")) p = `/${p}`;
	if (p.length > 1) p = p.replace(/\/+$/, "");

	return p;
}

function buildDateClause(dateField, from, to) {
	const f = String(dateField || "modified").trim();

	// Range is the most explicit and is documented by QNAP for date ranges.
	if (from && to) return `${f}:"${from}..${to}"`;

	// QNAP documents comparison operators for dates/sizes as well.
	// Example: modified:<2015 (year)
	// We use ISO date strings here for determinism.
	if (from) return `${f}:>=${from}`;
	if (to) return `${f}:<=${to}`;

	return null;
}

function buildBranchClause({ pathPrefix, branch }) {
	const prefix = normalizePathPrefix(pathPrefix);
	return `path:"${prefix}/${branch}"`;
}

function buildGlobalClause({ pathPrefix }) {
	const prefix = normalizePathPrefix(pathPrefix);
	return `path:"${prefix}"`;
}

/**
 * Build the Qsirch "q" string from normalized inputs.
 *
 * @param {{
 *   mode: "branch"|"multi"|"all",
 *   branches: string[]|null,
 *   q: string|null,
 *   from: string|null,
 *   to: string|null,
 *   dateField: "modified"|"created",
 *   pathPrefix: string
 * }} input
 * @returns {string}
 */
export function buildQsirchQuery({
	mode,
	branches,
	q,
	from,
	to,
	dateField,
	pathPrefix,
}) {
	const userTerms = sanitizeUserQuery(q);
	const dateClause = buildDateClause(dateField, from, to);
	const extClause = `extension:"pdf"`;

	// Base terms that should always apply within a clause.
	// Order does not matter for AND semantics, but keeping a stable ordering
	// makes debugging and tests easier.
	function assembleClause(pathClause) {
		const parts = [];
		if (userTerms) parts.push(userTerms);
		parts.push(pathClause);
		parts.push(extClause);
		if (dateClause) parts.push(dateClause);
		return parts.join(" ");
	}

	if (mode === "all") {
		return assembleClause(buildGlobalClause({ pathPrefix }));
	}

	if (mode === "branch") {
		const b = branches?.[0];
		return assembleClause(buildBranchClause({ pathPrefix, branch: b }));
	}

	// mode === "multi"
	// We replicate the full clause per branch and connect with OR.
	// This avoids precedence issues where a shared "extension:" or "modified:"
	// might only apply to the last OR segment.
	const clauses = (branches || []).map((b) =>
		assembleClause(buildBranchClause({ pathPrefix, branch: b }))
	);

	return clauses.join(" OR ");
}
