import type { Permission } from './permissions.js';

/**
 * The scopes an actor holds, resolved once per request from their role
 * assignments. A permission grants an *ability*; a scope bounds the *rows* that
 * ability may touch. Both are required — see ARCHITECTURE.md §5.
 */
export interface ActorScope {
  /** Colleges a Dean is assigned to. Empty for non-Deans. */
  readonly collegeIds: readonly string[];
  /**
   * Programs the actor is bound to. Usually derived from `collegeIds`, but a
   * Dean may also be assigned to individual programs across colleges.
   */
  readonly programIds: readonly string[];
  /** Section offerings a Faculty member teaches this term. */
  readonly sectionOfferingIds: readonly string[];
  /** The actor's own student record, when they are a student. */
  readonly studentId: string | null;
  /** The actor's own applicant record, when they applied through the portal. */
  readonly applicantProfileId: string | null;
  /** The actor's own faculty record, when they teach. */
  readonly facultyProfileId: string | null;
}

export const EMPTY_SCOPE: ActorScope = {
  collegeIds: [],
  programIds: [],
  sectionOfferingIds: [],
  studentId: null,
  applicantProfileId: null,
  facultyProfileId: null,
};

/**
 * Everything the authorization layer knows about the caller. Built by the
 * transport layer from the session and passed down into every service call.
 * Services never read cookies, headers or globals.
 */
export interface SecurityContext {
  readonly userId: string;
  readonly institutionId: string;
  readonly email: string;
  readonly displayName: string;
  /** Role keys held by the actor. Present for display and audit, not for logic. */
  readonly roles: readonly string[];
  /** Union of every permission granted by every role held. */
  readonly permissions: ReadonlySet<Permission>;
  /**
   * True when a role flagged `isSuperAdmin` is held. Short-circuits permission
   * checks; every such use is audited (SPEC.md §7).
   */
  readonly isSuperAdmin: boolean;
  readonly scope: ActorScope;
  /** Session identifier, recorded on audit entries so actions trace to a login. */
  readonly sessionId: string;
  /** Client IP, recorded on security events only. Treated as personal data. */
  readonly ipAddress: string | null;
  readonly userAgent: string | null;
}

/**
 * The unauthenticated caller. Used by public endpoints (the application wizard,
 * status lookup) so they share the same service signatures. It holds no
 * permissions, so any `authorize()` call against it fails.
 */
export function anonymousContext(institutionId: string, ipAddress: string | null = null): SecurityContext {
  return {
    userId: 'anonymous',
    institutionId,
    email: '',
    displayName: 'Anonymous',
    roles: [],
    permissions: new Set<Permission>(),
    isSuperAdmin: false,
    scope: EMPTY_SCOPE,
    sessionId: 'anonymous',
    ipAddress,
    userAgent: null,
  };
}

export function isAnonymous(ctx: SecurityContext): boolean {
  return ctx.userId === 'anonymous';
}
