# Code Style Guidelines

## General Principles

- **Readability First**: Write code that is easy to read and understand.
- **Consistency**: Follow the same patterns and conventions throughout the codebase.
- **Clean Code**: Avoid unnecessary complexity and keep functions small and focused.
- **SOLID Principles**: Follow SOLID design principles to create more maintainable, flexible, and scalable code.

## SOLID Design Principles

SOLID is an acronym for five design principles that help make software designs more understandable, flexible, and maintainable:

- **Single Responsibility Principle (SRP)**: A class should have only one reason to change, meaning it should have only one job or responsibility.

  ```typescript
  // Good - Each class has a single responsibility
  class UserAuthentication {
    authenticate(username: string, password: string): boolean {
      // Authentication logic
    }
  }

  class UserRepository {
    findById(id: string): User {
      // Database access logic
    }
  }

  // Bad - Class has multiple responsibilities
  class UserManager {
    authenticate(username: string, password: string): boolean {
      // Authentication logic
    }

    findById(id: string): User {
      // Database access logic
    }

    sendEmail(user: User, message: string): void {
      // Email sending logic
    }
  }
  ```

- **Open/Closed Principle (OCP)**: Software entities should be open for extension but closed for modification.

  ```typescript
  // Good - Open for extension
  interface PaymentProcessor {
    processPayment(amount: number): void;
  }

  class CreditCardProcessor implements PaymentProcessor {
    processPayment(amount: number): void {
      // Credit card processing logic
    }
  }

  class PayPalProcessor implements PaymentProcessor {
    processPayment(amount: number): void {
      // PayPal processing logic
    }
  }

  // New payment methods can be added without modifying existing code
  ```

- **Liskov Substitution Principle (LSP)**: Objects of a superclass should be replaceable with objects of subclasses without affecting the correctness of the program.

  ```typescript
  // Good - Derived classes can substitute base class
  class Rectangle {
    constructor(
      protected width: number,
      protected height: number,
    ) {}

    setWidth(width: number): void {
      this.width = width;
    }

    setHeight(height: number): void {
      this.height = height;
    }

    getArea(): number {
      return this.width * this.height;
    }
  }

  class Square extends Rectangle {
    constructor(size: number) {
      super(size, size);
    }

    // Preserve behavior when overriding methods
    setWidth(width: number): void {
      super.setWidth(width);
      super.setHeight(width);
    }

    setHeight(height: number): void {
      super.setWidth(height);
      super.setHeight(height);
    }
  }
  ```

- **Interface Segregation Principle (ISP)**: Clients should not be forced to depend on interfaces they do not use.

  ```typescript
  // Good - Segregated interfaces
  interface Printable {
    print(): void;
  }

  interface Scannable {
    scan(): void;
  }

  class AllInOnePrinter implements Printable, Scannable {
    print(): void {
      // Printing logic
    }

    scan(): void {
      // Scanning logic
    }
  }

  class BasicPrinter implements Printable {
    print(): void {
      // Printing logic
    }
  }

  // Bad - Fat interface
  interface OfficeMachine {
    print(): void;
    scan(): void;
    fax(): void;
    staple(): void;
  }

  // Classes must implement methods they don't need
  ```

- **Dependency Inversion Principle (DIP)**: High-level modules should not depend on low-level modules. Both should depend on abstractions.

  ```typescript
  // Good - Depending on abstractions
  interface Logger {
    log(message: string): void;
  }

  class ConsoleLogger implements Logger {
    log(message: string): void {
      console.log(message);
    }
  }

  class FileLogger implements Logger {
    log(message: string): void {
      // File logging logic
    }
  }

  class UserService {
    constructor(private logger: Logger) {}

    createUser(user: User): void {
      // Create user logic
      this.logger.log(`User created: ${user.name}`);
    }
  }

  // The UserService depends on the abstraction (Logger interface)
  // not on concrete implementations
  ```

Following these principles improves code quality by:

- Reducing coupling between components
- Making the system more modular and easier to maintain
- Facilitating testing and extension
- Promoting code reuse

## Extended Guidelines for Angular and TypeScript

This section extends the core code style principles with Angular-specific and advanced TypeScript best practices to ensure consistency and maintainability in our projects.

### Angular Enhancements

- **Change Detection**: Use the OnPush strategy by default for better performance.
- **Lifecycle Hooks**: Explicitly implement Angular lifecycle interfaces (OnInit, OnDestroy, etc.).
- **Template Management**: Keep templates concise and use the async pipe to handle observables.
- **Component Structure**: Follow best practices for component modularization to enhance readability and testability.
- **Naming Conventions**: Follow Angular's official naming conventions for selectors, files, and component classes.
- **File Organization**: Structure files according to features and follow the recommended folder structure.
- **Control Flow**: Use modern control flow syntax (@if, @for) instead of structural directives (*ngIf, *ngFor).
- **Signals**: Prefer signals over RxJS for simpler state management within components.
- **Standalone by Default**: Components and directives are standalone by default. The `standalone: true` flag is unnecessary. Only specify `standalone: false` when a component or directive explicitly needs to be part of an NgModule.

### TypeScript Enhancements

- **Strict Type Checking**: Enable strict mode (`strict: true`) and avoid excessive use of `any`.
- **Interfaces vs. Types**: Prefer interfaces for object definitions and use type aliases for unions and intersections.
- **Generics**: Use meaningful type parameter names and constrain generics when applicable.
- **Documentation**: Employ JSDoc comments for functions and generic parameters to improve code clarity.
- **Non-Nullability**: Use the non-null assertion operator (!) sparingly and only when you're certain a value cannot be null.
- **Type Guards**: Implement custom type guards to handle type narrowing safely.
- **Immutability**: Favor immutable data structures and use readonly modifiers when applicable.
- **Exhaustiveness Checking**: Use exhaustiveness checking for switch statements handling union types.

## TypeScript Guidelines

- **Strict Typing**:

  - Enable `strict: true` in tsconfig.json
  - Avoid `any` unless absolutely necessary
  - Use `unknown` instead of `any` when type is truly unknown
  - Always specify return types for functions
  - Use type inference for variable declarations where types are obvious

  ```typescript
  // Good
  const user: User = getUserById('123');
  const items = ['apple', 'banana']; // Type inference is fine here

  // Bad
  const user: any = getUserById('123');
  const items: string[] = ['apple', 'banana']; // Unnecessary type annotation
  ```

- **Interfaces and Types**:

  - Prefer `interface` over `type` for object definitions
  - Use `type` for unions, intersections, and mapped types
  - Follow Angular's naming convention: Don't prefix interfaces with 'I' (use `ComponentProps` not `IComponentProps`)
  - Extend interfaces instead of repeating properties
  - Use readonly modifiers where appropriate

  ```typescript
  // Good
  interface BaseProps {
    readonly id: string;
    name: string;
  }

  interface UserProps extends BaseProps {
    email: string;
  }

  type ValidationResult = 'success' | 'error' | 'pending';

  // Bad
  type UserProps = {
    id: string;
    name: string;
    email: string;
  };
  ```

- **Enums and Constants**:

  - Prefer this order of implementation (from most to least preferred):

    1. `const enum` for better compile-time performance
    2. Object literals with `as const` for runtime flexibility
    3. Regular `enum` only when necessary for runtime access

  - **When to use each approach**:
    - Use `const enum` for internal application enumerations that don't need runtime access
    - Use `const object as const` when values need to be inspected at runtime or exported in an API
    - Use regular `enum` only when runtime enumeration object access is required

  ```typescript
  // Good - const enum (preferred for most cases)
  // Advantages: Tree-shakable, type-safe, disappears at compile time
  export const enum ConstEnumStates {
    NotSet = 'not-set',
    Success = 'success',
  }

  // Good - const object with 'as const' assertion
  // Advantages: Runtime accessible, works well with API boundaries
  export const ConstStates = {
    NotSet: 'not-set',
    Success: 'success',
  } as const;

  // Types can be extracted from const objects
  type ConstStatesType = (typeof ConstStates)[keyof typeof ConstStates];

  // Least preferred - regular enum
  // Only use when you need the enum object at runtime
  export enum States {
    NotSet = 'not-set',
    Success = 'success',
  }
  ```

  - Use union types as an alternative for simple string literals

  ```typescript
  // Alternative approach using union types
  export type StatusType = 'not-set' | 'success';
  ```

- **Functions and Methods**:

  - Always export functions as arrow function expressions (const) instead of function declarations
  - Use arrow functions for callbacks and class methods
  - Explicitly type parameters and return values
  - Keep functions pure when possible
  - Use function overloads for complex type scenarios
  - Document complex functions with JSDoc

  ```typescript
  // Good
  /**
   * Fetches a user by ID and transforms it to the required format
   * @param id - The user's unique identifier
   * @param includeDetails - Whether to include additional user details
   */
  export const getUser = (id: string, includeDetails = false): Promise<User> => {
    // ...implementation
  };

  // Bad
  export function getUser(id) {
    // ...implementation
  }
  ```

  // Reason for using arrow function expressions:
  // 1. More consistent with modern JavaScript practices
  // 2. Easier to mock in unit tests
  // 3. Avoids 'this' binding issues

- **Generics**:
  - Use meaningful type parameter names (e.g., `T` for type, `K` for key)
  - Constrain generic types when possible using `extends`
  - Document generic parameters using JSDoc

Example:

```typescript
// Good
interface UserProps {
  id: string;
  name: string;
}

interface AdminProps extends UserProps {
  permissions: string[];
}

const enum UserRole {
  Admin = 'ADMIN',
  User = 'USER',
}

const getUser = <T extends UserProps>(id: string): Promise<T> => {
  // ...implementation
};

// Bad
type User = {
  id: any;
  name: any;
};

function getUser(id) {
  // ...implementation
}
```

## Angular-Specific Guidelines

- **Components**:

  - Use OnPush change detection strategy by default
  - Implement lifecycle hooks interfaces explicitly
  - Keep templates small and focused
  - Use async pipe instead of manual subscription management

  ```typescript
  // Good
  @Component({
    selector: 'app-user-list',
    changeDetection: ChangeDetectionStrategy.OnPush,
  })
  export class UserListComponent implements OnInit, OnDestroy {
    users$ = this.userService.getUsers().pipe(shareReplay(1));
  }

  // Bad
  @Component({
    selector: 'app-user-list',
  })
  export class UserListComponent {
    users: User[] = [];
    subscription: Subscription;

    ngOnInit() {
      this.subscription = this.userService
        .getUsers()
        .subscribe((users) => (this.users = users));
    }
  }
  ```

- **Templates and Control Flow**:

  - Use modern control flow syntax (`@if`, `@for`, `@switch`) instead of structural directives (`*ngIf`, `*ngFor`, `*ngSwitch`).

  ```html
  <!-- Good - Modern control flow syntax -->
  <div>
    @if (user) {
    <h1>Welcome, {{ user.name }}!</h1>
    } @else if (isLoading) {
    <h1>Loading user data...</h1>
    } @else {
    <h1>Please log in</h1>
    }

    <ul>
      @for (item of items; track item.id) {
      <li>{{ item.name }}</li>
      } @empty {
      <li>No items available</li>
      }
    </ul>

    @switch (userRole) { @case ('admin') {
    <app-admin-dashboard />
    } @case ('manager') {
    <app-manager-dashboard />
    } @default {
    <app-user-dashboard />
    } }
  </div>

  <!-- Bad - Old structural directives -->
  <div>
    <h1 *ngIf="user">Welcome, {{ user.name }}!</h1>
    <h1 *ngIf="!user && isLoading">Loading user data...</h1>
    <h1 *ngIf="!user && !isLoading">Please log in</h1>

    <ul>
      <li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
      <li *ngIf="!items || items.length === 0">No items available</li>
    </ul>

    <app-admin-dashboard *ngIf="userRole === 'admin'"></app-admin-dashboard>
    <app-manager-dashboard
      *ngIf="userRole === 'manager'"
    ></app-manager-dashboard>
    <app-user-dashboard
      *ngIf="userRole !== 'admin' && userRole !== 'manager'"
    ></app-user-dashboard>
  </div>
  ```

  - When using `@for`, always specify the `track` expression to optimize rendering performance:
    - Use a unique identifier property (like `id` or `uuid`) when available
    - Only use `$index` for static collections that never change
    - Avoid using non-unique properties that could result in DOM mismatches
  - Leverage contextual variables in `@for` blocks:
    - `$index` - Current item index
    - `$first` - Boolean indicating if this is the first item
    - `$last` - Boolean indicating if this is the last item
    - `$even` - Boolean indicating if this index is even
    - `$odd` - Boolean indicating if this index is odd
    - `$count` - Total number of items in the collection

  ```html
  <!-- Good - Using contextual variables -->
  @for (item of items; track item.id; let i = $index, isLast = $last) {
  <li [class.last-item]="isLast">{{ i + 1 }}. {{ item.name }}</li>
  }
  ```

  - Use the `@empty` block with `@for` to handle empty collections gracefully
  - Store conditional expression results in variables for clearer templates:

  ```html
  <!-- Good - Storing expression result in variable -->
  @if (user.permissions.canEditSettings; as canEdit) {
  <button [disabled]="!canEdit">Edit Settings</button>
  }
  ```

## Error Logging

Proper error logging is critical for diagnosing issues in production applications. Always use the `@isa/core/logging` module for logging throughout the application.

### Basic Logging Guidelines

- Always use the appropriate log level for the situation:
  - `trace` - For fine-grained debugging information
  - `debug` - For development-time debugging information
  - `info` - For general runtime information
  - `warn` - For potential issues that don't interrupt operation
  - `error` - For errors that affect functionality

### Setting Up Logging

Use the logger factory function to create loggers in your components and services:

```typescript
import { logger } from '@isa/core/logging';

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
})
export class ExampleComponent implements OnInit {
  // Create a private logger instance
  #logger = logger();
  
  constructor(private userService: UserService) {}
  
  ngOnInit(): void {
    this.#logger.info('Component initialized');
  }
}
```

### Context-Aware Logging

Always provide relevant context when logging:

```typescript
// Good - With detailed context
this.#logger.error(
  'Failed to load user data', 
  error, 
  { userId: '123', attempt: 2, source: 'UserProfileComponent' }
);

// Bad - No context for troubleshooting
this.#logger.error('Failed to load user data');
```

### Component and Service Level Context

Use the `provideLoggerContext` to set component-level context:

```typescript
@Component({
  selector: 'app-user-profile',
  templateUrl: './user-profile.component.html',
  providers: [
    provideLoggerContext({ 
      component: 'UserProfile',
      section: 'Account'
    })
  ]
})
export class UserProfileComponent {
  #logger = logger();
  
  // All logs from this component will include the context
  updateProfile(): void {
    this.#logger.info('Updating user profile');
    // Log output will include { component: 'UserProfile', section: 'Account' }
  }
}
```

### Error Handling Best Practices

1. **Always log the original Error object**:

```typescript
// Good - Includes the original error with stack trace
try {
  // risky operation
} catch (error) {
  this.#logger.error('Operation failed', error, { context: 'additional data' });
}

// Bad - Loses the stack trace and error details
try {
  // risky operation
} catch (error) {
  this.#logger.error(`Operation failed: ${error}`);
}
```

2. **Structure error handling with contextual information**:

```typescript
saveData(data: UserData): void {
  this.userService.save(data).pipe(
    catchError((error) => {
      this.#logger.error(
        'Failed to save user data', 
        error,
        {
          userId: data.id,
          dataSize: JSON.stringify(data).length,
          operation: 'saveData'
        }
      );
      
      // Handle the error appropriately
      return throwError(() => new Error('Failed to save data'));
    })
  ).subscribe();
}
```

3. **Use log levels appropriately**:

```typescript
// Debug information during development
this.#logger.debug('Processing data batch', { batchSize: items.length });

// General information during runtime
this.#logger.info('User logged in', { userId: user.id });

// Potential issues that don't break functionality
this.#logger.warn('API response slow', { responseTime: '2500ms', endpoint: '/users' });

// Errors that affect functionality
this.#logger.error('Payment processing failed', error, { orderId: order.id });
```

4. **In RxJS operators, use tap for logging**:

```typescript
return this.http.get<User[]>('/api/users').pipe(
  tap({
    next: (users) => this.#logger.info('Users loaded', { count: users.length }),
    error: (error) => this.#logger.error('Failed to load users', error)
  }),
  catchError((error) => {
    this.#logger.error('Error in users API call', error, { retry: true });
    return this.fallbackUserService.getUsers();
  })
);
```

5. **Log lifecycle events when relevant**:

```typescript
export class ImportantComponent implements OnInit, OnDestroy {
  #logger = logger();
  
  ngOnInit(): void {
    this.#logger.debug('Component initialized');
  }
  
  ngOnDestroy(): void {
    this.#logger.debug('Component destroyed');
  }
}
```

### Performance Considerations

- Avoid expensive operations in log messages in production:

```typescript
// Bad - Performs expensive operation even if debug level is disabled
this.#logger.debug('Data state:', JSON.stringify(this.largeDataObject));

// Good - Only performs expensive operation if needed
if (isDevMode()) {
  this.#logger.debug('Data state:', { data: this.largeDataObject });
}
```

- Consider log level in production environments:

```typescript
// In app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    // Other providers...
    provideLogging(
      withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn),
      withSink(ConsoleLogSink)
    )
  ]
};
```

## Project-Specific Preferences

- **Frameworks**: Follow best practices for Nx, Angular, date-fns, Ngrx, RxJs and Zod.
- **Testing**: Use Jest with Spectator for unit tests and follow the Arrange-Act-Assert pattern.
- **File Naming**:

  - Use kebab-case for filenames (e.g., `my-component.ts`).
  - Follow a pattern that describes the symbol's feature then its type: `feature.type.ts`

    ```
    // Good examples
    user.service.ts
    auth.guard.ts
    product-list.component.ts
    order.model.ts

    // Bad examples
    service-user.ts
    userService.ts
    ```

- **Comments**: Use JSDoc for documenting functions, classes, and modules.

## Formatting

- **Indentation**: Use 2 spaces for indentation.
- **Line Length**: Limit lines to 80 characters where possible.
- **Semicolons**: Always use semicolons.
- **Quotes**: Use single quotes for strings, except when using template literals.

## Linting and Tools

- Use ESLint with the recommended TypeScript and Nx configurations.
- Prettier should be used for consistent formatting.

## References

- [Angular Style Guide](https://angular.dev/style-guide) - Official Angular style guide with best practices for Angular development
- [Angular Control Flow](https://angular.dev/guide/templates/control-flow) - Official Angular documentation on the new control flow syntax (@if, @for, @switch)
- [TypeScript Style Guide](https://ts.dev/style/) - TypeScript community style guide with patterns and practices
- [SOLID Design Principles](https://en.wikipedia.org/wiki/SOLID) - Wikipedia article explaining the SOLID principles in object-oriented design
- [Clean Code](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) - Robert C. Martin's seminal book on writing clean, maintainable code
