import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import { Modal } from '../ui/Modal';
import { ConfirmDialog } from '../ui/ConfirmDialog';
import { useUIStore } from '../../stores/uiStore';
import { useSpaceStore, getApiForOrigin } from '../../stores/spaceStore';
import { api } from '../../api/client';
import { PermissionBits, permissionsToString, stringToPermissions, hasPermissionBit } from '../../utils/permissions';
import { Toggle } from '../ui/Toggle';
import type { Role, MemberWithUser } from '@backspace/shared';
interface ChannelOverride {
channelId: string;
targetType: string;
targetId: string;
allow: string;
deny: string;
}
// ─── Permission Definitions for Channel Overrides ──────────────────────────────
interface PermissionDef {
key: keyof typeof PermissionBits;
label: string;
bit: bigint;
}
const TEXT_CHANNEL_PERMISSIONS: PermissionDef[] = [
{ key: 'VIEW_CHANNEL', label: 'View Channel', bit: PermissionBits.VIEW_CHANNEL },
{ key: 'SEND_MESSAGES', label: 'Send Messages', bit: PermissionBits.SEND_MESSAGES },
{ key: 'MANAGE_MESSAGES', label: 'Manage Messages', bit: PermissionBits.MANAGE_MESSAGES },
{ key: 'ATTACH_FILES', label: 'Attach Files', bit: PermissionBits.ATTACH_FILES },
{ key: 'READ_MESSAGE_HISTORY', label: 'Read Message History', bit: PermissionBits.READ_MESSAGE_HISTORY },
{ key: 'ADD_REACTIONS', label: 'Add Reactions', bit: PermissionBits.ADD_REACTIONS },
];
const VOICE_CHANNEL_PERMISSIONS: PermissionDef[] = [
{ key: 'VIEW_CHANNEL', label: 'View Channel', bit: PermissionBits.VIEW_CHANNEL },
{ key: 'CONNECT', label: 'Connect', bit: PermissionBits.CONNECT },
{ key: 'SPEAK', label: 'Speak', bit: PermissionBits.SPEAK },
{ key: 'STREAM', label: 'Stream', bit: PermissionBits.STREAM },
{ key: 'MUTE_MEMBERS', label: 'Mute Members', bit: PermissionBits.MUTE_MEMBERS },
{ key: 'DEAFEN_MEMBERS', label: 'Deafen Members', bit: PermissionBits.DEAFEN_MEMBERS },
{ key: 'MOVE_MEMBERS', label: 'Move Members', bit: PermissionBits.MOVE_MEMBERS },
{ key: 'DISCONNECT_MEMBERS', label: 'Disconnect Members', bit: PermissionBits.DISCONNECT_MEMBERS },
];
// ─── Tri-State Toggle ──────────────────────────────────────────────────────────
type TriState = 'allow' | 'neutral' | 'deny';
function TriStateToggle({
value,
onChange,
disabled,
}: {
value: TriState;
onChange: (v: TriState) => void;
disabled?: boolean;
}) {
const btnClass = (v: TriState, active: boolean) => {
const base = 'w-6 h-6 flex items-center justify-center rounded-full transition-colors text-xs font-bold';
if (disabled) return `${base} cursor-not-allowed opacity-40`;
if (!active) return `${base} cursor-pointer text-txt-muted hover:text-txt-tertiary`;
switch (v) {
case 'deny': return `${base} cursor-pointer bg-accent-rose/15 text-accent-rose`;
case 'neutral': return `${base} cursor-pointer bg-white/[0.06] text-txt-tertiary`;
case 'allow': return `${base} cursor-pointer bg-accent-primary/15 text-accent-primary`;
}
};
return (
);
}
// ─── Override Entry (expandable row) ────────────────────────────────────────────
function OverrideEntry({
label,
color,
permDefs,
allow,
deny,
onChange,
onRemove,
isEveryone,
}: {
label: string;
color?: string;
permDefs: PermissionDef[];
allow: bigint;
deny: bigint;
onChange: (allow: bigint, deny: bigint) => void;
onRemove?: () => void;
isEveryone?: boolean;
}) {
const [expanded, setExpanded] = useState(false);
const getState = (bit: bigint): TriState => {
if ((allow & bit) !== 0n) return 'allow';
if ((deny & bit) !== 0n) return 'deny';
return 'neutral';
};
const setState = (bit: bigint, state: TriState) => {
let newAllow = allow & ~bit;
let newDeny = deny & ~bit;
if (state === 'allow') newAllow |= bit;
if (state === 'deny') newDeny |= bit;
onChange(newAllow, newDeny);
};
// Compact summary of non-neutral permissions
const summary = permDefs.filter(p => getState(p.bit) !== 'neutral');
return (
{expanded && (
{permDefs.map((perm) => (
{perm.label}
setState(perm.bit, v)}
/>
))}
)}
);
}
// ─── Permissions Tab ────────────────────────────────────────────────────────────
function PermissionsTab({
channelId,
channelType,
spaceId,
overrides,
onOverridesChange,
}: {
channelId: string;
channelType: 'text' | 'voice';
spaceId: string;
overrides: ChannelOverride[];
onOverridesChange: () => void;
}) {
const spaces = useSpaceStore((s) => s.spaces);
const space = spaces.find(s => s.id === spaceId);
const roles = useSpaceStore((s) => s.roles);
const members = useSpaceStore((s) => s.members);
// Draft state: keyed by "role:id" or "member:id"
const [draftOverrides, setDraftOverrides] = useState