import { APP_ROLES, ROUTES, type AppRole } from '@/constants';
import type { Me, RoleCode } from './types';

const ADMIN_ROLES: RoleCode[] = ['admin', 'support', 'finance', 'moderation', 'super_admin'];

export function isAdminRole(role: RoleCode): boolean {
  return ADMIN_ROLES.includes(role);
}

/**
 * Collapse the server's fine-grained role codes to the three actor experiences the shells
 * render. Admin sub-roles all map to the single ADMIN shell; unknown/non-public codes drop out.
 */
export function toAppRoles(roles: RoleCode[]): AppRole[] {
  const actors = new Set<AppRole>();
  for (const role of roles) {
    if (role === 'customer') actors.add(APP_ROLES.CUSTOMER);
    else if (role === 'nurse') actors.add(APP_ROLES.NURSE);
    else if (isAdminRole(role)) actors.add(APP_ROLES.ADMIN);
  }
  return [...actors];
}

/**
 * The locale-less path a signed-in user should land on after auth — the pure core of the role
 * router (the component just navigates to it). `intendedRole` is the role carried from the login
 * switch (A1 vs B1); it only disambiguates a user who holds **both** customer and nurse.
 *
 * A nurse always routes to the nurse app here; the B3 "verification in progress" landing for an
 * unverified nurse is f5's job (this only routes correctly, it doesn't render the banner).
 */
export function resolveRoleDestination(me: Pick<Me, 'roles'>, intendedRole?: AppRole): string {
  if (me.roles.length === 0) return ROUTES.SELECT_ROLE;
  if (me.roles.some(isAdminRole)) return ROUTES.ADMIN;

  const hasNurse = me.roles.includes('nurse');
  const hasCustomer = me.roles.includes('customer');

  let target: AppRole;
  if (hasNurse && hasCustomer) {
    target = intendedRole === APP_ROLES.NURSE ? APP_ROLES.NURSE : APP_ROLES.CUSTOMER;
  } else {
    target = hasNurse ? APP_ROLES.NURSE : APP_ROLES.CUSTOMER;
  }

  return target === APP_ROLES.NURSE ? ROUTES.NURSE : ROUTES.HOME;
}

/** True for a same-origin relative path only — rejects a protocol-relative (`//host`) or absolute-URL `next`. */
function isSafeRelativePath(path: string): boolean {
  return path.startsWith('/') && !path.startsWith('//') && !path.startsWith('/\\');
}

/** The app (nurse/admin/customer) whose route tree owns `path`; customer paths carry no prefix. */
function appRoleForPath(path: string): AppRole | null {
  const pathname = path.split('?')[0].split('#')[0];
  if (pathname === ROUTES.NURSE || pathname.startsWith(`${ROUTES.NURSE}/`)) return APP_ROLES.NURSE;
  if (pathname === ROUTES.ADMIN || pathname.startsWith(`${ROUTES.ADMIN}/`)) return APP_ROLES.ADMIN;
  // The partner portal isn't an AppRole (it self-gates via useMyPartnerCenter) — never a `next` target.
  if (pathname === ROUTES.PARTNER || pathname.startsWith(`${ROUTES.PARTNER}/`)) return null;
  return APP_ROLES.CUSTOMER;
}

/**
 * The `?next=` deep-link carried through login (middleware.ts appends it on the redirect-to-login).
 * Returns `next` only when it is a validated same-origin relative path **and** the resolved session
 * actually holds the role that owns it (a customer's `next=/nurse/...` falls through) — otherwise
 * defers to `resolveRoleDestination`, which stays the single "which app" source of truth. Never
 * returns an absolute URL or a protocol-relative path — this is the one guard against `next`
 * becoming an open redirect.
 */
export function resolvePostLoginDestination(
  me: Pick<Me, 'roles'>,
  intendedRole: AppRole | undefined,
  next: string | null | undefined,
): string {
  if (next && isSafeRelativePath(next)) {
    const owner = appRoleForPath(next);
    if (owner && toAppRoles(me.roles).includes(owner)) return next;
  }
  return resolveRoleDestination(me, intendedRole);
}
