import type { DriveFileInfo } from "@/lib/api/types" import { parentFolderPath } from "@/lib/drive/drive-search" import { normalizeDriveFolderPath } from "@/lib/drive/drive-sidebar-tree" import type { DriveFiltersSnapshot } from "@/lib/stores/drive-filters-store" import { driveFiltersActive } from "@/lib/stores/drive-filters-store" export type DriveMimeCategory = | "folder" | "document" | "spreadsheet" | "presentation" | "image" | "pdf" | "video" | "audio" | "archive" | "other" export type DriveSourceId = "ultimail" | "ultimeet" const ARCHIVE_EXT = new Set(["zip", "rar", "7z", "tar", "gz", "bz2"]) const AUDIO_EXT = new Set(["mp3", "wav", "ogg", "flac", "m4a", "aac"]) const IMAGE_EXT = new Set(["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp", "avif", "heic"]) const VIDEO_EXT = new Set(["mp4", "webm", "mov", "mkv", "ogv", "m4v"]) function ext(name: string) { const i = name.lastIndexOf(".") return i > 0 ? name.slice(i + 1).toLowerCase() : "" } export function classifyDriveFile(file: DriveFileInfo): DriveMimeCategory { if (file.type === "directory") return "folder" const mime = (file.mime_type ?? "").toLowerCase() const e = ext(file.name) if (mime.includes("pdf") || e === "pdf") return "pdf" if (mime.startsWith("image/") || IMAGE_EXT.has(e)) return "image" if (mime.startsWith("video/") || VIDEO_EXT.has(e)) return "video" if (mime.startsWith("audio/") || AUDIO_EXT.has(e)) return "audio" if ( mime.includes("zip") || mime.includes("archive") || ARCHIVE_EXT.has(e) ) { return "archive" } if ( mime.includes("spreadsheet") || mime.includes("excel") || ["xls", "xlsx", "ods", "csv"].includes(e) ) { return "spreadsheet" } if ( mime.includes("presentation") || mime.includes("powerpoint") || ["ppt", "pptx", "odp"].includes(e) ) { return "presentation" } if ( mime.includes("word") || mime.includes("document") || mime.includes("opendocument.text") || ["doc", "docx", "odt", "rtf", "txt"].includes(e) ) { return "document" } return "other" } export function matchesDriveSource(file: DriveFileInfo, source: DriveSourceId): boolean { if (file.source === source) return true const p = file.path.toLowerCase() const n = file.name.toLowerCase() if (source === "ultimail") { return ( p.includes("/ultimail") || p.includes("/mail/") || n.includes("ultimail") || (file.mime_type ?? "").includes("rfc822") ) } return ( file.source === "ultimeet" || p.includes("/ultimeet") || p.includes("/meet") || p.includes("/recordings") || n.includes("ultimeet") || n.includes("meet-") ) } function parseModified(iso: string): Date | null { const d = new Date(iso) return Number.isNaN(d.getTime()) ? null : d } function inDateRange(file: DriveFileInfo, filters: DriveFiltersSnapshot): boolean { const { datePreset, dateFrom, dateTo } = filters if (!datePreset) return true const modified = parseModified(file.last_modified) if (!modified) return true const now = new Date() const startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate()) if (datePreset === "today") { const today = startOfDay(now) return modified >= today } if (datePreset === "last7") { const from = new Date(now) from.setDate(from.getDate() - 7) return modified >= from } if (datePreset === "last30") { const from = new Date(now) from.setDate(from.getDate() - 30) return modified >= from } if (datePreset === "thisYear") { return modified.getFullYear() === now.getFullYear() } if (datePreset === "lastYear") { return modified.getFullYear() === now.getFullYear() - 1 } if (datePreset === "custom" && dateFrom) { const from = startOfDay(new Date(dateFrom)) const to = dateTo ? new Date(dateTo) : now return modified >= from && modified <= to } return true } export function matchesDriveFilters( file: DriveFileInfo, filters: DriveFiltersSnapshot ): boolean { if (filters.types.size > 0) { const cat = classifyDriveFile(file) if (!filters.types.has(cat)) return false } if (filters.sources.size > 0) { const ok = [...filters.sources].some((s) => matchesDriveSource(file, s)) if (!ok) return false } if (!matchesContact(file, filters)) return false if (!inDateRange(file, filters)) return false return true } function isPathUnderScope(path: string, scopePath: string): boolean { const normalized = normalizeDriveFolderPath(path) const scope = normalizeDriveFolderPath(scopePath) if (scope === "/") return true return normalized === scope || normalized.startsWith(`${scope}/`) } /** Folder paths that contain (or nest) at least one file matching filters. */ export function buildDriveFolderPathsWithMatches( corpus: DriveFileInfo[], filters: DriveFiltersSnapshot, scopePath = "/" ): Set { const keep = new Set() const scope = normalizeDriveFolderPath(scopePath) for (const file of corpus) { if (file.type === "directory") continue if (!matchesDriveFilters(file, filters)) continue let dir = parentFolderPath(file.path) while (dir !== "/" && isPathUnderScope(dir, scope)) { const normalized = normalizeDriveFolderPath(dir) keep.add(normalized) if (normalized === scope) break dir = parentFolderPath(normalized) } } return keep } function matchesContact(file: DriveFileInfo, filters: DriveFiltersSnapshot): boolean { if (!filters.contactEmail && !filters.contactName) return true const hay = `${file.path} ${file.name}`.toLowerCase() if (filters.contactEmail && hay.includes(filters.contactEmail.toLowerCase())) { return true } if (filters.contactName) { const parts = filters.contactName.toLowerCase().split(/\s+/).filter(Boolean) if (parts.length > 0 && parts.every((p) => hay.includes(p))) return true } return false } export function applyDriveFilters( items: DriveFileInfo[], filters: DriveFiltersSnapshot, options?: { /** When set, only folders in this set stay visible (plus direct filter matches). */ folderKeepPaths?: Set /** Corpus for folder pruning when folderKeepPaths is omitted. */ matchCorpus?: DriveFileInfo[] /** Limit folder pruning to descendants of this path (default `/`). */ scopePath?: string } ): DriveFileInfo[] { if (!driveFiltersActive(filters)) return items const folderKeepPaths = options?.folderKeepPaths ?? (options?.matchCorpus ? buildDriveFolderPathsWithMatches( options.matchCorpus, filters, options.scopePath ?? "/" ) : undefined) return items.filter((file) => { if (file.type === "directory") { const path = normalizeDriveFolderPath(file.path) if (folderKeepPaths?.has(path)) return true return matchesDriveFilters(file, filters) } return matchesDriveFilters(file, filters) }) } export { driveFiltersActive as hasActiveDriveFilters }