# Login Flow State Machine (Strict)

This document defines the canonical, deterministic login flow controller for the website.

Authoritative website contract:

- [`docs/architecture/website/WEBSITE_CONTRACT.md`](docs/architecture/website/WEBSITE_CONTRACT.md:1)

## 1) Core rule

Login flow logic MUST be deterministic.

The same inputs MUST produce the same state and the same next action.

## 2) State machine definition (strict)

### 2.1 State definitions

```typescript
enum LoginState {
  UNAUTHENTICATED = "UNAUTHENTICATED",
  AUTHENTICATED_WITH_PERMISSIONS = "AUTHENTICATED_WITH_PERMISSIONS", 
  AUTHENTICATED_WITHOUT_PERMISSIONS = "AUTHENTICATED_WITHOUT_PERMISSIONS",
  POST_AUTH_REDIRECT = "POST_AUTH_REDIRECT"
}
```

### 2.2 State transition table

| Current State | Session | ReturnTo | Next State | Action |
|---------------|---------|----------|------------|--------|
| INITIAL | null | any | UNAUTHENTICATED | Show login form |
| INITIAL | exists | '/dashboard' | AUTHENTICATED_WITH_PERMISSIONS | Redirect to dashboard |
| INITIAL | exists | NOT '/dashboard' | AUTHENTICATED_WITHOUT_PERMISSIONS | Show permission error |
| UNAUTHENTICATED | exists | any | POST_AUTH_REDIRECT | Redirect to returnTo |
| AUTHENTICATED_WITHOUT_PERMISSIONS | exists | any | POST_AUTH_REDIRECT | Redirect to returnTo |

### 2.3 Class-based controller

```typescript
class LoginFlowController {
  // Immutable state
  private readonly session: AuthSessionDTO | null;
  private readonly returnTo: string;
  
  // State machine
  private state: LoginState;
  
  constructor(session: AuthSessionDTO | null, returnTo: string) {
    this.session = session;
    this.returnTo = returnTo;
    this.state = this.determineInitialState();
  }
  
  private determineInitialState(): LoginState {
    if (!this.session) return LoginState.UNAUTHENTICATED;
    if (this.returnTo === '/dashboard') return LoginState.AUTHENTICATED_WITH_PERMISSIONS;
    return LoginState.AUTHENTICATED_WITHOUT_PERMISSIONS;
  }
  
  // Pure function - no side effects
  getState(): LoginState {
    return this.state;
  }
  
  // Pure function - returns action, does not execute
  getNextAction(): LoginAction {
    switch (this.state) {
      case LoginState.UNAUTHENTICATED:
        return { type: 'SHOW_LOGIN_FORM' };
      case LoginState.AUTHENTICATED_WITH_PERMISSIONS:
        return { type: 'REDIRECT', path: '/dashboard' };
      case LoginState.AUTHENTICATED_WITHOUT_PERMISSIONS:
        return { type: 'SHOW_PERMISSION_ERROR' };
      case LoginState.POST_AUTH_REDIRECT:
        return { type: 'REDIRECT', path: this.returnTo };
    }
  }
  
  // Transition called after authentication
  transitionToPostAuth(): void {
    if (this.session) {
      this.state = LoginState.POST_AUTH_REDIRECT;
    }
  }
}
```

## 3) Non-negotiable rules

1. The controller MUST be constructed from explicit inputs only.
2. The controller MUST NOT perform side effects.
3. Side effects (routing) MUST be executed outside the controller.
4. The controller MUST be unit-tested per transition.

## 4) Usage in login page (example)

```typescript
export default function LoginPage() {
  const router = useRouter();
  const searchParams = useSearchParams();
  const { session } = useAuth();
  
  const returnTo = searchParams.get('returnTo') ?? '/dashboard';
  
  // Create controller once
  const controller = useMemo(() => 
    new LoginFlowController(session, returnTo), 
    [session, returnTo]
  );
  
  // Get current state
  const state = controller.getState();
  const action = controller.getNextAction();
  
  // Execute action (only once)
  useEffect(() => {
    if (action.type === 'REDIRECT') {
      router.replace(action.path);
    }
  }, [action, router]);
  
  // Render based on state
  if (state === LoginState.UNAUTHENTICATED) {
    return <LoginForm />;
  }
  
  if (state === LoginState.AUTHENTICATED_WITHOUT_PERMISSIONS) {
    return <PermissionError returnTo={returnTo} />;
  }
  
  // Show loading while redirecting
  return <LoadingSpinner />;
}
```

This pattern ensures deterministic behavior and makes the flow testable.
