import {
  useMutation,
  useQuery,
  useQueryClient,
} from "@tanstack/react-query";
import * as usersApi from "../../api/users";
import type {
  CreateUserRequest,
  Permission,
  UpdateUserRequest,
} from "../../api/types";

// Distinct from ["machines"], ["sessions"], ["codes"], ["machine-keys"] — no
// cache-key collision with the other features.
const USERS_KEY = ["users"] as const;

/**
 * List all users. Users change rarely (an admin creates/edits them by hand), so
 * there is no polling — a modest staleTime plus explicit invalidation on every
 * mutation keeps the table correct without a render storm. AbortSignal is
 * threaded so a navigation-away cancels the in-flight request.
 */
export function useUsers() {
  return useQuery({
    queryKey: USERS_KEY,
    queryFn: ({ signal }) => usersApi.listUsers(signal),
    staleTime: 30_000,
  });
}

/** Create a user, then invalidate the list so the new row appears. */
export function useCreateUser() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (body: CreateUserRequest) => usersApi.createUser(body),
    onSuccess: () => {
      void qc.invalidateQueries({ queryKey: USERS_KEY });
    },
  });
}

/**
 * Update a user's core fields (role / enabled / email / optional password) and,
 * when `permissions` is provided, replace their permission set in the same
 * logical save. The two calls are sequenced (user first, then permissions) so a
 * failure on either surfaces as a single mutation error.
 *
 * This is a two-step write, so the steps can succeed independently. If the core
 * update lands but the permissions replacement throws, the account fields are
 * already persisted server-side while the mutation still rejects — so the error
 * message is rewritten to make clear the account saved and only permissions need
 * a retry. Either way, the list is invalidated in `onSettled` (not `onSuccess`)
 * so the table reconciles to true server state after a partial failure instead
 * of showing the stale pre-edit row behind an error toast.
 */
export function useUpdateUser() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (vars: {
      id: string;
      body: UpdateUserRequest;
      permissions?: (Permission | string)[];
    }) => {
      const updated = await usersApi.updateUser(vars.id, vars.body);
      if (vars.permissions) {
        try {
          await usersApi.setUserPermissions(vars.id, vars.permissions);
        } catch (err) {
          // The core update already succeeded; signal a partial failure so the
          // admin knows to retry permissions specifically rather than re-saving
          // the whole form.
          throw new Error(
            "User saved, but permissions update failed - retry permissions.",
            { cause: err },
          );
        }
      }
      return updated;
    },
    // Reconcile the table after EITHER outcome: on a partial failure the core
    // fields are persisted, so the cached row would otherwise stay stale.
    onSettled: () => {
      void qc.invalidateQueries({ queryKey: USERS_KEY });
    },
  });
}

/** Delete a user, then invalidate the list so the row disappears. */
export function useDeleteUser() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (id: string) => usersApi.deleteUser(id),
    onSuccess: () => {
      void qc.invalidateQueries({ queryKey: USERS_KEY });
    },
  });
}
