import { SEARCH_SCOPE } from "@/lib/frontend/search/urlState"; function isNonEmptyString(value) { return typeof value === "string" && value.trim().length > 0; } /** * Build the apiClient.search(...) input from URL state + current user context. * * Why this exists: * - Search UI state is URL-driven and shareable. * - Cursor is intentionally kept out of the URL by default (client state only). * - Role/scoping rules must be enforced consistently (branch users are always single-branch). * * Return shape: * - input: object for apiClient.search(...) or null (no search yet / not ready) * - error: ApiClientError or null (local validation / fast-fail) * * UX policy for MULTI without branches: * - Treat it as "not ready" (input=null, error=null) instead of an error. * The UI should show a friendly hint (select at least one branch). * * @param {{ * urlState: { * q: string|null, * scope: "single"|"multi"|"all", * branch: string|null, * branches: string[], * from: string|null, * to: string|null * }, * routeBranch: string, * user: { role: string, branchId: string|null }|null, * cursor?: string|null, * limit?: number * }} args * @returns {{ input: any|null, error: any|null }} */ export function buildSearchApiInput({ urlState, routeBranch, user, cursor = null, limit = 100, }) { const q = isNonEmptyString(urlState?.q) ? urlState.q.trim() : null; // No query => no search request. UI should show an "idle" empty state. if (!q) return { input: null, error: null }; const input = { q, limit }; // Keep from/to as pass-through for RHL-025 (future). if (isNonEmptyString(urlState?.from)) input.from = urlState.from.trim(); if (isNonEmptyString(urlState?.to)) input.to = urlState.to.trim(); if (isNonEmptyString(cursor)) input.cursor = cursor.trim(); const role = user?.role; // Branch users: always restricted to the current route branch. if (role === "branch") { input.branch = routeBranch; return { input, error: null }; } // Admin/dev: respect scope rules from URL state. if (role === "admin" || role === "dev") { if (urlState.scope === SEARCH_SCOPE.ALL) { input.scope = "all"; return { input, error: null }; } if (urlState.scope === SEARCH_SCOPE.MULTI) { const branches = Array.isArray(urlState.branches) ? urlState.branches : []; // UX: missing branches is not an error, it's "not ready". if (branches.length === 0) { return { input: null, error: null }; } input.scope = "multi"; input.branches = branches; return { input, error: null }; } // SINGLE input.branch = routeBranch; return { input, error: null }; } // Unknown role: fail-safe to single-branch using the route context. input.branch = routeBranch; return { input, error: null }; }