/**
 * Filter state for the timeline. One source of truth; routes read from it,
 * the left sidebar writes to it, and `filtersToQ()` derives the search
 * string PhotoPrism's `q` parameter accepts.
 *
 * Sections behave like saved searches: picking a section sets a default
 * filter shape (favorites → `favorite:true`, archive → `archived:true`,
 * etc.), and the search box on top stacks an additional `q` term.
 */

export type Section =
	| 'all-photos'
	| 'favorites'
	| 'review'
	| 'archive'
	| 'hidden'
	| 'heap';

export interface FilterState {
	section: Section;
	/** Heap UID, used when section === 'heap'. */
	heapUid: string | null;
	/** Relative folder path under originals/. Stacks with section terms. */
	folderPath: string | null;
	/** Free-form search text, ANDed with section-derived terms. */
	search: string;
}

// Default landing = root folder (`/`). The Folders group sits at the top
// of the sidebar; landing inside it gives users a stable starting point
// instead of dumping them into the full library. Picking any other view
// (favorites, a heap, "All photos") clears `folderPath` to `null`.
export const filters = $state<FilterState>({
	section: 'all-photos',
	heapUid: null,
	folderPath: '/',
	search: ''
});

export function setSection(section: Section, heapUid: string | null = null): void {
	filters.section = section;
	filters.heapUid = section === 'heap' ? heapUid : null;
}

export function setSearch(q: string): void {
	filters.search = q;
}

export function setFolderPath(path: string | null): void {
	filters.folderPath = path;
}

/**
 * Quote a DSL term value when it contains characters that PhotoPrism's
 * parser treats as boundaries (spaces, colons). We surround in double
 * quotes; users can still type a raw `q=` for advanced search.
 */
function quoteIfNeeded(v: string): string {
	if (!v) return '';
	if (/^[A-Za-z0-9_\-./]+$/.test(v)) return v;
	return `"${v.replace(/"/g, '\\"')}"`;
}

/**
 * Build the PhotoPrism `q=` DSL string from the current filter state.
 * Returns "" when nothing's restricting (the timeline default).
 */
export function filtersToQ(f: FilterState = filters): string {
	const parts: string[] = [];
	switch (f.section) {
		case 'favorites':
			parts.push('favorite:true');
			break;
		case 'review':
			// PhotoPrism's review pile: photos the indexer flagged as
			// uncertain (low quality score). Cleared per-photo via the
			// `/approve` endpoint or by archiving.
			parts.push('review:true');
			break;
		case 'archive':
			parts.push('archived:true');
			break;
		case 'hidden':
			// Auto-hidden by the indexer (broken files, very low quality
			// score). Excluded from every other view — this section is the
			// only way to see them without a manual `q=hidden:true`.
			parts.push('hidden:true');
			break;
		case 'heap':
			if (f.heapUid) parts.push(`album:${f.heapUid}`);
			break;
		case 'all-photos':
		default:
			break;
	}
	// `/` is the root-folder sentinel. PhotoPrism's `path:` operator can't
	// express "exact root match" (path:"" / path:/ both fall back to "no
	// filter"), so we leave the server query unfiltered and let the
	// timeline post-filter to `Path === ''` client-side.
	//
	// For any non-root folder we want EVERY photo under that subtree, not
	// just photos whose `photo_path` is an exact match. PhotoPrism's
	// `path:` operator is exact-by-default but supports a trailing `*`
	// wildcard:
	//   path:"2024"     →  matches only photos directly at `2024/` (none,
	//                       if all files live in date-stamped sub-folders)
	//   path:"2024*"    →  matches `2024`, `2024/01`, `2024/02/...`, etc.
	//   path:"2024/01*" →  matches `2024/01` plus descendants — still
	//                       correct for a leaf folder.
	// Always append `*` so internal tree nodes return the union of all
	// descendant photos and leaves keep returning their direct contents.
	if (f.folderPath && f.folderPath !== '/') {
		parts.push(`path:${quoteIfNeeded(f.folderPath + '*')}`);
	}
	if (f.search) parts.push(quoteIfNeeded(f.search));
	return parts.join(' ');
}

/** Inverse of filtersToQ for URL hydration. Returns the parsed filter state. */
export function parseUrlParams(params: URLSearchParams): Partial<FilterState> {
	const sectionRaw = params.get('section') as Section | null;
	const section: Section =
		sectionRaw && ['all-photos', 'favorites', 'review', 'archive', 'hidden', 'heap'].includes(sectionRaw)
			? sectionRaw
			: 'all-photos';
	// Bare URL (no section/folder/heap/q params) lands on the root folder
	// — same default the store carries. Any explicit param means the user
	// asked for a specific view, so the folder filter clears unless
	// `folder=` is supplied on top.
	const bare =
		!params.has('section') &&
		!params.has('folder') &&
		!params.has('heap') &&
		!params.has('q');
	const folderRaw = params.get('folder');
	const folderPath = folderRaw !== null ? folderRaw : bare ? '/' : null;
	return {
		section,
		heapUid: params.get('heap'),
		folderPath,
		search: params.get('q') ?? ''
	};
}

/** Serialise the current filter state to URL search params (only set keys
 *  that differ from defaults so the URL stays clean). */
export function filtersToUrlParams(f: FilterState = filters): URLSearchParams {
	const params = new URLSearchParams();
	if (f.section !== 'all-photos') params.set('section', f.section);
	if (f.heapUid) params.set('heap', f.heapUid);
	if (f.folderPath) params.set('folder', f.folderPath);
	if (f.search) params.set('q', f.search);
	return params;
}
