import { createFileRoute } from '@tanstack/react-router'
import { FolderPlus, Pencil, RefreshCw, Sparkles, Trash2, Wrench } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { CreateMountDialog } from '@/components/mount/create-mount-dialog'
import { EditMountDialog } from '@/components/mount/edit-mount-dialog'
import { EmptyState } from '@/components/shared/empty-state'
import { ErrorState } from '@/components/shared/error-state'
import { LoadingCards } from '@/components/shared/loading'
import { RefreshButton } from '@/components/shared/refresh-button'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { useDeleteMount, useMounts, useRescanMount } from '@/hooks/use-mounts'
import type { Mount } from '@/types'
export const Route = createFileRoute('/mounts/')({ component: MountsPage })
function MountsPage() {
const { t } = useTranslation()
const [search, setSearch] = useState('')
const [showCreate, setShowCreate] = useState(false)
const [editingMount, setEditingMount] = useState<Mount | null>(null)
const [deleteTarget, setDeleteTarget] = useState<Mount | null>(null)
const { data, isLoading, isError, refetch } = useMounts(search || undefined)
const deleteMount = useDeleteMount()
const rescanMount = useRescanMount()
const mounts = Array.isArray(data?.items) ? data.items : []
const handleDelete = async (mount: Mount) => {
try {
await deleteMount.mutateAsync(mount.id)
toast.success(t('mounts.deleted'))
} catch (err) {
toast.error(err instanceof Error ? err.message : t('mounts.deleteFailed'))
}
}
const handleRescan = async (mount: Mount) => {
try {
await rescanMount.mutateAsync(mount.id)
toast.success(t('mounts.rescanned'))
} catch (err) {
toast.error(err instanceof Error ? err.message : t('mounts.rescanFailed'))
}
}
return (
<div className="space-y-4">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">{t('mounts.title')}</h1>
<p className="text-muted-foreground text-sm">{t('mounts.desc')}</p>
</div>
<Button onClick={() => setShowCreate(true)}>
<FolderPlus className="h-4 w-4 mr-2" />
{t('mounts.create')}
</Button>
</div>
{/* Search */}
<div className="flex items-center gap-2">
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder={t('mounts.searchPlaceholder')}
className="max-w-xs"
/>
<RefreshButton onClick={() => refetch()} />
</div>
{/* Content */}
{isLoading ? (
<LoadingCards />
) : isError ? (
<ErrorState onRetry={() => refetch()} />
) : mounts.length === 0 ? (
<EmptyState
icon={<FolderPlus className="h-8 w-8" />}
title={t('mounts.empty')}
description={t('mounts.emptyDesc')}
action={
<Button onClick={() => setShowCreate(true)}>
<FolderPlus className="h-4 w-4 mr-2" />
{t('mounts.create')}
</Button>
}
/>
) : (
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{mounts.map((mount) => (
<div
key={mount.id}
className="group relative rounded-lg border bg-card p-4 transition-all hover:shadow-sm"
>
{/* Action buttons: rescan + delete (top-right) */}
<div className="absolute right-2 top-2 flex items-center gap-0.5">
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={() => setEditingMount(mount)}
aria-label={t('common.edit')}
>
<Pencil className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={() => handleRescan(mount)}
aria-label={t('mounts.rescan')}
>
<RefreshCw className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-destructive hover:text-destructive"
onClick={() => setDeleteTarget(mount)}
aria-label={t('common.delete')}
>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</div>
{/* Name */}
<div className="mb-2 flex items-center gap-2">
{mount.source === 'auto_promoted' ? (
<Sparkles className="h-4 w-4 text-violet-500" />
) : (
<Wrench className="h-4 w-4" />
)}
<h3 className="font-semibold truncate">{mount.name}</h3>
{mount.source === 'auto_promoted' && (
<span className="rounded-full bg-violet-500/10 px-2 py-0.5 text-xs text-violet-600">
{t('mounts.autoPromoted')}
</span>
)}
{mount.enrichment_pending && (
<button
type="button"
onClick={() => handleRescan(mount)}
className="rounded-full bg-amber-500/10 px-2 py-0.5 text-xs text-amber-600 hover:bg-amber-500/20 transition-colors"
title={t('mounts.rescan')}
>
{t('mounts.needsRefresh')} ↻
</button>
)}
</div>
{/* Path */}
<p className="mb-2 text-xs text-muted-foreground truncate font-mono">
{mount.paths[0] ?? '(no path)'}
</p>
{/* Auto-description */}
{mount.auto_description && (
<p className="mb-2 text-sm text-muted-foreground line-clamp-2">
{mount.auto_description}
</p>
)}
{/* Languages + stack */}
<div className="flex flex-wrap gap-1">
{mount.auto_meta.languages.map((lang) => (
<span
key={lang}
className="rounded bg-primary/10 px-1.5 py-0.5 text-xs text-primary"
>
{lang}
</span>
))}
{mount.auto_meta.stack.slice(0, 4).map((s) => (
<span
key={s}
className="rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground"
>
{s}
</span>
))}
</div>
</div>
))}
</div>
)}
{/* Create dialog */}
<CreateMountDialog open={showCreate} onOpenChange={setShowCreate} />
{/* Edit dialog */}
<EditMountDialog mount={editingMount} onOpenChange={setEditingMount} />
{/* Delete confirm */}
<Dialog open={deleteTarget !== null} onOpenChange={(open) => !open && setDeleteTarget(null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('mounts.deleteConfirmTitle')}</DialogTitle>
<DialogDescription>{t('mounts.deleteConfirmDescription')}</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="ghost"
size="sm"
onClick={() => setDeleteTarget(null)}
disabled={deleteMount.isPending}
>
{t('common.cancel')}
</Button>
<Button
size="sm"
variant="destructive"
onClick={() => {
if (!deleteTarget) return
handleDelete(deleteTarget)
setDeleteTarget(null)
}}
disabled={deleteMount.isPending}
>
{t('common.delete')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}