# Signal Library Documentation

## Overview

The Signal library provides a reactive state management solution for TypeScript/JavaScript applications. It implements the Observer pattern with automatic dependency tracking and computed values.

## Core Concepts

- **Signal**: A wrapper around a value that notifies subscribers when the value changes
- **Computed Signal**: A signal that derives its value from other signals
- **Effect**: A side effect that runs when its dependencies change
- **Batch**: A way to group multiple signal updates together

## Installation

```typescript
import { createSignal, computed, effect, batch } from '@avatijs/signal';
```

## Basic Usage

### Creating and Using Signals

```typescript
// Create a basic signal
const count = createSignal(0);

// Subscribe to changes
const unsubscribe = count.subscribe(() => {
    console.log('Count changed to:', count.value);
});

// Update the value
count.value = 1; // Logs: "Count changed to: 1"

// Update using a function
count.update(current => current + 1); // Logs: "Count changed to: 2"

// Cleanup
unsubscribe();
```

### Custom Equality Checking

```typescript
// Create a signal with custom equality checking
const user = createSignal(
    { name: 'John', age: 30 },
    {
        equals: (prev, next) => 
            prev.name === next.name && prev.age === next.age
    }
);

user.subscribe(() => console.log('User changed:', user.value));

// This won't trigger an update because the values are equal
user.value = { name: 'John', age: 30 };

// This will trigger an update
user.value = { name: 'John', age: 31 };
```

### Computed Signals

```typescript
// Basic computed value
const count = createSignal(0);
const doubleCount = computed(() => count.value * 2);

console.log(doubleCount.value); // 0
count.value = 5;
console.log(doubleCount.value); // 10

// Computed with multiple dependencies
const firstName = createSignal('John');
const lastName = createSignal('Doe');
const fullName = computed(() => `${firstName.value} ${lastName.value}`);

console.log(fullName.value); // "John Doe"
firstName.value = 'Jane';
console.log(fullName.value); // "Jane Doe"

// Computed with error handling
const safeDivision = computed(() => {
    if (count.value === 0) {
        throw new Error('Cannot divide by zero');
    }
    return 100 / count.value;
});

try {
    console.log(safeDivision.value);
} catch (error) {
    console.error('Division error:', error.message);
}
```

### Effects

```typescript
// Basic effect
const name = createSignal('John');
const cleanupEffect = effect(() => {
    console.log('Name changed to:', name.value);
    // Return cleanup function (optional)
    return () => console.log('Cleaning up previous effect');
});

name.value = 'Jane';
// Logs:
// "Cleaning up previous effect"
// "Name changed to: Jane"

// Cleanup when done
cleanupEffect();

// Effect with multiple dependencies
const user = createSignal({ name: 'John', age: 30 });
const settings = createSignal({ theme: 'dark' });

effect(() => {
    console.log(
        `User ${user.value.name} (${user.value.age}) ` +
        `prefers ${settings.value.theme} theme`
    );
});
```

### Batching Updates

```typescript
// Without batching - triggers three updates
const counter = createSignal(0);
counter.subscribe(() => console.log('Counter:', counter.value));

counter.value = 1; // Logs immediately
counter.value = 2; // Logs immediately
counter.value = 3; // Logs immediately

// With batching - triggers only one update
batch(() => {
    counter.value = 1;
    counter.value = 2;
    counter.value = 3;
}); // Logs only once with final value
```

## Advanced Examples

### Form Handling

```typescript
const formData = createSignal({ username: '', password: '' });
const isValid = computed(() => {
    const { username, password } = formData.value;
    return username.length >= 3 && password.length >= 8;
});

const errors = computed(() => {
    const { username, password } = formData.value;
    const errors: string[] = [];
    
    if (username.length < 3) {
        errors.push('Username must be at least 3 characters');
    }
    if (password.length < 8) {
        errors.push('Password must be at least 8 characters');
    }
    
    return errors;
});

// Form submission effect
effect(() => {
    if (isValid.value) {
        console.log('Form is valid, ready to submit!');
    } else {
        console.log('Form errors:', errors.value);
    }
});
```

### Shopping Cart Example

```typescript
interface Product {
    id: number;
    name: string;
    price: number;
}

interface CartItem extends Product {
    quantity: number;
}

// Create signals
const products = createSignal<Product[]>([
    { id: 1, name: 'Book', price: 10 },
    { id: 2, name: 'Pen', price: 2 },
]);

const cart = createSignal<CartItem[]>([]);

// Computed values
const totalItems = computed(() => 
    cart.value.reduce((sum, item) => sum + item.quantity, 0)
);

const totalPrice = computed(() => 
    cart.value.reduce((sum, item) => sum + (item.price * item.quantity), 0)
);

// Add to cart function
function addToCart(productId: number) {
    const product = products.value.find(p => p.id === productId);
    if (!product) return;

    cart.update(items => {
        const existingItem = items.find(item => item.id === productId);
        if (existingItem) {
            return items.map(item =>
                item.id === productId
                    ? { ...item, quantity: item.quantity + 1 }
                    : item
            );
        }
        return [...items, { ...product, quantity: 1 }];
    });
}

// Usage example
effect(() => {
    console.log(`Cart has ${totalItems.value} items`);
    console.log(`Total price: $${totalPrice.value}`);
});

addToCart(1); // Adds a book
addToCart(1); // Adds another book
addToCart(2); // Adds a pen
```

### Async Data Fetching

```typescript
interface User {
    id: number;
    name: string;
}

const userId = createSignal<number | null>(null);
const userIsLoading = createSignal(false);
const userData = createSignal<User | null>(null);
const userError = createSignal<string | null>(null);

// Effect to fetch user data
effect(() => {
    const currentUserId = userId.value;
    if (!currentUserId) {
        userData.value = null;
        return;
    }

    async function fetchUser() {
        userIsLoading.value = true;
        userError.value = null;
        
        try {
            const response = await fetch(
                `https://api.example.com/users/${currentUserId}`
            );
            if (!response.ok) {
                throw new Error('Failed to fetch user');
            }
            const data = await response.json();
            userData.value = data;
        } catch (error) {
            userError.value = error instanceof Error 
                ? error.message 
                : 'Unknown error';
        } finally {
            userIsLoading.value = false;
        }
    }

    fetchUser();
});

// Computed state for UI
const userState = computed(() => {
    if (userIsLoading.value) return 'loading';
    if (userError.value) return 'error';
    if (userData.value) return 'success';
    return 'idle';
});

// Usage
effect(() => {
    switch (userState.value) {
        case 'loading':
            console.log('Loading user data...');
            break;
        case 'error':
            console.log('Error:', userError.value);
            break;
        case 'success':
            console.log('User:', userData.value);
            break;
        case 'idle':
            console.log('No user selected');
            break;
    }
});

// Trigger a fetch
userId.value = 1;
```

## Best Practices

1. **Resource Management**
  - Always dispose of signals when they're no longer needed
  - Clean up effects when component unmounts
  - Use batch for multiple related updates

```typescript
const cleanup = effect(() => {
    // Effect logic
});

// Later, when cleaning up:
cleanup();
signal.dispose();
```

2. **Error Handling**
  - Always handle potential errors in computed signals
  - Provide fallback values for error cases
  - Use try-catch blocks when accessing computed values that might throw

3. **Performance**
  - Use batch for multiple updates
  - Implement custom equality checking for complex objects
  - Dispose of unused signals and effects

4. **Type Safety**
  - Always provide proper types for signals
  - Use interface definitions for complex data structures
  - Leverage TypeScript's type system for better error catching

## Common Pitfalls

1. **Circular Dependencies**
   ```typescript
   // DON'T: This will cause an infinite loop
   const a = createSignal(0);
   const b = computed(() => a.value + 1);
   const c = computed(() => b.value + 1);
   a.value = c.value; // Circular dependency!
   ```

2. **Memory Leaks**
   ```typescript
   // DON'T: Forgetting to clean up
   effect(() => {
       // Effect logic
   });

   // DO: Store and call cleanup
   const cleanup = effect(() => {
       // Effect logic
   });
   // Later:
   cleanup();
   ```

3. **Unnecessary Computations**
   ```typescript
   // DON'T: Computing values that could be static
   const static = computed(() => {
       return heavyCalculation(42); // This value never changes!
   });

   // DO: Use regular variables for static values
   const static = heavyCalculation(42);
   ```

## Advanced Topics

### Custom Signal Types

```typescript
class DebugSignal<T> extends Signal<T> {
    constructor(initialValue: T) {
        super(initialValue);
        this.subscribe(() => {
            console.log(`Signal updated to:`, this.value);
        });
    }
}

const debugCount = new DebugSignal(0);
debugCount.value = 1; // Logs automatically
```

### Integration with React

```typescript
function useSignal<T>(signal: Signal<T>): T {
    const [, forceUpdate] = useState({});
    
    useEffect(() => {
        return signal.subscribe(() => forceUpdate({}));
    }, [signal]);
    
    return signal.value;
}

// Usage in component
function Counter() {
    const count = useSignal(counterSignal);
    return <div>Count: {count}</div>;
}
```



## Changelog

Please see [CHANGELOG](./CHANGELOG.md) for more information what has changed recently.

## Contributing

I welcome contributions from developers of all experience levels. If you have an idea, found a bug, or want to improve something, I encourage you to get involved!

### How to Contribute
1. Read [Contributing Guide](https://github.com/KhaledSMQ/avati/blob/master/Contributing.md) for details on how to get started.
2. Fork the repository and make your changes.
3. Submit a pull request, and we’ll review it as soon as possible.

## License

[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/KhaledSMQ/avati/blob/master/LICENSE)

Avati is open-source and distributed under the [MIT License](https://github.com/KhaledSMQ/avati/blob/master/LICENSE).

---
<div align="center">

[![Follow on Twitter](https://img.shields.io/twitter/follow/KhaledSMQ.svg?style=social)](https://x.com/khaledsmq_)
[![Follow on LinkedIn](https://img.shields.io/badge/LinkedIn-Connect-blue.svg)](https://www.linkedin.com/in/khaledsmq/)
[![Follow on Medium](https://img.shields.io/badge/Medium-Follow-black.svg)](https://medium.com/@khaled.smq)
[![Made with ❤️](https://img.shields.io/badge/Made%20with-❤️-red.svg)](https://github.com/KhaledSMQ)
[![Star on GitHub](https://img.shields.io/github/stars/KhaledSMQ/avati.svg?style=social)](https://github.com/KhaledSMQ/avati/stargazers)
[![Follow on GitHub](https://img.shields.io/github/followers/KhaledSMQ.svg?style=social&label=Follow)](https://github.com/KhaledSMQ)

</div>
