import { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { authStorage } from '../utils/auth-storage';

interface TokenClaims {
  permissions?: string[];
  roles?: string[];
}

/**
 * Reads the claims out of the access token without verifying it.
 *
 * Verification is the API's job — this is only used to decide what to *show*.
 * Every guarded route is enforced server-side by `PermissionGuard`, so the
 * worst a wrong answer here can do is offer a menu item that then 403s.
 */
function decodeClaims(token: string | undefined): TokenClaims | null {
  if (!token) return null;
  const payload = token.split('.')[1];
  if (!payload) return null;
  try {
    const json = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
    // The claim set is UTF-8; atob yields latin-1, so non-ASCII names would
    // otherwise come back mangled.
    const decoded = decodeURIComponent(
      json
        .split('')
        .map((c) => `%${c.charCodeAt(0).toString(16).padStart(2, '0')}`)
        .join(''),
    );
    return JSON.parse(decoded) as TokenClaims;
  } catch {
    return null;
  }
}

export interface PermissionSet {
  permissions: string[];
  /** True if the user holds any one of `required`. Empty `required` = allowed. */
  can: (required?: string[]) => boolean;
  /**
   * Whether permissions could be read at all. When false, callers should show
   * everything rather than hide the whole application from someone whose token
   * simply does not carry the claim.
   */
  known: boolean;
}

/**
 * What the signed-in user is allowed to do.
 *
 * Deliberately fails open: if the token carries no `permissions` claim we
 * report `known: false` and `can()` returns true. Hiding navigation on a
 * claim-shape mismatch would leave a legitimate officer staring at an empty
 * sidebar with no way to tell why, whereas failing open costs at most a 403
 * on a link they should not have seen.
 */
export function usePermissions(): PermissionSet {
  // Re-read whenever the session changes rather than only on mount.
  const token = useSelector(
    (state: { auth?: { token?: string | null } }) => state.auth?.token,
  );

  return useMemo(() => {
    const claims = decodeClaims(token ?? authStorage.getToken());
    const permissions = claims?.permissions ?? [];
    const known = Array.isArray(claims?.permissions);
    const granted = new Set(permissions);

    return {
      permissions,
      known,
      can: (required?: string[]) => {
        if (!required?.length) return true;
        if (!known) return true;
        return required.some((permission) => granted.has(permission));
      },
    };
  }, [token]);
}
