import type { FilterCondition } from '../types' import { evaluatePropertyArrayCondition } from './viewFilterArrayProperties' export type ViewFilterArrayKind = 'property' | 'relationship' type ConditionText = string type RelationshipValue = string type RelationshipArrayOperator = (field: RelationshipArrayField, value: ConditionText, cond: FilterCondition) => boolean interface ArrayFieldCondition { cond: FilterCondition values: RelationshipValue[] arrayKind: ViewFilterArrayKind condVal: ConditionText regex: RegExp | null } function toStringValue(value: unknown): ConditionText { if (value == null) return '' if (typeof value === 'string') return value return String(value) } function conditionList(value: unknown): ConditionText[] | null { return Array.isArray(value) ? value.map(toStringValue) : null } class WikilinkValue { private readonly trimmed: RelationshipValue constructor(raw: RelationshipValue) { this.trimmed = raw.trim() } get isBracketed(): boolean { return this.trimmed.startsWith('[[') } get normalizedStem(): RelationshipValue { return this.stem.toLowerCase() } get candidates(): ConditionText[] { const pipe = this.inner.indexOf('|') if (pipe >= 0) return [this.trimmed, this.inner.slice(0, pipe), this.inner.slice(pipe + 1)] return [this.trimmed, this.inner] } includesStem(target: WikilinkValue): boolean { return this.normalizedStem.includes(target.normalizedStem) } equals(target: WikilinkValue): boolean { const targetParts = target.parts return this.parts.some((part) => targetParts.some((targetPart) => part === targetPart)) } private get parts(): ConditionText[] { const pipe = this.inner.indexOf('|') if (pipe >= 0) return [this.inner.substring(0, pipe).toLowerCase(), this.inner.substring(pipe + 1).toLowerCase()] return [this.inner.toLowerCase()] } private get stem(): RelationshipValue { return this.inner.split('|')[0] ?? this.inner } private get inner(): RelationshipValue { return this.trimmed.replace(/^\[\[/, '').replace(/\]\]$/, '') } } class RelationshipArrayField { private readonly links: WikilinkValue[] constructor(values: RelationshipValue[]) { this.links = values.map((value) => new WikilinkValue(value)) } contains(targetValue: ConditionText): boolean { const target = new WikilinkValue(targetValue) return this.links.some((link) => target.isBracketed ? link.equals(target) : link.includesStem(target)) } equals(targetValue: ConditionText): boolean { return this.links.length === 1 && this.links[0]?.equals(new WikilinkValue(targetValue)) === true } matchesAny(targets: ConditionText[] | null): boolean { return targets?.some((target) => this.links.some((link) => link.equals(new WikilinkValue(target)))) ?? false } matchesRegex(regex: RegExp): boolean { return this.links.some((link) => link.candidates.some((candidate) => regex.test(candidate))) } isEmpty(): boolean { return this.links.length === 0 } } const RELATIONSHIP_ARRAY_OPERATORS: Partial> = { contains: (field, value) => field.contains(value), not_contains: (field, value) => !field.contains(value), equals: (field, value) => field.equals(value), not_equals: (field, value) => !field.equals(value), any_of: (field, _value, cond) => field.matchesAny(conditionList(cond.value)), none_of: (field, _value, cond) => !field.matchesAny(conditionList(cond.value)), is_empty: (field) => field.isEmpty(), is_not_empty: (field) => !field.isEmpty(), } function textMatchResult(op: FilterCondition['op'], matched: boolean): boolean { if (op === 'contains' || op === 'equals') return matched if (op === 'not_contains' || op === 'not_equals') return !matched return false } function evaluateRelationshipArrayCondition(cond: FilterCondition, values: RelationshipValue[], condVal: ConditionText, regex: RegExp | null): boolean { const { op } = cond const field = new RelationshipArrayField(values) if (regex) return textMatchResult(op, field.matchesRegex(regex)) return RELATIONSHIP_ARRAY_OPERATORS[op]?.(field, condVal, cond) ?? false } export function evaluateArrayFieldCondition({ cond, values, arrayKind, condVal, regex }: ArrayFieldCondition): boolean { if (arrayKind === 'property') return evaluatePropertyArrayCondition(cond, values, condVal, regex) return evaluateRelationshipArrayCondition(cond, values, condVal, regex) }