import { searchTerritoryHref } from './search-territory.ts'; const geography = [ 'province_id', 'district_id', 'admin_post_id', 'locality_id', ]; const catalogueKeys: Record = { '/carteira': ['q', 'kind', 'sector', 'geo_location_id'], '/zonas': [ 'q', 'category', 'physical_category', 'legal_regime', 'development_status', 'availability_status', 'province', ], '/oportunidades': [ 'q', 'query', 'sector', 'province', 'district', 'geo_location_id', 'include_national', 'cluster_id', 'zee_id', 'zfi_id', 'park_id', 'corridor_id', 'project_id', 'education', 'min_amount', 'max_amount', 'currency', 'maturity_level', 'preparation', 'before', 'sort', 'facets[]', 'view', 'dimension', 'association_type', 'association_target', 'association_state', 'association_purpose', 'chain_id', 'subsector_id', ], }; function family(path: string): string { if (path.startsWith('/management/portfolio')) return '/carteira'; if (path.startsWith('/management/investment/zones')) return '/zonas'; if (path === '/educacao' || path.startsWith('/educacao/')) return '/oportunidades'; return ( Object.keys(catalogueKeys).find( (route) => path === route || path.startsWith(route + '/'), ) ?? '' ); } /** Explicit URL criteria only. No ambient storage, dossier IDs or nested return URLs. */ export function catalogueQuery(path: string, query: string): URLSearchParams { const group = family(path); const allowed = new Set( group ? [...geography, ...catalogueKeys[group], 'page', 'per_page'] : [], ); const result = new URLSearchParams(); for (const [key, value] of new URLSearchParams(query)) { if (!allowed.has(key) || !value || value.length > 2000) continue; if ( ['page', 'per_page'].includes(key) && (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value))) ) continue; if (!key.endsWith('[]')) result.delete(key); result.append(key, value); } return result; } /** Only a query string is carried; the return destination is always local. */ export function catalogueDetailHref( path: string, query: string, pageUrl?: string, ): string { const target = new URL(path, 'https://portal.invalid'); if (target.origin !== 'https://portal.invalid') return path; if (pageUrl) { const page = new URL( pageUrl, 'https://portal.invalid', ).searchParams.get('page'); if ( page && /^[1-9]\d*$/.test(page) && Number.isSafeInteger(Number(page)) ) { const params = new URLSearchParams(query); params.set('page', page); query = params.toString(); } } const selected = catalogueQuery(target.pathname, query).toString(); if (selected) target.searchParams.set('catalogue_query', selected); return target.pathname + target.search + target.hash; } export function catalogueReturnHref(base: string, pageUrl: string): string { const source = new URL(pageUrl, 'https://portal.invalid'); const target = new URL(base, 'https://portal.invalid'); if (target.origin !== 'https://portal.invalid') return base; const query = source.searchParams.get('catalogue_query'); const selected = catalogueQuery(target.pathname, query ?? ''); let changedAncestor = false; for (const key of geography) { if (target.searchParams.has(key)) changedAncestor ||= target.searchParams.get(key) !== selected.get(key); else if (changedAncestor) selected.delete(key); } const explicit = new Set(target.searchParams.keys()); for (const [key, value] of selected) { // Text, kind, price and page belong to their original catalogue only. if ( family(source.pathname) !== family(target.pathname) && !geography.includes(key) ) continue; if (!explicit.has(key)) target.searchParams.append(key, value); } return searchTerritoryHref( target.pathname + target.search + target.hash, pageUrl, ); }