import { Meta } from '@storybook/addon-docs/blocks'

<Meta title='@dxtmisha/ru/functional/Functions/executeUse - Синглтон с управлением жизненным циклом'/>

# `executeUse`

`executeUse` — это фабрика, которая оборачивает любую функцию-инициализатор и превращает её результат в **управляемый синглтон**. Объект создаётся ровно один раз, замораживается (`Object.freeze`) и переиспользуется при каждом последующем вызове. В зависимости от выбранного типа синглтон может быть компонентным, глобальным или локальным.

Для обеспечения уникальности идентификаторов используется сочетание внутреннего глобального кода и функции `getElementId()`, что гарантирует отсутствие коллизий даже при множественном использовании в разных модулях.



### Ограничения дженериков (Generic Constraints)

- `R` — тип объекта, возвращаемого функцией-инициализатором (`callback`).
- `O extends any[]` — типы аргументов, которые принимает `callback`.
- `RI extends ExecuteUseReturn<R>` — результирующий тип синглтона, включающий методы управления.

```typescript
function executeUse<R, O extends any[], RI extends ExecuteUseReturn<R>>(
  callback: (...args: O) => R,
  type: ExecuteUseType = ExecuteUseType.provide
): (...args: O) => RI

function executeUseGlobal<R>(callback: () => R): () => ExecuteUseReturn<R>
function executeUseProvide<R, O extends any[]>(callback: (...args: O) => R): (...args: O) => ExecuteUseReturn<R>
function executeUseLocal<R, O extends any[]>(callback: (...args: O) => R): (...args: O) => ExecuteUseReturn<R>
```

**Параметры:**
- `callback: (...args: O) => R` — Функция-инициализатор. Вызывается при первом обращении и возвращает объект, который будет заморожен и закеширован. Аргументы передаются только при первом вызове.
- `type: ExecuteUseType` — Тип синглтона (по умолчанию `ExecuteUseType.provide`).

**Возвращает:** `ExecuteUseReturn<R>` — замороженный объект, расширяющий `R` следующими методами:
- `init(): Readonly<R>` — возвращает исходный результат `callback` без методов управления (`init`, `destroyExecute`).
- `destroyExecute?(): void` — только у `local` и `global`. Сбрасывает кеш, позволяя пересоздать объект при следующем вызове.

### Функция-инициализатор (`callback`) и её аргументы (`...args: O`)

Главная особенность `executeUse` заключается в том, что фабричная функция (`callback`) и переданные в неё аргументы (`...args`) используются **только один раз** — во время фактической инициализации (самого первого вызова).

- **`callback`**: Это фабрика, описывающая логику создания состояния. Внутри неё обычно объявляются реактивные переменные (`ref`, `reactive`), функции для их изменения и вычисляемые значения (`computed`). Возвращаемый из `callback` объект будет "заморожен" и закеширован.
- **`...args: O`**: Вы можете передавать любые аргументы в ваш инициализатор (например, начальное состояние, ID загружаемой сущности или API-клиент). Эти аргументы будут прокинуты внутрь `callback`. 

> **Важно об аргументах:** Так как `executeUse` жестко кеширует результат первого вызова, любые аргументы, переданные при **последующих** вызовах этого же хука в других компонентах, **будут полностью проигнорированы**. Объект не будет пересоздан, и функция `callback` не выполнится повторно с новыми аргументами (если только вы предварительно не сбросили кеш через `destroyExecute()`).

### Метод-заглушка `init()`

Метод `init` — это просто пустая функция-заглушка, которая возвращает тот же самый замороженный объект состояния. Она нужна **исключительно для удобства чтения кода (Developer Experience)**. 

Её главная цель — дать возможность разработчику явно и визуально выделить в коде то самое место, где происходит фактическая (первая) инициализация синглтона. С технической точки зрения `const state = useTheme()` и `const state = useTheme().init()` делают абсолютно одно и то же.

```typescript
// Где-то глубоко в дереве компонентов мы просто получаем готовый стейт
const theme = useTheme()

// А здесь, в корневом компоненте (или main.ts), 
// мы явно показываем другим разработчикам: "Инициализация происходит именно ТУТ"
const theme = useTheme().init()
```
## Пошаговая инструкция: Как создать и использовать свой сервис

Создание управляемого синглтона с помощью `executeUse` обычно состоит из трёх простых шагов.

### Шаг 1: Определение состояния (Фабрика)
Сначала решите, какой тип стейта вам нужен (`global`, `local` или `provide`). Создайте отдельный файл (например, `useMyService.ts`). Используйте одну из функций-фабрик (например, `executeUseGlobal`).

Внутри функции-колбека опишите все ваши реактивные переменные (`ref`, `reactive`), вычисляемые свойства (`computed`) и функции для изменения состояния:

```typescript
// src/composables/useMyService.ts
import { ref, computed } from 'vue'
import { executeUseGlobal } from '@dxtmisha/functional'

// Создаем синглтон и инкапсулируем логику
const instance = executeUseGlobal(() => {
  // 1. Реактивное состояние
  const count = ref(0)
  
  // 2. Вычисляемые свойства
  const isPositive = computed(() => count.value > 0)
  
  // 3. Методы управления
  const increment = () => count.value++
  const reset = () => { count.value = 0 }

  // Возвращаем то, что должно быть доступно снаружи
  return {
    count,
    isPositive,
    increment,
    reset
  }
})

// Экспортируем функцию-обертку.
// Конструкция () => instance() нужна для того, чтобы редакторы кода (IDE)
// корректно распознавали useMyService именно как функцию-composable.
// Это обеспечивает корректную работу автокомплита и вывода типов.
export const useMyService = () => instance()
```

### Шаг 2: Инициализация (Только для global)
Если вы выбрали режим `global` (как в примере выше), вам необходимо указать приложению, когда именно создать этот экземпляр. Обычно это делается однократно в точке входа (`main.ts`):

```typescript
// src/main.ts
import { createApp } from 'vue'
import { executeUseGlobalInit } from '@dxtmisha/functional'
import App from './App.vue'

// Инициализируем абсолютно все global-синглтоны разом
executeUseGlobalInit() 

createApp(App).mount('#app')
```

> **Примечание:** Если в вашем приложении подключен плагин `dxtFunctionalPlugin`, вызывать `executeUseGlobalInit()` вручную при старте приложения **не нужно** — плагин делает это автоматически под капотом.

### Шаг 3: Использование в компоненте
Теперь вы можете импортировать вашу функцию `useMyService` в любой Vue-компонент. При самом первом логическом вызове для наглядности используйте метод `init()`.

```vue
<!-- src/components/MyComponent.vue -->
<script setup lang="ts">
import { useMyService } from '@/composables/useMyService'

// Получаем стейт напрямую. Метод init() подсказывает, 
// что здесь происходит первичное обращение в рамках этого компонента.
const { count, isPositive, increment } = useMyService().init()
</script>

<template>
  <div>
    <p>Текущий счетчик: {{ count }}</p>
    <p>Больше нуля? {{ isPositive ? 'Да' : 'Нет' }}</p>
    <button @click="increment">Увеличить</button>
  </div>
</template>
```

Если по какой-то причине вам нужно принудительно сбросить стейт (например, при выходе пользователя из аккаунта), вызовите встроенный метод сброса:
```typescript
const service = useMyService()
if (service.destroyExecute) {
  service.destroyExecute()
}
```

## Тип `ExecuteUseType`

### `provide` (компонентный синглтон, по умолчанию)

Это компонентно-ориентированный синглтон, который жестко привязан к жизненному циклу компонента Vue и всего его поддерева. Он использует под капотом встроенный в Vue механизм реактивности и передачи контекста (provide/inject), позволяя безопасно прокидывать один и тот же экземпляр состояния глубоко вниз по дереву без необходимости передавать его через props.

**Когда использовать:** 
- Для совместного использования состояния между строго определенной веткой компонентов (родитель -> много уровней вложенности -> дочерний), без засорения глобального хранилища.
- Когда на одной странице нужно вывести несколько независимых блоков (например, несколько сложных виджетов загрузки файлов), у каждого из которых должно быть своё уникальное состояние, разделяемое только внутри этого виджета.
- Если нужно автоматическое удаление данных из памяти (garbage collection) при размонтировании "компонента-владельца".

**Как работает:**
Компонент, который первым вызывает эту функцию, автоматически становится «владельцем» (owner). Функция выполняет генерацию объекта и сразу же делает `provide(id, instance)`. Все вложенные компоненты при вызове этой же функции внутри себя не создают новый объект, а получают ранее созданный через `inject(id)`. 
> Примечание: У этого типа отсутствует метод `destroyExecute()`, так как память освобождается автоматически системой реактивности Vue при уничтожении компонента-владельца.

```typescript
import { executeUseProvide } from '@dxtmisha/functional'
import { ref } from 'vue'

const useTheme = executeUseProvide(() => {
  const mode = ref<'light' | 'dark'>('light')
  const toggle = () => { mode.value = mode.value === 'light' ? 'dark' : 'light' }
  return { mode, toggle }
})

// В родительском компоненте (владелец) — объект создаётся и публикуется (provide):
const theme = useTheme()
theme.toggle()

// В глубоко вложенном дочернем компоненте — получает тот же объект (inject):
const theme = useTheme()

// В совершенно другом, независимом дереве компонентов — создастся НОВЫЙ независимый экземпляр:
const theme2 = useTheme()
```

### `global` (глобальный синглтон приложения)

Это классический архитектурный паттерн Singleton, существующий в строго единственном экземпляре на протяжении всего времени работы вашего приложения (с момента инициализации до закрытия вкладки браузера). Он обеспечивает глобальную точку доступа к состоянию из любого места в коде.

**Когда использовать:**
- Для хранения глобального стейта приложения: авторизации, профиля текущего пользователя, языковых настроек, глобальной корзины товаров.
- Когда доступ к одним и тем же данным требуется из совершенно разных, не связанных между собой частей приложения (или даже вне компонентов Vue, например, в роутере).
- Как легковесная замена Vuex / Pinia для простых задач.

**Как работает:**
Функция инициализации **не выполняется при вызове**. Вместо этого она добавляется в специальную внутреннюю очередь (`globalMethods`). Полная инициализация всех глобальных объектов происходит только при явном вызове `executeUseGlobalInit()`, который обычно размещается в `main.ts` до того, как приложение будет смонтировано. В дальнейшем все компоненты получают один и тот же закешированный объект из памяти.

```typescript
import { executeUseGlobal, executeUseGlobalInit } from '@dxtmisha/functional'
import { ref } from 'vue'

const useAuth = executeUseGlobal(() => {
  const user = ref<string | null>(null)
  const login = (name: string) => { user.value = name }
  const logout = () => { user.value = null }
  return { user, login, logout }
})

// В main.ts — строго ДО монтирования приложения (app.mount()):
executeUseGlobalInit()

// В любом компоненте, утилите или роутере:
const auth = useAuth()
auth.login('Alice')

// Если нужно принудительно сбросить состояние (пересоздать стейт заново):
auth.destroyExecute?.() 
```

### `local` (локальный синглтон в замыкании)

Это независимый синглтон, который опирается на механизм лексических замыканий (closure caching) в JavaScript. Вместо использования контекста Vue или глобального скоупа, он сохраняет однажды сгенерированный результат прямо внутри своей собственной функции в памяти, мгновенно отдавая его при всех последующих вызовах.

**Когда использовать:**
- Для утилит, фабрик или сервисов, которые не привязаны к Vue (`provide`/`inject` не работают вне setup-функции компонента).
- Когда вам нужен изолированный инстанс тяжелого класса или сервиса только тогда, когда он реально понадобится (ленивая инициализация по первому требованию), а не при старте всего приложения.
- Для кэширования результатов сложных математических вычислений или преобразований конфигураций.

**Как работает:**
Не использует механизмы Vue. При самом первом вызове выполняется функция-инициализатор с переданными аргументами. Полученный результат жестко замораживается (`Object.freeze`) и сохраняется в переменной внутри замыкания `executeUse`. При всех последующих вызовах (даже если вы передадите другие аргументы) функция просто мгновенно возвращает уже сохраненный объект из переменной. 
Метод `destroyExecute()` позволяет занулить эту внутреннюю переменную, чтобы при следующем вызове алгоритм инициализации отработал заново.

```typescript
import { executeUseLocal } from '@dxtmisha/functional'
import { ref } from 'vue'

const useHeavyConfig = executeUseLocal((env: string) => {
  // Тяжелая операция вычисления конфигурации...
  const config = ref({ api: `https://${env}.example.com` })
  return { config }
})

// Первый вызов — инициализация
const devConfig = useHeavyConfig('dev')
console.log(devConfig.config.value.api) // https://dev.example.com

// Второй вызов в другом файле — вернет тот же объект, аргумент игнорируется!
const sameConfig = useHeavyConfig('prod') 
console.log(sameConfig.config.value.api) // Всё еще https://dev.example.com

// Принудительная очистка кэша
sameConfig.destroyExecute?.()
const newConfig = useHeavyConfig('prod') // Инициализация запустится заново
```

## `executeUseGlobalInit`

```typescript
function executeUseGlobalInit(): void
```

Вызывает все зарегистрированные глобальные инициализаторы (`ExecuteUseType.global`) и очищает очередь. Повторный вызов не пересоздаёт уже инициализированные синглтоны. Должна быть вызвана **один раз** при старте приложения — до монтирования корневого компонента.

```typescript
import { createApp } from 'vue'
import { executeUseGlobalInit } from '@dxtmisha/functional'
import App from './App.vue'

executeUseGlobalInit()
createApp(App).mount('#app')
```

