# 📋 Управление коллекциями в CSElement

CSElement превосходно подходит для работы с коллекциями данных. В этом разделе мы изучим все аспекты создания, управления и оптимизации списков элементов.

## 🎯 Основы работы с коллекциями

### Создание коллекций

```typescript
import { CSElement } from 'cs-element';

// Создаем корневую коллекцию
const library = CSElement.create('Library')
  .withData('name', 'Городская библиотека')
  .withData('founded', 1945);

// Добавляем коллекции книг
const books = await library.addElement('books');
const authors = await library.addElement('authors');
const categories = await library.addElement('categories');

console.log(`Создана библиотека с ${library.elementsCount()} разделами`);
```

### Добавление элементов в коллекции

```typescript
// Простое добавление
await books.addElement('book1', {
  data: new Map([
    ['title', 'Война и мир'],
    ['author', 'Лев Толстой'],
    ['year', 1869],
    ['pages', 1225],
    ['genre', 'роман'],
    ['available', true]
  ])
});

// Добавление с индексом (для сортировки)
await books.addElement('book2', {
  index: 0, // Будет первой книгой
  data: new Map([
    ['title', 'Преступление и наказание'],
    ['author', 'Федор Достоевский'],
    ['year', 1866],
    ['pages', 671],
    ['genre', 'роман'],
    ['available', false]
  ])
});

// Fluent API для быстрого создания
books.withChild('book3')
  .withData('title', 'Мастер и Маргарита')
  .withData('author', 'Михаил Булгаков')
  .withData('year', 1967)
  .withData('pages', 384)
  .withData('genre', 'фантастика')
  .withData('available', true);
```

## 🔍 Мощная система поиска

### Базовые селекторы

```typescript
// Поиск по атрибутам
const availableBooks = books.query('[available=true]');
const novels = books.query('[genre=роман]');
const oldBooks = books.query('[year<1900]');

// Комбинированные селекторы
const availableNovels = books.query('[available=true][genre=роман]');
const modernBooks = books.query('[year>=1950]');

console.log(`Доступных романов: ${availableNovels.length}`);
```

### Продвинутые запросы

```typescript
// Поиск по частичному совпадению
const tolstoyBooks = books.findElements(book => 
  book.getData('author')?.includes('Толстой')
);

// Поиск с множественными условиями
const longBooks = books.findElements(book => {
  const pages = book.getData('pages');
  const available = book.getData('available');
  return pages > 500 && available;
});

// Поиск с сортировкой
const booksByYear = books.getAllElements()
  .sort((a, b) => a.getData('year') - b.getData('year'));

// Поиск с пагинацией
function getBooks(page: number, pageSize: number = 10) {
  const start = page * pageSize;
  const end = start + pageSize;
  return books.getAllElements().slice(start, end);
}

const firstPage = getBooks(0);
console.log(`Первая страница: ${firstPage.length} книг`);
```

### Сложные запросы с QueryEngine

```typescript
// Создаем селектор с помощью QueryEngine
const complexQuery = CSElement.createSelector()
  .where('available', true)
  .and('pages', '>', 300)
  .and('year', '>=', 1950)
  .orderBy('year', 'desc')
  .limit(5);

const selectedBooks = books.query(complexQuery);
console.log(`Найдено книг: ${selectedBooks.length}`);

// Группировка по жанрам
const booksByGenre = new Map();
books.getAllElements().forEach(book => {
  const genre = book.getData('genre');
  if (!booksByGenre.has(genre)) {
    booksByGenre.set(genre, []);
  }
  booksByGenre.get(genre).push(book);
});

console.log('Книги по жанрам:', Object.fromEntries(booksByGenre));
```

## 🎨 Продвинутые техники управления коллекциями

### Создание умных коллекций с автоматической сортировкой

```typescript
class SmartCollection extends CSElement {
  private sortKey: string;
  private sortOrder: 'asc' | 'desc';

  constructor(name: string, sortKey: string = 'name', sortOrder: 'asc' | 'desc' = 'asc') {
    super(name);
    this.sortKey = sortKey;
    this.sortOrder = sortOrder;
    this.setupAutoSort();
  }

  private setupAutoSort() {
    // Автоматическая сортировка при добавлении элементов
    this.on('element:added', () => {
      this.sort();
    });

    this.on('data:changed', (element, key) => {
      if (key === this.sortKey) {
        this.sort();
      }
    });
  }

  private sort() {
    const elements = this.getAllElements();
    elements.sort((a, b) => {
      const aValue = a.getData(this.sortKey);
      const bValue = b.getData(this.sortKey);
      
      if (this.sortOrder === 'asc') {
        return aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
      } else {
        return aValue > bValue ? -1 : aValue < bValue ? 1 : 0;
      }
    });

    // Обновляем индексы
    elements.forEach((element, index) => {
      element._index = index;
    });
  }

  setSortOrder(key: string, order: 'asc' | 'desc' = 'asc') {
    this.sortKey = key;
    this.sortOrder = order;
    this.sort();
  }
}

// Использование
const sortedBooks = new SmartCollection('SortedBooks', 'title', 'asc');
```

### Коллекции с фильтрацией в реальном времени

```typescript
class FilteredCollection extends CSElement {
  private filters: Map<string, (element: CSElement) => boolean> = new Map();
  private filteredElements: CSElement[] = [];

  addFilter(name: string, predicate: (element: CSElement) => boolean) {
    this.filters.set(name, predicate);
    this.updateFiltered();
  }

  removeFilter(name: string) {
    this.filters.delete(name);
    this.updateFiltered();
  }

  private updateFiltered() {
    this.filteredElements = this.getAllElements().filter(element => {
      return Array.from(this.filters.values()).every(filter => filter(element));
    });

    this.emit('filtered:updated', this.filteredElements);
  }

  getFiltered(): readonly CSElement[] {
    return this.filteredElements;
  }

  getFilteredCount(): number {
    return this.filteredElements.length;
  }

  // Переопределяем методы для обновления фильтра
  async addElement(value: any, options: any = {}) {
    const result = await super.addElement(value, options);
    this.updateFiltered();
    return result;
  }

  async removeElement(nameOrIndex: any) {
    const result = await super.removeElement(nameOrIndex);
    this.updateFiltered();
    return result;
  }
}

// Использование
const filteredBooks = new FilteredCollection('FilteredBooks');

// Добавляем фильтры
filteredBooks.addFilter('available', book => book.getData('available') === true);
filteredBooks.addFilter('modern', book => book.getData('year') >= 1950);
filteredBooks.addFilter('fiction', book => 
  ['роман', 'фантастика', 'детектив'].includes(book.getData('genre'))
);

// Подписываемся на обновления
filteredBooks.on('filtered:updated', (filtered) => {
  console.log(`Отфильтровано книг: ${filtered.length}`);
});
```

## 📊 Работа с большими коллекциями

### Виртуализация и ленивая загрузка

```typescript
class VirtualizedCollection extends CSElement {
  private pageSize: number;
  private loadedPages: Set<number> = new Set();
  private totalItems: number = 0;

  constructor(name: string, pageSize: number = 50) {
    super(name);
    this.pageSize = pageSize;
  }

  async loadPage(pageNumber: number): Promise<CSElement[]> {
    if (this.loadedPages.has(pageNumber)) {
      return this.getPage(pageNumber);
    }

    // Симуляция загрузки данных
    const pageData = await this.fetchPageData(pageNumber);
    
    for (const item of pageData) {
      await this.addElement(`item_${pageNumber}_${item.id}`, {
        data: new Map(Object.entries(item))
      });
    }

    this.loadedPages.add(pageNumber);
    return this.getPage(pageNumber);
  }

  getPage(pageNumber: number): CSElement[] {
    const start = pageNumber * this.pageSize;
    const end = start + this.pageSize;
    return this.getAllElements().slice(start, end);
  }

  async getVisibleRange(startIndex: number, endIndex: number): Promise<CSElement[]> {
    const startPage = Math.floor(startIndex / this.pageSize);
    const endPage = Math.floor(endIndex / this.pageSize);

    // Загружаем необходимые страницы
    for (let page = startPage; page <= endPage; page++) {
      if (!this.loadedPages.has(page)) {
        await this.loadPage(page);
      }
    }

    return this.getAllElements().slice(startIndex, endIndex + 1);
  }

  private async fetchPageData(pageNumber: number): Promise<any[]> {
    // Здесь был бы реальный API вызов
    return Array.from({ length: this.pageSize }, (_, i) => ({
      id: pageNumber * this.pageSize + i,
      title: `Book ${pageNumber * this.pageSize + i}`,
      author: `Author ${Math.floor(Math.random() * 100)}`,
      year: 1900 + Math.floor(Math.random() * 124)
    }));
  }
}
```

### Индексирование для быстрого поиска

```typescript
class IndexedCollection extends CSElement {
  private indexes: Map<string, Map<any, CSElement[]>> = new Map();

  createIndex(fieldName: string) {
    const index = new Map();
    
    this.getAllElements().forEach(element => {
      const value = element.getData(fieldName);
      if (value !== undefined) {
        if (!index.has(value)) {
          index.set(value, []);
        }
        index.get(value).push(element);
      }
    });

    this.indexes.set(fieldName, index);

    // Обновляем индекс при изменении данных
    this.on('element:added', (element) => {
      this.updateIndex(fieldName, element);
    });

    this.on('data:changed', (element, key, newValue, oldValue) => {
      if (key === fieldName) {
        this.removeFromIndex(fieldName, element, oldValue);
        this.updateIndex(fieldName, element);
      }
    });
  }

  private updateIndex(fieldName: string, element: CSElement) {
    const index = this.indexes.get(fieldName);
    if (!index) return;

    const value = element.getData(fieldName);
    if (value !== undefined) {
      if (!index.has(value)) {
        index.set(value, []);
      }
      index.get(value).push(element);
    }
  }

  private removeFromIndex(fieldName: string, element: CSElement, value: any) {
    const index = this.indexes.get(fieldName);
    if (!index || !index.has(value)) return;

    const elements = index.get(value);
    const elementIndex = elements.indexOf(element);
    if (elementIndex > -1) {
      elements.splice(elementIndex, 1);
      if (elements.length === 0) {
        index.delete(value);
      }
    }
  }

  findByIndex(fieldName: string, value: any): CSElement[] {
    const index = this.indexes.get(fieldName);
    return index?.get(value) || [];
  }

  getIndexStats(fieldName: string): { uniqueValues: number, totalElements: number } {
    const index = this.indexes.get(fieldName);
    if (!index) return { uniqueValues: 0, totalElements: 0 };

    const uniqueValues = index.size;
    const totalElements = Array.from(index.values())
      .reduce((sum, arr) => sum + arr.length, 0);

    return { uniqueValues, totalElements };
  }
}

// Использование
const indexedBooks = new IndexedCollection('IndexedBooks');
indexedBooks.createIndex('author');
indexedBooks.createIndex('genre');
indexedBooks.createIndex('year');

// Быстрый поиск по индексу
const tolstoyBooks = indexedBooks.findByIndex('author', 'Лев Толстой');
const novels = indexedBooks.findByIndex('genre', 'роман');

console.log('Статистика индексов:');
console.log('Авторы:', indexedBooks.getIndexStats('author'));
console.log('Жанры:', indexedBooks.getIndexStats('genre'));
```

## 🔄 Реактивные коллекции

### Live Collections с автообновлением

```typescript
class LiveCollection extends CSElement {
  private liveQuery: any;
  private selector: string;

  constructor(name: string, sourceCollection: CSElement, selector: string) {
    super(name);
    this.selector = selector;
    this.setupLiveQuery(sourceCollection);
  }

  private setupLiveQuery(sourceCollection: CSElement) {
    this.liveQuery = CSElement.createLiveQuery(this.selector, {
      root: sourceCollection,
      debounce: 50
    });

    this.liveQuery.onResults((results: CSElement[]) => {
      // Очищаем текущие элементы
      this.clear();

      // Добавляем новые результаты
      results.forEach(async (element, index) => {
        await this.addElement(`item_${index}`, {
          data: element.data
        });
      });

      this.emit('collection:updated', results);
    });

    CSElement.liveQueries.start(this.liveQuery.id);
  }

  private clear() {
    const elements = [...this.getAllElements()];
    elements.forEach(element => {
      this.removeElement(element);
    });
  }

  updateSelector(newSelector: string) {
    this.selector = newSelector;
    CSElement.liveQueries.stop(this.liveQuery.id);
    // Создаем новый live query с обновленным селектором
    // this.setupLiveQuery(sourceCollection); // Нужна ссылка на источник
  }

  destroy() {
    if (this.liveQuery) {
      CSElement.liveQueries.stop(this.liveQuery.id);
    }
    super.destroy();
  }
}
```

### Computed коллекции

```typescript
class ComputedCollection extends CSElement {
  private computedProperty: any;
  private computeFunction: () => CSElement[];

  constructor(name: string, computeFunction: () => CSElement[]) {
    super(name);
    this.computeFunction = computeFunction;
    this.setupComputed();
  }

  private setupComputed() {
    this.computedProperty = CSElement.computed(() => {
      return this.computeFunction();
    });

    // Обновляем коллекцию при изменении computed свойства
    CSElement.watch([this.computedProperty.id], (newValue) => {
      this.updateCollection(newValue);
    });
  }

  private updateCollection(newElements: CSElement[]) {
    // Очищаем текущие элементы
    this.clear();

    // Добавляем новые элементы
    newElements.forEach(async (element, index) => {
      await this.addElement(`computed_${index}`, {
        data: element.data
      });
    });

    this.emit('computed:updated', newElements);
  }

  private clear() {
    const elements = [...this.getAllElements()];
    elements.forEach(element => {
      this.removeElement(element);
    });
  }

  get value(): CSElement[] {
    return this.computedProperty.value;
  }
}

// Использование
const availableBooks = new ComputedCollection('AvailableBooks', () => {
  return books.query('[available=true]');
});

const popularBooks = new ComputedCollection('PopularBooks', () => {
  return books.getAllElements()
    .filter(book => book.getData('rating') >= 4.5)
    .sort((a, b) => b.getData('rating') - a.getData('rating'))
    .slice(0, 10);
});
```

## 🎯 Полный пример - Система управления библиотекой

```typescript
import { CSElement } from 'cs-element';

class LibrarySystem {
  private library: CSElement;
  private books: IndexedCollection;
  private authors: CSElement;
  private members: CSElement;
  private loans: CSElement;
  
  // Реактивные коллекции
  private availableBooks: LiveCollection;
  private overdueLoans: LiveCollection;
  private popularBooks: ComputedCollection;

  constructor() {
    this.initializeLibrary();
    this.setupCollections();
    this.setupReactivity();
    this.setupEventHandlers();
  }

  private initializeLibrary() {
    this.library = CSElement.create('LibrarySystem')
      .withData('name', 'Центральная библиотека')
      .withData('established', 1925)
      .withData('totalBooks', 0)
      .withData('totalMembers', 0);
  }

  private setupCollections() {
    // Основные коллекции
    this.books = new IndexedCollection('Books');
    this.authors = new CSElement('Authors');
    this.members = new CSElement('Members');
    this.loans = new CSElement('Loans');

    // Добавляем в библиотеку
    this.library.withChild(this.books);
    this.library.withChild(this.authors);
    this.library.withChild(this.members);
    this.library.withChild(this.loans);

    // Создаем индексы для быстрого поиска
    this.books.createIndex('author');
    this.books.createIndex('genre');
    this.books.createIndex('isbn');
    this.books.createIndex('available');
  }

  private setupReactivity() {
    // Доступные книги
    this.availableBooks = new LiveCollection(
      'AvailableBooks',
      this.books,
      '[available=true]'
    );

    // Просроченные займы
    this.overdueLoans = new LiveCollection(
      'OverdueLoans',
      this.loans,
      `[dueDate<${Date.now()}][returned=false]`
    );

    // Популярные книги (computed)
    this.popularBooks = new ComputedCollection('PopularBooks', () => {
      return this.books.getAllElements()
        .filter(book => book.getData('loanCount') > 0)
        .sort((a, b) => b.getData('loanCount') - a.getData('loanCount'))
        .slice(0, 20);
    });
  }

  private setupEventHandlers() {
    // Обновляем статистику при добавлении книг
    this.books.on('element:added', () => {
      this.library.setData('totalBooks', this.books.elementsCount());
    });

    // Обновляем статистику при добавлении читателей
    this.members.on('element:added', () => {
      this.library.setData('totalMembers', this.members.elementsCount());
    });

    // Обрабатываем займы книг
    this.loans.on('element:added', (loan) => {
      const bookId = loan.getData('bookId');
      const book = this.books.getElementById(bookId);
      if (book) {
        book.setData('available', false);
        const loanCount = book.getData('loanCount') || 0;
        book.setData('loanCount', loanCount + 1);
      }
    });

    // Обрабатываем возврат книг
    this.loans.on('data:changed', (loan, key, newValue) => {
      if (key === 'returned' && newValue === true) {
        const bookId = loan.getData('bookId');
        const book = this.books.getElementById(bookId);
        if (book) {
          book.setData('available', true);
        }
      }
    });
  }

  // Методы управления книгами
  async addBook(bookData: {
    title: string;
    author: string;
    isbn: string;
    genre: string;
    publishYear: number;
    pages: number;
  }) {
    const bookId = `book_${bookData.isbn}`;
    
    const book = await this.books.addElement(bookId, {
      data: new Map([
        ['title', bookData.title],
        ['author', bookData.author],
        ['isbn', bookData.isbn],
        ['genre', bookData.genre],
        ['publishYear', bookData.publishYear],
        ['pages', bookData.pages],
        ['available', true],
        ['loanCount', 0],
        ['addedDate', new Date().toISOString()]
      ])
    });

    // Добавляем автора если его нет
    await this.ensureAuthorExists(bookData.author);

    return book;
  }

  private async ensureAuthorExists(authorName: string) {
    const existingAuthor = this.authors.findElements(author => 
      author.getData('name') === authorName
    );

    if (existingAuthor.length === 0) {
      await this.authors.addElement(`author_${Date.now()}`, {
        data: new Map([
          ['name', authorName],
          ['bookCount', 1]
        ])
      });
    } else {
      const author = existingAuthor[0];
      const bookCount = author.getData('bookCount') || 0;
      author.setData('bookCount', bookCount + 1);
    }
  }

  // Методы поиска
  searchBooks(query: {
    title?: string;
    author?: string;
    genre?: string;
    available?: boolean;
    yearFrom?: number;
    yearTo?: number;
  }): CSElement[] {
    let results = this.books.getAllElements();

    if (query.title) {
      results = results.filter(book => 
        book.getData('title')?.toLowerCase().includes(query.title.toLowerCase())
      );
    }

    if (query.author) {
      results = this.books.findByIndex('author', query.author);
    }

    if (query.genre) {
      results = this.books.findByIndex('genre', query.genre);
    }

    if (query.available !== undefined) {
      results = this.books.findByIndex('available', query.available);
    }

    if (query.yearFrom || query.yearTo) {
      results = results.filter(book => {
        const year = book.getData('publishYear');
        if (query.yearFrom && year < query.yearFrom) return false;
        if (query.yearTo && year > query.yearTo) return false;
        return true;
      });
    }

    return results;
  }

  // Управление займами
  async loanBook(bookId: string, memberId: string, durationDays: number = 14): Promise<boolean> {
    const book = this.books.getElementById(bookId);
    const member = this.members.getElementById(memberId);

    if (!book || !member || !book.getData('available')) {
      return false;
    }

    const loanId = `loan_${Date.now()}`;
    const dueDate = new Date();
    dueDate.setDate(dueDate.getDate() + durationDays);

    await this.loans.addElement(loanId, {
      data: new Map([
        ['bookId', bookId],
        ['memberId', memberId],
        ['loanDate', new Date().toISOString()],
        ['dueDate', dueDate.getTime()],
        ['returned', false],
        ['renewalCount', 0]
      ])
    });

    return true;
  }

  async returnBook(loanId: string): Promise<boolean> {
    const loan = this.loans.getElementById(loanId);
    if (!loan || loan.getData('returned')) {
      return false;
    }

    await loan.setData('returned', true);
    await loan.setData('returnDate', new Date().toISOString());

    return true;
  }

  // Получение статистики
  getLibraryStats() {
    return {
      totalBooks: this.books.elementsCount(),
      availableBooks: this.availableBooks.elementsCount(),
      totalAuthors: this.authors.elementsCount(),
      totalMembers: this.members.elementsCount(),
      activeLoans: this.loans.query('[returned=false]').length,
      overdueLoans: this.overdueLoans.elementsCount(),
      popularBooks: this.popularBooks.value.slice(0, 5).map(book => ({
        title: book.getData('title'),
        author: book.getData('author'),
        loanCount: book.getData('loanCount')
      }))
    };
  }

  // Получение аналитики по жанрам
  getGenreAnalytics() {
    const genreStats = new Map();
    
    this.books.getAllElements().forEach(book => {
      const genre = book.getData('genre');
      if (!genreStats.has(genre)) {
        genreStats.set(genre, {
          totalBooks: 0,
          availableBooks: 0,
          totalLoans: 0
        });
      }
      
      const stats = genreStats.get(genre);
      stats.totalBooks++;
      if (book.getData('available')) {
        stats.availableBooks++;
      }
      stats.totalLoans += book.getData('loanCount') || 0;
    });

    return Object.fromEntries(genreStats);
  }
}

// Использование системы
const library = new LibrarySystem();

// Добавляем книги
await library.addBook({
  title: 'Война и мир',
  author: 'Лев Толстой',
  isbn: '978-5-17-082417-1',
  genre: 'роман',
  publishYear: 1869,
  pages: 1225
});

await library.addBook({
  title: '1984',
  author: 'Джордж Оруэлл',
  isbn: '978-5-17-082418-8',
  genre: 'антиутопия',
  publishYear: 1949,
  pages: 328
});

// Поиск книг
const novels = library.searchBooks({ genre: 'роман' });
const availableBooks = library.searchBooks({ available: true });
const modernBooks = library.searchBooks({ yearFrom: 1950 });

console.log('Статистика библиотеки:', library.getLibraryStats());
console.log('Аналитика по жанрам:', library.getGenreAnalytics());
```

## 🎉 Заключение

Управление коллекциями в CSElement предоставляет невероятную гибкость и мощь:

- **🔍 Мощный поиск** - от простых селекторов до сложных запросов
- **⚡ Реактивность** - автоматическое обновление при изменениях
- **📊 Индексирование** - быстрый доступ к данным
- **🎯 Виртуализация** - работа с большими объемами данных
- **🔄 Live Collections** - коллекции, обновляющиеся в реальном времени

**Готовы к изучению реактивности?** Переходите к [следующему разделу](03-reactivity.md)! 🚀 