import { useCallback } from 'react' import { invoke } from '@tauri-apps/api/core' import { isTauri, addMockEntry, updateMockContent } from '../mock-tauri' import type { VaultEntry } from '../types' import type { FrontmatterValue } from '../components/Inspector' import { useTabManagement } from './useTabManagement' import { updateMockFrontmatter, deleteMockFrontmatterProperty } from './mockFrontmatterHelpers' interface NewEntryParams { path: string slug: string title: string type: string status: string | null } function buildNewEntry({ path, slug, title, type, status }: NewEntryParams): VaultEntry { const now = Math.floor(Date.now() / 1000) return { path, filename: `${slug}.md`, title, isA: type, aliases: [], belongsTo: [], relatedTo: [], status, owner: null, cadence: null, archived: false, trashed: false, trashedAt: null, modifiedAt: now, createdAt: now, fileSize: 0, snippet: '', relationships: {}, icon: null, color: null, order: null, } } function slugify(text: string): string { return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '') } function entryMatchesTarget(e: VaultEntry, targetLower: string, targetAsWords: string): boolean { if (e.title.toLowerCase() === targetLower) return true if (e.aliases.some((a) => a.toLowerCase() === targetLower)) return true const pathStem = e.path.replace(/^.*\/Laputa\//, '').replace(/\.md$/, '') if (pathStem.toLowerCase() === targetLower) return true const fileStem = e.filename.replace(/\.md$/, '') if (fileStem.toLowerCase() === targetLower.split('/').pop()) return true return e.title.toLowerCase() === targetAsWords } async function invokeFrontmatter(command: string, args: Record): Promise { return invoke(command, args) } function applyMockFrontmatterUpdate(path: string, key: string, value: FrontmatterValue): string { const content = updateMockFrontmatter(path, key, value) updateMockContent(path, content) return content } function applyMockFrontmatterDelete(path: string, key: string): string { const content = deleteMockFrontmatterProperty(path, key) updateMockContent(path, content) return content } const TYPE_FOLDER_MAP: Record = { Note: 'note', Project: 'project', Experiment: 'experiment', Responsibility: 'responsibility', Procedure: 'procedure', Person: 'person', Event: 'event', Topic: 'topic', } const NO_STATUS_TYPES = new Set(['Topic', 'Person']) function addEntryWithMock(entry: VaultEntry, content: string, addEntry: (e: VaultEntry, c: string) => void) { if (!isTauri()) addMockEntry(entry, content) addEntry(entry, content) } function buildNoteContent(title: string, type: string, status: string | null): string { const lines = ['---', `title: ${title}`, `is_a: ${type}`] if (status) lines.push(`status: ${status}`) lines.push('---') return `${lines.join('\n')}\n\n# ${title}\n\n` } function resolveNewNote(title: string, type: string): { entry: VaultEntry; content: string } { const folder = TYPE_FOLDER_MAP[type] || slugify(type) const slug = slugify(title) const status = NO_STATUS_TYPES.has(type) ? null : 'Active' const entry = buildNewEntry({ path: `/Users/luca/Laputa/${folder}/${slug}.md`, slug, title, type, status }) return { entry, content: buildNoteContent(title, type, status) } } function resolveNewType(typeName: string): { entry: VaultEntry; content: string } { const slug = slugify(typeName) const entry = buildNewEntry({ path: `/Users/luca/Laputa/type/${slug}.md`, slug, title: typeName, type: 'Type', status: null }) return { entry, content: `---\nIs A: Type\n---\n\n# ${typeName}\n\n` } } async function executeFrontmatterOp(op: 'update' | 'delete', path: string, key: string, value?: FrontmatterValue): Promise { if (op === 'update') { return isTauri() ? invokeFrontmatter('update_frontmatter', { path, key, value }) : applyMockFrontmatterUpdate(path, key, value!) } return isTauri() ? invokeFrontmatter('delete_frontmatter_property', { path, key }) : applyMockFrontmatterDelete(path, key) } export function useNoteActions( addEntry: (entry: VaultEntry, content: string) => void, updateContent: (path: string, content: string) => void, entries: VaultEntry[], setToastMessage: (msg: string | null) => void, ) { const tabMgmt = useTabManagement() const { setTabs, handleSelectNote } = tabMgmt const updateTabContent = useCallback((path: string, newContent: string) => { setTabs((prev) => prev.map((t) => t.entry.path === path ? { ...t, content: newContent } : t)) updateContent(path, newContent) }, [setTabs, updateContent]) const handleNavigateWikilink = useCallback((target: string) => { const targetLower = target.toLowerCase() const targetAsWords = target.split('/').pop()?.replace(/-/g, ' ').toLowerCase() ?? targetLower const found = entries.find((e) => entryMatchesTarget(e, targetLower, targetAsWords)) if (found) handleSelectNote(found) else console.warn(`Navigation target not found: ${target}`) }, [entries, handleSelectNote]) const handleCreateNote = useCallback((title: string, type: string) => { const { entry, content } = resolveNewNote(title, type) addEntryWithMock(entry, content, addEntry) handleSelectNote(entry) }, [handleSelectNote, addEntry]) const handleCreateType = useCallback((typeName: string) => { const { entry, content } = resolveNewType(typeName) addEntryWithMock(entry, content, addEntry) handleSelectNote(entry) }, [handleSelectNote, addEntry]) const runFrontmatterOp = useCallback(async (op: 'update' | 'delete', path: string, key: string, value?: FrontmatterValue) => { try { updateTabContent(path, await executeFrontmatterOp(op, path, key, value)) setToastMessage(op === 'update' ? 'Property updated' : 'Property deleted') } catch (err) { console.error(`Failed to ${op} frontmatter:`, err) setToastMessage(`Failed to ${op} property`) } }, [updateTabContent, setToastMessage]) return { ...tabMgmt, handleNavigateWikilink, handleCreateNote, handleCreateType, handleUpdateFrontmatter: useCallback((path: string, key: string, value: FrontmatterValue) => runFrontmatterOp('update', path, key, value), [runFrontmatterOp]), handleDeleteProperty: useCallback((path: string, key: string) => runFrontmatterOp('delete', path, key), [runFrontmatterOp]), handleAddProperty: useCallback((path: string, key: string, value: FrontmatterValue) => runFrontmatterOp('update', path, key, value), [runFrontmatterOp]), } }