import { useEffect, useMemo, useState, ReactNode } from 'react';
import { Head, usePage } from '@inertiajs/react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import AdminLayout from '@/layouts/AdminLayout';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { toast } from 'sonner';
import { Shield, Trash2, Plus, Building2, Search, Clock, Mail, UserPlus, ChevronsUpDown, Check, User, LogIn, KeyRound } from 'lucide-react';
import { ResetPasswordDialog, type EditableUser } from '@/components/admin/UserAccountDialogs';
import { cn } from '@/lib/utils';

type RoleDef = {
  id: string;
  name: string;
  label: string;
  is_sys: boolean;
  is_system: boolean;
  deletable: boolean;
};

type UserSearchResult = { id: string; full_name: string | null; email: string };

const csrfToken = () =>
  (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement | null)?.content ?? '';

const roleBadgeVariant = (role: string) => {
  if (role === 'sys_admin' || role === 'admin') return 'destructive';
  if (role === 'sys_manager' || role === 'manager') return 'default';
  if (role === 'company_manager') return 'secondary';
  return 'outline';
};

const UserRolesPage = () => {
  const queryClient = useQueryClient();
  const [search, setSearch] = useState('');

  const { auth } = usePage().props as { auth: { user: { id: string; roles: string[] } | null } };
  const currentUserId = auth?.user?.id ?? null;
  const isSysAdmin = !!auth?.user?.roles?.includes('sys_admin');

  // Impersonation — only rendered for sys_admin. Server-side gating is the
  // real enforcement (sys.admin middleware on the route); this flag just
  // hides the button for everyone else.
  const loginAs = useMutation({
    mutationFn: async (userId: string) => {
      const res = await fetch(`/api/admin/functions/login-as/${encodeURIComponent(userId)}`, {
        method: 'POST',
        headers: { 'X-CSRF-TOKEN': csrfToken() },
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data?.error || 'Failed to switch user');
      return data as { redirect: string };
    },
    onSuccess: (data) => {
      // Full reload so Inertia drops cached pages and the new session takes effect.
      window.location.href = data.redirect || '/admin';
    },
    onError: (err: Error) => toast.error(err.message),
  });

  // Assign to existing user
  const [newEmail, setNewEmail] = useState('');
  const [newRole, setNewRole] = useState<string>('');
  const [newCompanyId, setNewCompanyId] = useState<string>('');

  // Per-row "add another role" dialog target.
  const [addRoleFor, setAddRoleFor] = useState<{ email: string; full_name: string | null } | null>(null);

  // Per-row "set password" dialog target.
  const [resettingPassword, setResettingPassword] = useState<EditableUser | null>(null);

  // Invite new user
  const [inviteEmail, setInviteEmail] = useState('');
  const [inviteFullName, setInviteFullName] = useState('');
  const [inviteRole, setInviteRole] = useState<string>('');
  const [inviteCompanyId, setInviteCompanyId] = useState('');

  // "Create without invite" dialog — reuses the invite form fields, adds a password.
  const [createOpen, setCreateOpen] = useState(false);
  const [createPw, setCreatePw] = useState('');
  const [createConfirm, setCreateConfirm] = useState('');

  // Fetch the dynamic role catalog (managed at /admin/role-management).
  const { data: rolesCatalog = [] } = useQuery<RoleDef[]>({
    queryKey: ['admin-roles-catalog'],
    queryFn: async () => {
      const res = await fetch('/api/admin/roles');
      if (!res.ok) return [];
      return res.json();
    },
  });

  const selectedRoleMeta = rolesCatalog.find((r) => r.name === newRole);
  const selectedInviteRoleMeta = rolesCatalog.find((r) => r.name === inviteRole);
  // Non-sys roles can be company-scoped (legacy behaviour). The company picker
  // is shown for any non-sys role; it's optional unless the role explicitly
  // requires it (we don't enforce required here — the API treats it as optional).
  const newRoleNeedsCompany = !!selectedRoleMeta && !selectedRoleMeta.is_sys;
  const inviteRoleNeedsCompany = !!selectedInviteRoleMeta && !selectedInviteRoleMeta.is_sys;

  // Fetch all active roles
  const { data: roleEntries = [], isLoading } = useQuery<any[]>({
    queryKey: ['admin-user-roles'],
    queryFn: async () => {
      const res = await fetch('/api/admin/user-roles');
      if (!res.ok) return [];
      return res.json();
    },
  });

  // Fetch pending role assignments
  const { data: pendingAssignments = [], isLoading: pendingLoading } = useQuery<any[]>({
    queryKey: ['admin-pending-role-assignments'],
    queryFn: async () => {
      const res = await fetch('/api/admin/pending-role-assignments');
      if (!res.ok) return [];
      return res.json();
    },
  });

  // Fetch companies
  const { data: companies = [] } = useQuery<any[]>({
    queryKey: ['companies-for-roles'],
    queryFn: async () => {
      const res = await fetch('/api/admin/training-companies?fields=id-name');
      if (!res.ok) return [];
      return res.json();
    },
  });

  // Invite new user mutation
  const inviteUser = useMutation({
    mutationFn: async () => {
      if (!inviteEmail || !inviteRole) throw new Error('Email and role are required');

      const res = await fetch('/api/admin/functions/invite-user', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken() },
        body: JSON.stringify({
          email: inviteEmail.trim(),
          full_name: inviteFullName.trim() || undefined,
          role: inviteRole || undefined,
          company_id: inviteRoleNeedsCompany && inviteCompanyId ? inviteCompanyId : undefined,
        }),
      });

      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data?.error || 'Failed to send invite');
      if (data?.error) throw new Error(data.error);
      return data;
    },
    onSuccess: (data: any) => {
      const msg = data?.message ?? "Invite sent! They'll receive an email to set their password.";
      toast.success(msg, { duration: 5000 });
      setInviteEmail('');
      setInviteFullName('');
      setInviteRole('');
      setInviteCompanyId('');
      queryClient.invalidateQueries({ queryKey: ['admin-user-roles'] });
      queryClient.invalidateQueries({ queryKey: ['admin-pending-role-assignments'] });
    },
    onError: (err: Error) => toast.error(err.message),
  });

  // Create user directly (no invite email) — admin sets the password up front.
  const createUser = useMutation({
    mutationFn: async () => {
      if (!inviteEmail || !inviteRole) throw new Error('Email and role are required');

      const res = await fetch('/api/admin/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken() },
        body: JSON.stringify({
          email: inviteEmail.trim(),
          full_name: inviteFullName.trim() || undefined,
          password: createPw,
          role: inviteRole,
          company_id: inviteRoleNeedsCompany && inviteCompanyId ? inviteCompanyId : undefined,
        }),
      });

      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data?.error || 'Failed to create user');
      return data;
    },
    onSuccess: () => {
      toast.success('Account created — no email was sent. Share the password with them yourself.', { duration: 6000 });
      setCreateOpen(false);
      setCreatePw('');
      setCreateConfirm('');
      setInviteEmail('');
      setInviteFullName('');
      setInviteRole('');
      setInviteCompanyId('');
      queryClient.invalidateQueries({ queryKey: ['admin-user-roles'] });
      queryClient.invalidateQueries({ queryKey: ['admin-pending-role-assignments'] });
    },
    onError: (err: Error) => toast.error(err.message),
  });

  // Assign role to existing user mutation
  const assignRole = useMutation({
    mutationFn: async () => {
      if (!newEmail || !newRole) throw new Error('User and role are required');

      const res = await fetch('/api/admin/user-roles', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken() },
        body: JSON.stringify({
          email: newEmail.trim().toLowerCase(),
          role: newRole,
          ...(newRoleNeedsCompany && newCompanyId ? { company_id: newCompanyId } : {}),
        }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) {
        if (data?.code === '23505') {
          throw new Error(
            data?.kind === 'pending'
              ? 'A pending assignment for this email and role already exists'
              : 'This user already has this role',
          );
        }
        throw new Error(data?.error || 'Failed to assign role');
      }
      return (data?.kind || 'assigned') as 'assigned' | 'pending';
    },
    onSuccess: (result) => {
      if (result === 'pending') {
        toast.success('No account found — role queued and will apply automatically when they register.', { duration: 5000 });
      } else {
        toast.success('Role assigned successfully');
      }
      setNewEmail(''); setNewRole(''); setNewCompanyId('');
      queryClient.invalidateQueries({ queryKey: ['admin-user-roles'] });
      queryClient.invalidateQueries({ queryKey: ['admin-pending-role-assignments'] });
    },
    onError: (err: Error) => toast.error(err.message),
  });

  // Remove role mutation
  const removeRole = useMutation({
    mutationFn: async (roleId: string) => {
      const res = await fetch(`/api/admin/user-roles/${encodeURIComponent(roleId)}`, {
        method: 'DELETE',
        headers: { 'X-CSRF-TOKEN': csrfToken() },
      });
      if (!res.ok) throw new Error('Failed to remove role');
    },
    onSuccess: () => {
      toast.success('Role removed');
      queryClient.invalidateQueries({ queryKey: ['admin-user-roles'] });
    },
    onError: (err: Error) => toast.error(err.message),
  });

  // Remove pending assignment mutation
  const removePending = useMutation({
    mutationFn: async (id: string) => {
      const res = await fetch(`/api/admin/pending-role-assignments/${encodeURIComponent(id)}`, {
        method: 'DELETE',
        headers: { 'X-CSRF-TOKEN': csrfToken() },
      });
      if (!res.ok) throw new Error('Failed to cancel pending assignment');
    },
    onSuccess: () => {
      toast.success('Pending assignment cancelled');
      queryClient.invalidateQueries({ queryKey: ['admin-pending-role-assignments'] });
    },
    onError: (err: Error) => toast.error(err.message),
  });

  const q = search.toLowerCase();
  const matches = (s: unknown) => typeof s === 'string' && s.toLowerCase().includes(q);

  const filtered = roleEntries.filter(
    (r: any) => !search || matches(r.email) || matches(r.full_name) || matches(r.role) || matches(r.company_name),
  );

  const filteredPending = pendingAssignments.filter(
    (r: any) => !search || matches(r.email) || matches(r.role) || matches(r.company_name),
  );

  return (
    <>
      <Head title="User Role Management" />
      <div className="space-y-6">
        <div>
          <h1 className="text-2xl font-bold text-foreground flex items-center gap-2">
            <Shield className="h-6 w-6 text-primary" />
            User Role Management
          </h1>
          <p className="text-muted-foreground mt-1">
            Invite new users or assign roles to existing accounts.
          </p>
        </div>

        {/* Action Tabs */}
        <Tabs defaultValue="invite">
          <TabsList>
            <TabsTrigger value="invite" className="flex items-center gap-2">
              <UserPlus className="h-4 w-4" /> Invite New User
            </TabsTrigger>
            <TabsTrigger value="assign" className="flex items-center gap-2">
              <Plus className="h-4 w-4" /> Assign Role to Existing
            </TabsTrigger>
          </TabsList>

          {/* ── Invite Tab ── */}
          <TabsContent value="invite">
            <Card>
              <CardHeader>
                <CardTitle className="text-base flex items-center gap-2">
                  <Mail className="h-4 w-4" /> Invite User
                </CardTitle>
                <p className="text-xs text-muted-foreground">
                  The user will receive an email invitation with a link to set their password. Their role will be applied automatically when they accept.
                  Or use <strong>Create Without Invite</strong> to set their password yourself — no email is sent.
                </p>
              </CardHeader>
              <CardContent>
                <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                  <div className="space-y-2">
                    <Label>Email Address *</Label>
                    <Input
                      placeholder="user@example.com"
                      type="email"
                      value={inviteEmail}
                      onChange={(e) => setInviteEmail(e.target.value)}
                    />
                  </div>
                  <div className="space-y-2">
                    <Label>Full Name</Label>
                    <Input
                      placeholder="Jane Smith"
                      value={inviteFullName}
                      onChange={(e) => setInviteFullName(e.target.value)}
                    />
                  </div>
                  <div className="space-y-2">
                    <Label>Role *</Label>
                    <Select value={inviteRole} onValueChange={(v) => { setInviteRole(v); setInviteCompanyId(''); }}>
                      <SelectTrigger>
                        <SelectValue placeholder="Select role" />
                      </SelectTrigger>
                      <SelectContent>
                        {rolesCatalog.map((r) => (
                          <SelectItem key={r.id} value={r.name}>{r.label}</SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  </div>
                  {inviteRoleNeedsCompany && (
                    <div className="space-y-2">
                      <Label className="flex items-center gap-1">
                        <Building2 className="h-3 w-3" /> Company
                      </Label>
                      <Select value={inviteCompanyId} onValueChange={setInviteCompanyId}>
                        <SelectTrigger>
                          <SelectValue placeholder="Select company (optional)" />
                        </SelectTrigger>
                        <SelectContent>
                          {companies.map((c: any) => (
                            <SelectItem key={c.id} value={c.id}>{c.name}</SelectItem>
                          ))}
                        </SelectContent>
                      </Select>
                    </div>
                  )}
                </div>
                <div className="mt-4 flex items-center gap-2">
                  <Button
                    onClick={() => inviteUser.mutate()}
                    disabled={inviteUser.isPending || !inviteEmail || !inviteRole}
                  >
                    <Mail className="mr-2 h-4 w-4" />
                    {inviteUser.isPending ? 'Sending Invite...' : 'Send Invite'}
                  </Button>
                  <Button
                    variant="outline"
                    onClick={() => { setCreatePw(''); setCreateConfirm(''); setCreateOpen(true); }}
                    disabled={createUser.isPending || !inviteEmail || !inviteRole}
                    title="Create the account now with a password you set — no invite email is sent"
                  >
                    <UserPlus className="mr-2 h-4 w-4" />
                    Create Without Invite
                  </Button>
                </div>
              </CardContent>
            </Card>
          </TabsContent>

          {/* ── Assign Tab ── */}
          <TabsContent value="assign">
            <Card>
              <CardHeader>
                <CardTitle className="text-base flex items-center gap-2">
                  <Plus className="h-4 w-4" /> Assign Role
                </CardTitle>
                <p className="text-xs text-muted-foreground">
                  Pick an existing user and the role to give them. To onboard someone who doesn't have an account yet, use <strong>Invite New User</strong>.
                </p>
              </CardHeader>
              <CardContent>
                <div className="grid grid-cols-1 md:grid-cols-4 gap-4 items-end">
                  <div className="space-y-2">
                    <Label>User</Label>
                    <UserPicker
                      value={newEmail}
                      onChange={setNewEmail}
                      placeholder="Search users by name or email…"
                    />
                  </div>
                  <div className="space-y-2">
                    <Label>Role</Label>
                    <Select value={newRole} onValueChange={(v) => { setNewRole(v); setNewCompanyId(''); }}>
                      <SelectTrigger><SelectValue placeholder="Select role" /></SelectTrigger>
                      <SelectContent>
                        {rolesCatalog.map((r) => (
                          <SelectItem key={r.id} value={r.name}>{r.label}</SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  </div>
                  {newRoleNeedsCompany && (
                    <div className="space-y-2">
                      <Label className="flex items-center gap-1">
                        <Building2 className="h-3 w-3" /> Company
                      </Label>
                      <Select value={newCompanyId} onValueChange={setNewCompanyId}>
                        <SelectTrigger><SelectValue placeholder="Select company (optional)" /></SelectTrigger>
                        <SelectContent>
                          {companies.map((c: any) => (
                            <SelectItem key={c.id} value={c.id}>{c.name}</SelectItem>
                          ))}
                        </SelectContent>
                      </Select>
                    </div>
                  )}
                  <Button
                    onClick={() => assignRole.mutate()}
                    disabled={assignRole.isPending || !newEmail || !newRole}
                  >
                    <Plus className="mr-2 h-4 w-4" />
                    {assignRole.isPending ? 'Processing...' : 'Assign Role'}
                  </Button>
                </div>
              </CardContent>
            </Card>
          </TabsContent>
        </Tabs>

        {/* Pending Assignments */}
        {(filteredPending.length > 0 || pendingLoading) && (
          <Card className="border-warning/30 bg-warning/5">
            <CardHeader>
              <CardTitle className="text-base flex items-center gap-2">
                <Clock className="h-4 w-4 text-warning" />
                Pending Assignments ({filteredPending.length})
              </CardTitle>
              <p className="text-xs text-muted-foreground">
                Applied automatically when the user registers or accepts their invite.
              </p>
            </CardHeader>
            <CardContent>
              {pendingLoading ? (
                <p className="text-muted-foreground text-center py-4">Loading...</p>
              ) : (
                <Table>
                  <TableHeader>
                    <TableRow>
                      <TableHead>Email</TableHead>
                      <TableHead>Role</TableHead>
                      <TableHead>Company</TableHead>
                      <TableHead>Queued</TableHead>
                      <TableHead className="w-16"></TableHead>
                    </TableRow>
                  </TableHeader>
                  <TableBody>
                    {filteredPending.map((entry: any) => (
                      <TableRow key={entry.id}>
                        <TableCell className="font-medium">{entry.email}</TableCell>
                        <TableCell>
                          <Badge variant={roleBadgeVariant(entry.role)}>
                            {rolesCatalog.find((r) => r.name === entry.role)?.label ?? entry.role}
                          </Badge>
                        </TableCell>
                        <TableCell>
                          {entry.company_name ? (
                            <span className="flex items-center gap-1 text-sm">
                              <Building2 className="h-3 w-3 text-muted-foreground" />
                              {entry.company_name}
                            </span>
                          ) : <span className="text-muted-foreground">—</span>}
                        </TableCell>
                        <TableCell className="text-muted-foreground text-sm">
                          {new Date(entry.created_at).toLocaleDateString()}
                        </TableCell>
                        <TableCell>
                          <Button
                            variant="ghost" size="icon"
                            className="h-8 w-8 text-destructive hover:text-destructive"
                            onClick={() => removePending.mutate(entry.id)}
                            disabled={removePending.isPending}
                          >
                            <Trash2 className="h-4 w-4" />
                          </Button>
                        </TableCell>
                      </TableRow>
                    ))}
                  </TableBody>
                </Table>
              )}
            </CardContent>
          </Card>
        )}

        {/* Active Role List */}
        <Card>
          <CardHeader>
            <div className="flex items-center justify-between">
              <CardTitle className="text-base">Current Role Assignments ({filtered.length})</CardTitle>
              <div className="relative w-64">
                <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
                <Input
                  placeholder="Search users, roles..."
                  value={search}
                  onChange={(e) => setSearch(e.target.value)}
                  className="pl-9"
                />
              </div>
            </div>
          </CardHeader>
          <CardContent>
            {isLoading ? (
              <p className="text-muted-foreground text-center py-8">Loading roles...</p>
            ) : (
              <Table>
                <TableHeader>
                  <TableRow>
                    <TableHead>User</TableHead>
                    <TableHead>Email</TableHead>
                    <TableHead>Role</TableHead>
                    <TableHead>Company</TableHead>
                    <TableHead className={cn('text-right', isSysAdmin ? 'w-48' : 'w-32')}>Actions</TableHead>
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {filtered.map((entry: any) => (
                    <TableRow key={entry.id}>
                      <TableCell className="font-medium">{entry.full_name || '—'}</TableCell>
                      <TableCell className="text-muted-foreground">{entry.email}</TableCell>
                      <TableCell>
                        <Badge variant={roleBadgeVariant(entry.role)}>
                          {rolesCatalog.find((r) => r.name === entry.role)?.label ?? entry.role}
                        </Badge>
                      </TableCell>
                      <TableCell>
                        {entry.company_name ? (
                          <span className="flex items-center gap-1 text-sm">
                            <Building2 className="h-3 w-3 text-muted-foreground" />
                            {entry.company_name}
                          </span>
                        ) : <span className="text-muted-foreground">—</span>}
                      </TableCell>
                      <TableCell>
                        <div className="flex items-center justify-end gap-1">
                          {isSysAdmin && entry.user_id && entry.user_id !== currentUserId && (
                            <Button
                              variant="outline" size="sm"
                              className="h-8"
                              onClick={() => loginAs.mutate(entry.user_id)}
                              disabled={loginAs.isPending}
                              title={`Sign in as ${entry.full_name || entry.email}`}
                            >
                              <LogIn className="mr-1 h-3.5 w-3.5" />
                              Login As
                            </Button>
                          )}
                          {entry.user_id && (
                            <Button
                              variant="ghost" size="icon"
                              className="h-8 w-8"
                              onClick={() => setResettingPassword({
                                user_id: entry.user_id,
                                full_name: entry.full_name,
                                email: entry.email,
                              })}
                              title="Set a new password for this user"
                            >
                              <KeyRound className="h-4 w-4" />
                            </Button>
                          )}
                          <Button
                            variant="ghost" size="icon"
                            className="h-8 w-8"
                            onClick={() => setAddRoleFor({ email: entry.email, full_name: entry.full_name })}
                            title="Add another role for this user"
                          >
                            <Plus className="h-4 w-4" />
                          </Button>
                          <Button
                            variant="ghost" size="icon"
                            className="h-8 w-8 text-destructive hover:text-destructive"
                            onClick={() => removeRole.mutate(entry.id)}
                            disabled={removeRole.isPending}
                            title="Remove this role"
                          >
                            <Trash2 className="h-4 w-4" />
                          </Button>
                        </div>
                      </TableCell>
                    </TableRow>
                  ))}
                  {filtered.length === 0 && (
                    <TableRow>
                      <TableCell colSpan={5} className="text-center text-muted-foreground py-8">
                        No role assignments found
                      </TableCell>
                    </TableRow>
                  )}
                </TableBody>
              </Table>
            )}
          </CardContent>
        </Card>
      </div>

      <AddRoleDialog
        target={addRoleFor}
        onClose={() => setAddRoleFor(null)}
        rolesCatalog={rolesCatalog}
        companies={companies}
        onAssigned={() => {
          queryClient.invalidateQueries({ queryKey: ['admin-user-roles'] });
          queryClient.invalidateQueries({ queryKey: ['admin-pending-role-assignments'] });
        }}
      />

      <ResetPasswordDialog
        user={resettingPassword}
        onClose={() => setResettingPassword(null)}
      />

      {/* Create-without-invite: collect the password, then create the account directly. */}
      <Dialog open={createOpen} onOpenChange={(o) => { if (!o) setCreateOpen(false); }}>
        <DialogContent className="max-w-md">
          <DialogHeader>
            <DialogTitle>Create user without invite</DialogTitle>
            <DialogDescription>
              Creates an account for <strong>{inviteEmail}</strong> with the role applied immediately.
              No email is sent — share the password with them yourself.
            </DialogDescription>
          </DialogHeader>
          <div className="space-y-3">
            <div className="space-y-2">
              <Label htmlFor="create-pw">Password *</Label>
              <Input
                id="create-pw"
                type="password"
                value={createPw}
                onChange={(e) => setCreatePw(e.target.value)}
                minLength={8}
              />
              <p className="text-[10px] text-muted-foreground">Minimum 8 characters.</p>
            </div>
            <div className="space-y-2">
              <Label htmlFor="create-pw-confirm">Confirm password *</Label>
              <Input
                id="create-pw-confirm"
                type="password"
                value={createConfirm}
                onChange={(e) => setCreateConfirm(e.target.value)}
              />
              {createConfirm && createConfirm !== createPw && (
                <p className="text-[10px] text-destructive">Passwords don't match.</p>
              )}
            </div>
          </div>
          <DialogFooter>
            <Button variant="outline" onClick={() => setCreateOpen(false)}>Cancel</Button>
            <Button
              onClick={() => createUser.mutate()}
              disabled={createUser.isPending || createPw.length < 8 || createPw !== createConfirm}
            >
              <UserPlus className="mr-2 h-4 w-4" />
              {createUser.isPending ? 'Creating…' : 'Create user'}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  );
};

UserRolesPage.layout = (page: ReactNode) => <AdminLayout>{page}</AdminLayout>;

export default UserRolesPage;

// ─────────────────────────────────────────────────────────────────────────────
// AddRoleDialog — pre-targeted "assign another role" modal for an existing user.
// Reuses the public POST /api/admin/user-roles endpoint, so it falls back to a
// pending assignment if the user somehow doesn't exist on the server side.

function AddRoleDialog({
  target,
  onClose,
  rolesCatalog,
  companies,
  onAssigned,
}: {
  target: { email: string; full_name: string | null } | null;
  onClose: () => void;
  rolesCatalog: RoleDef[];
  companies: any[];
  onAssigned: () => void;
}) {
  const [role, setRole] = useState('');
  const [companyId, setCompanyId] = useState('');
  const roleMeta = rolesCatalog.find((r) => r.name === role);
  const needsCompany = !!roleMeta && !roleMeta.is_sys;

  // Reset state whenever the dialog (re)opens for a new user.
  useEffect(() => {
    if (target) {
      setRole('');
      setCompanyId('');
    }
  }, [target?.email]);

  const submit = useMutation({
    mutationFn: async () => {
      if (!target || !role) throw new Error('Role is required');
      const res = await fetch('/api/admin/user-roles', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken() },
        body: JSON.stringify({
          email: target.email,
          role,
          ...(needsCompany && companyId ? { company_id: companyId } : {}),
        }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) {
        const msg = data?.kind === 'duplicate'
          ? 'This user already has that role with that company.'
          : (data?.error || 'Failed to assign role.');
        throw new Error(msg);
      }
      return data;
    },
    onSuccess: () => {
      toast.success('Role assigned');
      onAssigned();
      onClose();
    },
    onError: (e: Error) => toast.error(e.message),
  });

  const open = !!target;
  return (
    <Dialog open={open} onOpenChange={(o) => { if (!o) onClose(); }}>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Add another role</DialogTitle>
          <DialogDescription>
            for <strong>{target?.full_name || target?.email}</strong>
            {target?.full_name && <span className="text-muted-foreground"> · {target.email}</span>}
          </DialogDescription>
        </DialogHeader>

        <p className="text-xs text-muted-foreground -mt-2">
          This is added alongside any existing roles — nothing is replaced or removed.
          To remove a role, use the <Trash2 className="inline h-3 w-3 align-text-bottom" /> icon
          on its row in the table.
        </p>

        <div className="space-y-4">
          <div className="space-y-2">
            <Label>Role *</Label>
            <Select value={role} onValueChange={(v) => { setRole(v); setCompanyId(''); }}>
              <SelectTrigger><SelectValue placeholder="Select role" /></SelectTrigger>
              <SelectContent>
                {rolesCatalog.map((r) => (
                  <SelectItem key={r.id} value={r.name}>{r.label}</SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          {needsCompany && (
            <div className="space-y-2">
              <Label className="flex items-center gap-1">
                <Building2 className="h-3 w-3" /> Company
              </Label>
              <Select value={companyId} onValueChange={setCompanyId}>
                <SelectTrigger><SelectValue placeholder="Select company (optional)" /></SelectTrigger>
                <SelectContent>
                  {companies.map((c: any) => (
                    <SelectItem key={c.id} value={c.id}>{c.name}</SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
          )}
        </div>

        <DialogFooter>
          <Button variant="outline" onClick={onClose}>Cancel</Button>
          <Button onClick={() => submit.mutate()} disabled={!role || submit.isPending}>
            <Plus className="mr-2 h-4 w-4" />
            {submit.isPending ? 'Assigning…' : 'Assign role'}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// UserPicker — searchable user combobox backed by /api/admin/search/users.
// Stores the selected user's email in `value`. Selection is required (no free
// typing) since this form targets existing accounts only.

function UserPicker({
  value,
  onChange,
  placeholder,
}: {
  value: string;
  onChange: (email: string) => void;
  placeholder?: string;
}) {
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState('');
  // Debounce the network query so each keystroke doesn't fire a fetch.
  const [debounced, setDebounced] = useState('');
  useEffect(() => {
    const t = setTimeout(() => setDebounced(query), 200);
    return () => clearTimeout(t);
  }, [query]);

  const { data: users = [], isFetching } = useQuery<UserSearchResult[]>({
    queryKey: ['user-search', debounced],
    queryFn: async () => {
      if (debounced.trim().length < 2) return [];
      const res = await fetch(`/api/admin/search/users?q=${encodeURIComponent(debounced.trim())}`);
      if (!res.ok) return [];
      return res.json();
    },
    enabled: open && debounced.trim().length >= 2,
  });

  const selectedLabel = useMemo(() => {
    if (!value) return '';
    const match = users.find((u) => u.email === value);
    return match ? `${match.full_name || match.email}` : value;
  }, [users, value]);

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <Button
          variant="outline"
          role="combobox"
          aria-expanded={open}
          className={cn(
            'w-full justify-between font-normal',
            !value && 'text-muted-foreground',
          )}
        >
          <span className="flex items-center gap-2 truncate">
            <User className="h-4 w-4 shrink-0" />
            <span className="truncate">{value ? selectedLabel : (placeholder ?? 'Select user…')}</span>
          </span>
          <ChevronsUpDown className="h-4 w-4 opacity-50 shrink-0" />
        </Button>
      </PopoverTrigger>
      <PopoverContent className="p-0 w-[--radix-popover-trigger-width]" align="start">
        <Command shouldFilter={false}>
          <CommandInput
            placeholder="Search by name or email…"
            value={query}
            onValueChange={setQuery}
          />
          <CommandList>
            {debounced.trim().length < 2 ? (
              <div className="py-6 text-center text-xs text-muted-foreground">
                Type at least 2 characters to search.
              </div>
            ) : isFetching ? (
              <div className="py-6 text-center text-xs text-muted-foreground">Searching…</div>
            ) : users.length === 0 ? (
              <CommandEmpty>No users found.</CommandEmpty>
            ) : (
              <CommandGroup>
                {users.map((u) => (
                  <CommandItem
                    key={u.id}
                    value={u.email}
                    onSelect={() => {
                      onChange(u.email);
                      setOpen(false);
                      setQuery('');
                    }}
                    className="flex items-center gap-2"
                  >
                    <Check className={cn('h-4 w-4', value === u.email ? 'opacity-100' : 'opacity-0')} />
                    <div className="flex flex-col min-w-0">
                      <span className="text-sm font-medium truncate">{u.full_name || u.email}</span>
                      {u.full_name && (
                        <span className="text-[11px] text-muted-foreground truncate">{u.email}</span>
                      )}
                    </div>
                  </CommandItem>
                ))}
              </CommandGroup>
            )}
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}
