# @isa/utils/ean-validation

Lightweight Angular utility library for validating EAN (European Article Number) barcodes with reactive forms integration and standalone validation functions.

## Overview

The EAN Validation library provides tools for validating 13-digit EAN barcodes in Angular applications. It includes both an Angular Forms validator for reactive form validation and a standalone utility function for programmatic validation. The library uses a simple regex-based approach for fast, efficient validation without external dependencies.

## Table of Contents

- [Features](#features)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [API Reference](#api-reference)
- [Usage Examples](#usage-examples)
- [EAN Format](#ean-format)
- [Architecture Notes](#architecture-notes)
- [Testing](#testing)
- [Dependencies](#dependencies)

## Features

- **Angular Forms validator** - `eanValidator` for reactive form controls
- **Standalone validation** - `isEan()` utility function for programmatic checks
- **Regex-based validation** - Fast, efficient 13-digit EAN validation
- **Zero dependencies** - No external libraries required
- **Type-safe** - Full TypeScript support
- **Null-safe** - Graceful handling of undefined/null values
- **Lightweight** - Minimal bundle size impact
- **Framework agnostic core** - Validation logic can be used outside Angular

## Quick Start

### 1. Reactive Form Validation

```typescript
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { eanValidator } from '@isa/utils/ean-validation';

@Component({
  selector: 'app-product-form',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="productForm">
      <label>
        EAN:
        <input formControlName="ean" type="text" />
      </label>

      @if (eanControl.errors?.['invalidEan']) {
        <div class="error">
          Please enter a valid 13-digit EAN
        </div>
      }
    </form>
  `
})
export class ProductFormComponent {
  productForm = new FormGroup({
    ean: new FormControl('', [eanValidator])
  });

  get eanControl() {
    return this.productForm.get('ean')!;
  }
}
```

### 2. Programmatic Validation

```typescript
import { Component } from '@angular/core';
import { isEan } from '@isa/utils/ean-validation';

@Component({
  selector: 'app-barcode-scanner',
  template: `
    <button (click)="validateBarcode('1234567890123')">
      Validate Barcode
    </button>
  `
})
export class BarcodeScannerComponent {
  validateBarcode(code: string): void {
    if (isEan(code)) {
      console.log('Valid EAN:', code);
      // Process valid EAN
    } else {
      console.error('Invalid EAN:', code);
      // Handle invalid EAN
    }
  }
}
```

### 3. Combined Usage

```typescript
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { eanValidator, isEan } from '@isa/utils/ean-validation';

@Component({
  selector: 'app-product-search',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <input
      [formControl]="searchControl"
      placeholder="Enter EAN"
      (keyup.enter)="search()"
    />

    <button (click)="search()" [disabled]="!canSearch()">
      Search
    </button>
  `
})
export class ProductSearchComponent {
  searchControl = new FormControl('', [eanValidator]);

  canSearch(): boolean {
    const value = this.searchControl.value;
    return !!value && isEan(value);
  }

  search(): void {
    if (this.canSearch()) {
      const ean = this.searchControl.value!;
      console.log('Searching for:', ean);
      // Perform search
    }
  }
}
```

## Core Concepts

### EAN (European Article Number)

EAN is a 13-digit barcode standard used internationally for product identification:

```
1234567890123
└───┬────┘││└─ Check digit
    │     ││
    │     │└── Product code
    │     └─── Manufacturer code
    └───────── Country/GS1 prefix
```

### Validation Rules

The library validates EANs based on these criteria:

1. **Length**: Must be exactly 13 digits
2. **Content**: Must contain only numeric characters (0-9)
3. **Format**: No spaces, dashes, or other separators allowed

**Valid EANs:**
```typescript
'1234567890123' ✓ (13 digits, all numeric)
'0000000000000' ✓ (13 digits, all numeric)
'9999999999999' ✓ (13 digits, all numeric)
```

**Invalid EANs:**
```typescript
'123456789012'   ✗ (12 digits - too short)
'12345678901234' ✗ (14 digits - too long)
'123456789012A'  ✗ (contains letter)
'1234-5678-9012' ✗ (contains dashes)
' 1234567890123' ✗ (contains whitespace)
''               ✗ (empty string - via isEan())
undefined        ✗ (undefined - via isEan())
```

### Regex Pattern

The validation uses a simple regex pattern:

```typescript
const EAN_REGEX = /^[0-9]{13}$/;
```

**Pattern breakdown:**
- `^` - Start of string
- `[0-9]` - Any digit from 0 to 9
- `{13}` - Exactly 13 times
- `$` - End of string

## API Reference

### eanValidator

Angular Forms validator function for validating EAN input.

**Type:** `ValidatorFn`

**Signature:**
```typescript
eanValidator(control: AbstractControl): ValidationErrors | null
```

**Parameters:**
- `control: AbstractControl` - The form control to validate

**Returns:**
- `null` - If the value is valid or empty/null
- `{ invalidEan: true }` - If the value is invalid

**Validation Behavior:**
- **Empty values**: Returns `null` (considered valid - use `Validators.required` for required fields)
- **Valid EAN**: Returns `null`
- **Invalid EAN**: Returns `{ invalidEan: true }`

**Example:**
```typescript
import { FormControl } from '@angular/forms';
import { eanValidator } from '@isa/utils/ean-validation';

const eanControl = new FormControl('', [eanValidator]);

eanControl.setValue('1234567890123');
console.log(eanControl.valid); // true

eanControl.setValue('123');
console.log(eanControl.errors); // { invalidEan: true }

eanControl.setValue('');
console.log(eanControl.valid); // true (empty is valid)
```

### isEan

Standalone utility function for programmatic EAN validation.

**Signature:**
```typescript
isEan(value: string | undefined): boolean
```

**Parameters:**
- `value: string | undefined` - The value to validate

**Returns:**
- `true` - If the value is a valid 13-digit EAN
- `false` - If the value is invalid, undefined, null, or empty

**Validation Behavior:**
- **undefined/null**: Returns `false`
- **Empty string**: Returns `false`
- **Valid EAN**: Returns `true`
- **Invalid EAN**: Returns `false`

**Example:**
```typescript
import { isEan } from '@isa/utils/ean-validation';

isEan('1234567890123'); // true
isEan('123');           // false
isEan('');              // false
isEan(undefined);       // false
isEan(null);            // false (TypeScript allows via type coercion)
```

### EAN_REGEX

Regular expression constant for EAN validation.

**Type:** `RegExp`

**Value:** `/^[0-9]{13}$/`

**Note:** This is an internal constant. Prefer using `eanValidator` or `isEan()` for validation.

**Example:**
```typescript
import { EAN_REGEX } from '@isa/utils/ean-validation';

// Direct regex usage (not recommended)
const isValid = EAN_REGEX.test('1234567890123');
console.log(isValid); // true

// Prefer using isEan() instead
const isValidPreferred = isEan('1234567890123');
```

## Usage Examples

### Basic Form Validation

```typescript
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { eanValidator } from '@isa/utils/ean-validation';

@Component({
  selector: 'app-product-form',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <div class="form-field">
        <label for="ean">Product EAN*</label>
        <input
          id="ean"
          formControlName="ean"
          type="text"
          maxlength="13"
          placeholder="1234567890123"
        />

        @if (ean.touched && ean.errors) {
          <div class="errors">
            @if (ean.errors['required']) {
              <p class="error">EAN is required</p>
            }
            @if (ean.errors['invalidEan']) {
              <p class="error">EAN must be exactly 13 digits</p>
            }
          </div>
        }
      </div>

      <button type="submit" [disabled]="!form.valid">
        Save Product
      </button>
    </form>
  `
})
export class ProductFormComponent {
  form = new FormGroup({
    ean: new FormControl('', [
      Validators.required,
      eanValidator
    ])
  });

  get ean() {
    return this.form.get('ean')!;
  }

  onSubmit(): void {
    if (this.form.valid) {
      const productData = this.form.value;
      console.log('Saving product:', productData);
      // Save logic
    }
  }
}
```

### Barcode Scanner Integration

```typescript
import { Component, inject, signal } from '@angular/core';
import { isEan } from '@isa/utils/ean-validation';

interface ScanResult {
  code: string;
  valid: boolean;
  timestamp: Date;
}

@Component({
  selector: 'app-barcode-scanner',
  template: `
    <div class="scanner">
      <h2>Barcode Scanner</h2>

      <button (click)="startScanning()">
        Start Scanning
      </button>

      <div class="scan-history">
        <h3>Scan History</h3>
        @for (scan of scanHistory(); track scan.timestamp) {
          <div
            class="scan-result"
            [class.valid]="scan.valid"
            [class.invalid]="!scan.valid"
          >
            <span>{{ scan.code }}</span>
            <span>{{ scan.valid ? '✓ Valid' : '✗ Invalid' }}</span>
            <span>{{ scan.timestamp | date: 'short' }}</span>
          </div>
        }
      </div>
    </div>
  `
})
export class BarcodeScannerComponent {
  scanHistory = signal<ScanResult[]>([]);

  startScanning(): void {
    // Simulated barcode scan
    this.processScan('1234567890123');
  }

  processScan(code: string): void {
    const valid = isEan(code);

    this.scanHistory.update(history => [
      {
        code,
        valid,
        timestamp: new Date()
      },
      ...history
    ]);

    if (valid) {
      console.log('Valid EAN scanned:', code);
      // Process valid EAN
      this.lookupProduct(code);
    } else {
      console.error('Invalid EAN scanned:', code);
      // Show error message
    }
  }

  lookupProduct(ean: string): void {
    // Product lookup logic
  }
}
```

### Search with Validation

```typescript
import { Component, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { eanValidator, isEan } from '@isa/utils/ean-validation';

@Component({
  selector: 'app-product-search',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <div class="search-container">
      <input
        [formControl]="searchControl"
        type="text"
        placeholder="Enter 13-digit EAN"
        maxlength="13"
        (keyup.enter)="search()"
      />

      <button
        (click)="search()"
        [disabled]="!isValidEan()"
      >
        Search
      </button>

      @if (searchControl.touched && searchControl.errors?.['invalidEan']) {
        <p class="error">
          Please enter a valid 13-digit EAN
        </p>
      }

      @if (searchResult()) {
        <div class="result">
          <h3>Product Found</h3>
          <p>EAN: {{ searchResult()!.ean }}</p>
          <p>Name: {{ searchResult()!.name }}</p>
        </div>
      }

      @if (notFound()) {
        <p class="error">No product found with EAN: {{ lastSearch() }}</p>
      }
    </div>
  `
})
export class ProductSearchComponent {
  searchControl = new FormControl('', [eanValidator]);

  searchResult = signal<{ ean: string; name: string } | null>(null);
  notFound = signal(false);
  lastSearch = signal('');

  isValidEan(): boolean {
    const value = this.searchControl.value;
    return !!value && isEan(value);
  }

  search(): void {
    if (!this.isValidEan()) return;

    const ean = this.searchControl.value!;
    this.lastSearch.set(ean);

    // Simulated product lookup
    const product = this.mockProductLookup(ean);

    if (product) {
      this.searchResult.set(product);
      this.notFound.set(false);
    } else {
      this.searchResult.set(null);
      this.notFound.set(true);
    }
  }

  mockProductLookup(ean: string): { ean: string; name: string } | null {
    // Mock implementation
    return { ean, name: `Product ${ean}` };
  }
}
```

### Batch Validation

```typescript
import { Component, signal } from '@angular/core';
import { isEan } from '@isa/utils/ean-validation';

interface ValidationResult {
  ean: string;
  valid: boolean;
}

@Component({
  selector: 'app-batch-validator',
  template: `
    <div class="batch-validator">
      <h2>Batch EAN Validator</h2>

      <textarea
        [value]="inputEans()"
        (input)="inputEans.set($any($event.target).value)"
        placeholder="Enter EANs (one per line)"
        rows="10"
      ></textarea>

      <button (click)="validate()">Validate All</button>

      @if (results().length > 0) {
        <div class="results">
          <h3>Validation Results</h3>
          <p>
            Valid: {{ validCount() }} / {{ results().length }}
          </p>

          <table>
            <thead>
              <tr>
                <th>EAN</th>
                <th>Status</th>
              </tr>
            </thead>
            <tbody>
              @for (result of results(); track result.ean) {
                <tr [class.valid]="result.valid" [class.invalid]="!result.valid">
                  <td>{{ result.ean }}</td>
                  <td>{{ result.valid ? '✓ Valid' : '✗ Invalid' }}</td>
                </tr>
              }
            </tbody>
          </table>
        </div>
      }
    </div>
  `
})
export class BatchValidatorComponent {
  inputEans = signal('');
  results = signal<ValidationResult[]>([]);

  validCount = signal(0);

  validate(): void {
    const eans = this.inputEans()
      .split('\n')
      .map(line => line.trim())
      .filter(line => line.length > 0);

    const results: ValidationResult[] = eans.map(ean => ({
      ean,
      valid: isEan(ean)
    }));

    this.results.set(results);
    this.validCount.set(results.filter(r => r.valid).length);
  }
}
```

### Dynamic Form with Multiple EANs

```typescript
import { Component } from '@angular/core';
import { FormArray, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { eanValidator } from '@isa/utils/ean-validation';

@Component({
  selector: 'app-multi-ean-form',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <h3>Product EANs</h3>

      <div formArrayName="eans">
        @for (control of eanControls.controls; track $index) {
          <div class="ean-field">
            <input
              [formControlName]="$index"
              type="text"
              maxlength="13"
              placeholder="1234567890123"
            />

            @if (control.touched && control.errors?.['invalidEan']) {
              <span class="error">Invalid EAN</span>
            }

            <button type="button" (click)="removeEan($index)">
              Remove
            </button>
          </div>
        }
      </div>

      <button type="button" (click)="addEan()">
        Add EAN
      </button>

      <button type="submit" [disabled]="!form.valid">
        Save
      </button>
    </form>
  `
})
export class MultiEanFormComponent {
  form = new FormGroup({
    eans: new FormArray([
      new FormControl('', [eanValidator])
    ])
  });

  get eanControls() {
    return this.form.get('eans') as FormArray;
  }

  addEan(): void {
    this.eanControls.push(
      new FormControl('', [eanValidator])
    );
  }

  removeEan(index: number): void {
    this.eanControls.removeAt(index);
  }

  onSubmit(): void {
    if (this.form.valid) {
      const eans = this.eanControls.value;
      console.log('Saving EANs:', eans);
    }
  }
}
```

### Type Guard Usage

```typescript
import { Component } from '@angular/core';
import { isEan } from '@isa/utils/ean-validation';

interface Product {
  id: number;
  name: string;
  ean?: string; // Optional EAN
}

@Component({
  selector: 'app-product-list',
  template: `
    <div class="products">
      @for (product of products; track product.id) {
        <div class="product">
          <h3>{{ product.name }}</h3>

          @if (hasValidEan(product)) {
            <p>EAN: {{ product.ean }}</p>
          } @else {
            <p class="warning">No valid EAN</p>
          }
        </div>
      }
    </div>
  `
})
export class ProductListComponent {
  products: Product[] = [
    { id: 1, name: 'Product A', ean: '1234567890123' },
    { id: 2, name: 'Product B', ean: '123' }, // Invalid
    { id: 3, name: 'Product C' }, // Missing
  ];

  hasValidEan(product: Product): boolean {
    return isEan(product.ean);
  }
}
```

## EAN Format

### Standard Format

**EAN-13 Structure:**
```
┌──────────────────────────────────┐
│     1  2  3  4  5  6  7  8  9  0  1  2  3     │
│     └──┬──┘  └───────┬────────┘  │     │     │
│        │             │            │     │     │
│     Prefix      Manufacturer    Product Check │
│                  Code            Code   Digit │
└──────────────────────────────────┘
```

### Common Prefixes

| Prefix | Region/Type |
|--------|-------------|
| 000-019 | GS1 US |
| 020-029 | Restricted distribution |
| 030-039 | GS1 US |
| 040-049 | Restricted distribution |
| 050-059 | GS1 US reserved |
| 060-139 | GS1 US |
| 200-299 | Restricted distribution |
| 300-379 | GS1 France |
| 380 | GS1 Bulgaria |
| 383 | GS1 Slovenia |
| 385 | GS1 Croatia |
| 400-440 | GS1 Germany |
| 450-459, 490-499 | GS1 Japan |
| 460-469 | GS1 Russia |
| 470 | GS1 Kyrgyzstan |
| 471 | GS1 Taiwan |
| 474 | GS1 Estonia |
| 475 | GS1 Latvia |
| 476 | GS1 Azerbaijan |
| 477 | GS1 Lithuania |
| 478 | GS1 Uzbekistan |
| 479 | GS1 Sri Lanka |
| 480 | GS1 Philippines |
| 481 | GS1 Belarus |
| 482 | GS1 Ukraine |
| 484 | GS1 Moldova |
| 485 | GS1 Armenia |
| 486 | GS1 Georgia |
| 487 | GS1 Kazakhstan |
| 489 | GS1 Hong Kong |
| 500-509 | GS1 UK |
| 520-521 | GS1 Association Greece |
| 528 | GS1 Lebanon |
| 529 | GS1 Cyprus |
| 530 | GS1 Albania |
| 531 | GS1 Macedonia |
| 535 | GS1 Malta |
| 539 | GS1 Ireland |
| 540-549 | GS1 Belgium & Luxembourg |
| 560 | GS1 Portugal |
| 569 | GS1 Iceland |
| 570-579 | GS1 Denmark |
| 590 | GS1 Poland |
| 594 | GS1 Romania |
| 599 | GS1 Hungary |
| 600-601 | GS1 South Africa |
| 603 | GS1 Ghana |
| 604 | GS1 Senegal |
| 608 | GS1 Bahrain |
| 609 | GS1 Mauritius |
| 611 | GS1 Morocco |
| 613 | GS1 Algeria |
| 615 | GS1 Nigeria |
| 616 | GS1 Kenya |
| 618 | GS1 Ivory Coast |
| 619 | GS1 Tunisia |
| 620 | GS1 Tanzania |
| 621 | GS1 Syria |
| 622 | GS1 Egypt |
| 623 | GS1 Brunei |
| 624 | GS1 Libya |
| 625 | GS1 Jordan |
| 626 | GS1 Iran |
| 627 | GS1 Kuwait |
| 628 | GS1 Saudi Arabia |
| 629 | GS1 Emirates |
| 640-649 | GS1 Finland |
| 690-699 | GS1 China |
| 700-709 | GS1 Norway |
| 729 | GS1 Israel |
| 730-739 | GS1 Sweden |
| 740 | GS1 Guatemala |
| 741 | GS1 El Salvador |
| 742 | GS1 Honduras |
| 743 | GS1 Nicaragua |
| 744 | GS1 Costa Rica |
| 745 | GS1 Panama |
| 746 | GS1 Dominican Republic |
| 750 | GS1 Mexico |
| 754-755 | GS1 Canada |
| 759 | GS1 Venezuela |
| 760-769 | GS1 Switzerland |
| 770-771 | GS1 Colombia |
| 773 | GS1 Uruguay |
| 775 | GS1 Peru |
| 777 | GS1 Bolivia |
| 778-779 | GS1 Argentina |
| 780 | GS1 Chile |
| 784 | GS1 Paraguay |
| 786 | GS1 Ecuador |
| 789-790 | GS1 Brazil |
| 800-839 | GS1 Italy |
| 840-849 | GS1 Spain |
| 850 | GS1 Cuba |
| 858 | GS1 Slovakia |
| 859 | GS1 Czech |
| 860 | GS1 Serbia |
| 865 | GS1 Mongolia |
| 867 | GS1 North Korea |
| 868-869 | GS1 Turkey |
| 870-879 | GS1 Netherlands |
| 880 | GS1 South Korea |
| 884 | GS1 Cambodia |
| 885 | GS1 Thailand |
| 888 | GS1 Singapore |
| 890 | GS1 India |
| 893 | GS1 Vietnam |
| 896 | GS1 Pakistan |
| 899 | GS1 Indonesia |
| 900-919 | GS1 Austria |
| 930-939 | GS1 Australia |
| 940-949 | GS1 New Zealand |
| 950 | GS1 Global Office |
| 951 | GS1 Global Office (EPCglobal) |
| 955 | GS1 Malaysia |
| 958 | GS1 Macau |
| 960-969 | GS1 Global Office (GTIN-8) |
| 977 | Serial publications (ISSN) |
| 978-979 | Bookland (ISBN) |
| 980 | Refund receipts |
| 981-984 | GS1 coupon identification |
| 990-999 | GS1 coupon identification |

**Note:** This library validates format only (13 digits), not check digit accuracy or prefix validity.

## Architecture Notes

### Design Patterns

#### Validator Function Pattern

The `eanValidator` follows Angular's `ValidatorFn` pattern:

```typescript
export const eanValidator: ValidatorFn = (
  control: AbstractControl
): ValidationErrors | null => {
  const value = control.value;
  if (value && !EAN_REGEX.test(value)) {
    return { invalidEan: true };
  }
  return null;
};
```

**Benefits:**
- Composable with other validators
- Reusable across multiple form controls
- Type-safe with TypeScript
- Follows Angular conventions

#### Pure Function Pattern

The `isEan` function is a pure function with no side effects:

```typescript
export const isEan = (value: string | undefined): boolean => {
  if (!value) {
    return false;
  }
  return EAN_REGEX.test(value);
};
```

**Benefits:**
- Predictable - same input always produces same output
- Testable - easy to unit test
- Framework agnostic - can be used outside Angular
- No dependencies - doesn't rely on external state

### Performance Considerations

1. **Regex Performance** - Simple regex pattern for O(n) validation
2. **No External Dependencies** - Minimal bundle size impact
3. **Null-Safe** - Early returns for undefined/null values
4. **Reusable Regex** - Single compiled regex constant

### Future Enhancements

Potential improvements identified:

1. **Check Digit Validation** - Validate the EAN-13 check digit algorithm
2. **EAN-8 Support** - Support for 8-digit EAN format
3. **UPC Support** - Support for 12-digit UPC format
4. **Prefix Validation** - Validate GS1 country/region prefixes
5. **Format Conversion** - Convert between EAN-13, EAN-8, and UPC
6. **Barcode Generation** - Generate barcode images from EAN
7. **Async Validation** - Validate against product database

## Testing

The library uses **Vitest** with **Angular Testing Utilities** for testing.

### Running Tests

```bash
# Run tests for this library
npx nx test ean-validation --skip-nx-cache

# Run tests with coverage
npx nx test ean-validation --code-coverage --skip-nx-cache

# Run tests in watch mode
npx nx test ean-validation --watch
```

### Test Structure

The library includes comprehensive unit tests covering:

- **eanValidator Tests**:
  - Valid 13-digit EAN returns null
  - Empty value returns null (valid)
  - Invalid EAN returns error object
  - Null/undefined values handled correctly

- **isEan Tests**:
  - Valid 13-digit EAN returns true
  - Invalid EAN returns false
  - Empty string returns false
  - Undefined returns false

### Example Test

```typescript
import { describe, it, expect } from 'vitest';
import { FormControl } from '@angular/forms';
import { eanValidator, isEan } from './ean-validation';

describe('eanValidator', () => {
  it('should return null for valid EAN', () => {
    // Arrange
    const control = new FormControl('1234567890123');

    // Act
    const result = eanValidator(control);

    // Assert
    expect(result).toBeNull();
  });

  it('should return null for empty value', () => {
    // Arrange
    const control = new FormControl('');

    // Act
    const result = eanValidator(control);

    // Assert
    expect(result).toBeNull();
  });

  it('should return error for invalid EAN', () => {
    // Arrange
    const control = new FormControl('123');

    // Act
    const result = eanValidator(control);

    // Assert
    expect(result).toEqual({ invalidEan: true });
  });
});

describe('isEan', () => {
  it('should return true for valid EAN', () => {
    // Arrange
    const validEan = '1234567890123';

    // Act
    const result = isEan(validEan);

    // Assert
    expect(result).toBe(true);
  });

  it('should return false for undefined value', () => {
    // Arrange
    const undefinedValue = undefined;

    // Act
    const result = isEan(undefinedValue);

    // Assert
    expect(result).toBe(false);
  });

  it('should return false for invalid EAN', () => {
    // Arrange
    const invalidEan = '123';

    // Act
    const result = isEan(invalidEan);

    // Assert
    expect(result).toBe(false);
  });
});
```

## Dependencies

### Required Libraries

- `@angular/core` - For TypeScript types
- `@angular/forms` - For `ValidatorFn`, `AbstractControl`, `ValidationErrors` types

### Path Alias

Import from: `@isa/utils/ean-validation`

### Exports

- `eanValidator` - Angular Forms validator function
- `isEan` - Standalone validation utility function
- `EAN_REGEX` - Regular expression constant (internal use)

## Best Practices

### 1. Use with Validators.required

Combine with `Validators.required` for required fields:

```typescript
// Good - Required + EAN validation
const eanControl = new FormControl('', [
  Validators.required,
  eanValidator
]);

// Avoid - Only EAN validation (allows empty)
const eanControl = new FormControl('', [eanValidator]);
```

### 2. Provide Clear Error Messages

Show user-friendly error messages:

```typescript
// Good - Clear, actionable error message
@if (ean.errors?.['invalidEan']) {
  <p class="error">
    Please enter a valid 13-digit EAN (e.g., 1234567890123)
  </p>
}

// Avoid - Generic or unclear message
@if (ean.errors?.['invalidEan']) {
  <p class="error">Invalid</p>
}
```

### 3. Use isEan for Type Guards

Use `isEan()` for runtime type checking:

```typescript
// Good - Type-safe validation
function processEan(code: string | undefined): void {
  if (isEan(code)) {
    // TypeScript knows code is string here
    console.log('Processing:', code.toUpperCase());
  }
}

// Avoid - Unsafe type assumption
function processEan(code: string): void {
  // Might throw if code is invalid
  console.log('Processing:', code.toUpperCase());
}
```

### 4. Add Maxlength Attribute

Prevent users from entering more than 13 digits:

```typescript
// Good - Limit input length
<input
  formControlName="ean"
  type="text"
  maxlength="13"
  placeholder="1234567890123"
/>

// Avoid - No length restriction
<input
  formControlName="ean"
  type="text"
/>
```

### 5. Handle Validation in Components

Keep validation logic in components, not templates:

```typescript
// Good - Logic in component
canSubmit(): boolean {
  return this.form.valid && isEan(this.form.value.ean);
}

// Avoid - Complex logic in template
@if (form.valid && form.value.ean && /^[0-9]{13}$/.test(form.value.ean)) {
  ...
}
```

## Limitations

### What This Library Does NOT Do

1. **Check Digit Validation** - Does not validate the EAN-13 checksum digit
2. **Database Validation** - Does not verify if EAN exists in product database
3. **Prefix Validation** - Does not validate GS1 country/region prefixes
4. **Format Conversion** - Does not convert between EAN/UPC formats
5. **Barcode Generation** - Does not generate barcode images

### When to Use Additional Validation

For production systems, consider:

- **Check digit validation** for data integrity
- **Database lookup** for product existence
- **Prefix validation** for regional compliance
- **Duplicate detection** in batch processing

## License

Internal ISA Frontend library - not for external distribution.
