import { useCallback, useEffect, useRef, useState } from 'react'
import { Check, Copy, KeyRound, Link2, ShieldCheck, Trash2, Users } from 'lucide-react'
import { drive, message } from '@/api/client'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import QuotaField from '@/components/drive/QuotaField'
import {fromQuota, toQuota} from '@/lib/quota-utils'
import {fmtSize} from '@/lib/drive-format'
import PermissionsEditor, {PRESETS} from '@/components/drive/PermissionsEditor'
export default function MembersPage({ controller, user, embedded = false }) {
const { spaces, activeSpaceId, loadSpaces, managedSpaceId, setManagedSpaceId, setSpaceDialog } = controller
const [parentId, setParentId] = useState(managedSpaceId || user.space_id)
const [record, setRecord] = useState(null)
const reloadSequence = useRef(0)
const [rights, setRights] = useState(PRESETS.reader)
const [url, setUrl] = useState('')
const [title, setTitle] = useState('')
const [busy, setBusy] = useState(false)
const [error, setError] = useState('')
const [info, setInfo] = useState('')
const [expires, setExpires] = useState('7')
const [rootQuota, setRootQuota] = useState('')
const [userQuery, setUserQuery] = useState('')
const [userResults, setUserResults] = useState([])
const [searching, setSearching] = useState(false)
const [selectedAccount, setSelectedAccount] = useState(null)
const [directRights, setDirectRights] = useState(PRESETS.reader)
const mine = spaces.find(s => s.id === user.space_id)
const managed = spaces.filter(s => !s.is_private && s.can_invite && (!embedded || s.id === managedSpaceId))
const selectedManagedSpace = managed.find(space => space.id === parentId)
// Never render stale information from a previously selected parent space.
const selectedRecord = record?.parent?.id === parentId ? record : null
// An account must never appear among its own directly added members.
const directGrants = (selectedRecord?.grants || []).filter(grant =>
Number(grant.user_id) !== Number(user.id) &&
Number(grant.user_id) !== Number(selectedRecord.parent.user_id)
)
const managedKey = managed.map(space => space.id).join(',')
useEffect(() => { if (managedSpaceId) setParentId(managedSpaceId) }, [managedSpaceId])
useEffect(() => {
if (managed.length && !managed.some(space => space.id === parentId)) setParentId(managed[0].id)
}, [managedKey, parentId])
const reload = useCallback(async () => {
const sequence = ++reloadSequence.current
if (!managedKey.split(',').includes(String(parentId))) {setRecord(null);return}
try {
const nextRecord = await drive.members(parentId)
if (sequence !== reloadSequence.current) return
// Responses for a previously selected space must never overwrite this one.
if (nextRecord?.parent?.id !== parentId) throw new Error('Réponse incohérente pour cet espace.')
setRecord(nextRecord)
setError('')
} catch (e) {
if (sequence === reloadSequence.current) setError(e?.response ? message(e) : e.message || message(e))
}
}, [parentId,managedKey])
useEffect(() => { reload() }, [reload])
useEffect(() => { setTitle(mine?.display_name || '') }, [mine?.display_name])
useEffect(() => { setRootQuota(fromQuota(mine?.quota_bytes)) }, [mine?.quota_bytes])
const create = async () => {
setBusy(true);setError('');setUrl('')
try {
const inv = await drive.invite({ permissions:rights, expires_days:Number(expires), parent_space:parentId })
setUrl(`${window.location.origin}/invite/${inv.token}`)
setInfo('Invitation créée ! Le lien n’est affiché qu’une seule fois : copie-le maintenant.')
await reload()
} catch(e) { setError(e?.response ? message(e) : (e.message || message(e))) } finally { setBusy(false) }
}
const change = async (id, data) => {
setBusy(true);setError('')
try {await drive.memberPatch(id,data);await reload();await loadSpaces();controller.reloadStats();setInfo('Paramètres de l’espace mis à jour.')}
catch(e){setError(e?.response ? message(e) : (e.message || message(e)))}finally{setBusy(false)}
}
const revoke = async id => {
setBusy(true)
try {await drive.revokeInvite(id);await reload();setInfo('Invitation révoquée.')}
catch(e){setError(message(e))}finally{setBusy(false)}
}
const saveRootQuota = async () => {
setBusy(true);setError('')
try {
await drive.spaceQuota(user.space_id, toQuota(rootQuota))
await loadSpaces(); await reload(); controller.reloadStats()
setInfo('Limite de stockage enregistrée.')
} catch(e) {setError(e?.response ? message(e) : (e.message || message(e)))}
finally {setBusy(false)}
}
useEffect(() => {
if (selectedAccount?.username === userQuery || userQuery.trim().length < 2) { setUserResults([]); return }
let alive = true
const timer = setTimeout(async () => {
setSearching(true)
try { const users = await drive.searchUsers(userQuery.trim(), parentId); if(alive) setUserResults(users) }
catch(e) { if(alive) setError(message(e)) }
finally { if(alive) setSearching(false) }
}, 300)
return () => { alive = false; clearTimeout(timer) }
}, [userQuery, parentId, selectedAccount?.username])
const addExisting = async () => {
if (!selectedAccount) return
setBusy(true); setError('')
try {
await drive.addUser(selectedAccount.id,parentId,directRights)
setInfo(`Accès accordé à ${selectedAccount.first_name} ${selectedAccount.last_name}`.trim())
setSelectedAccount(null);setUserQuery('');setUserResults([])
await reload();await loadSpaces()
} catch(e) { setError(message(e)) }
finally { setBusy(false) }
}
const updateGrant = async (grantId, data) => {
setBusy(true);setError('')
try { await drive.grantPatch(grantId,data);await reload();setInfo('Droits mis à jour.') }
catch(e){setError(message(e))}finally{setBusy(false)}
}
const revokeGrant = async grantId => {
setBusy(true);setError('')
try {await drive.grantRemove(grantId);await reload();setInfo('Accès révoqué.')}
catch(e){setError(message(e))}finally{setBusy(false)}
}
const manageSubspace = child => {
// Close the access dialog before opening the *child's* settings.
setManagedSpaceId(null)
setSpaceDialog({ mode: 'manage', spaceId: child.id })
}
const rename = async () => {
setBusy(true)
try {await drive.renameSpace(title);await loadSpaces();setInfo('Nom de ton espace enregistré.')}
catch(e){setError(message(e))}finally{setBusy(false)}
}
return Gestion de « {selectedManagedSpace.display_name} » Cherche son identifiant, vérifie son nom et prénom, puis autorise l'accès à cet espace. Ses fichiers personnels restent privés. Recherche en cours… Utilisateur sélectionné : {[selectedAccount.first_name,selectedAccount.last_name].filter(Boolean).join(' ')} (@{selectedAccount.username}) Aucun sous-utilisateur dans cet espace. La création de sous-utilisateurs n’est pas autorisée pour ton compte.Espaces & invitations
Nom de mon espace
Quota de mon espace racine
Utilisateurs ajoutés directement
{directGrants.map(g=>
{!selectedRecord?.children?.length &&
Sous-espace partagé indépendant
Limite : {child.quota_bytes == null ? 'Sans limite' : fmtSize(child.quota_bytes)} · Utilisé : {fmtSize(child.used_bytes || 0)}{child.reserved_bytes > 0 ? ` · Transferts réservés : ${fmtSize(child.reserved_bytes)}` : ''} · Disponible : {child.available_bytes == null ? 'Sans limite' : fmtSize(child.available_bytes)}
{[grant.first_name,grant.last_name].filter(Boolean).join(' ') || grant.username}
@{grant.username}