# Spotlight

## Descrição

O Spotlight é uma biblioteca para criação de **tours guiados** (onboarding com múltiplos passos) e **destaques simples** (dicas únicas sobre um elemento). O posicionamento do popover é gerenciado automaticamente pelo Angular CDK, com fallbacks inteligentes para manter o popover sempre visível.

Dois modos de operação:

- **Tour**: sequência de passos com navegação Próximo / Voltar / Concluído, contador de progresso, focus trap e bloqueio de scroll.
- **Destaque simples**: popover único com botão "Entendi" e opção de "Não mostrar novamente".

## Instalação

```bash
npm install @seniorsistemas/angular-components/spotlight
```

## Conceitos

### Diretiva `sSpotlightStep`

Registra um elemento do DOM como alvo de um passo do tour. Deve ser aplicada ao elemento que receberá o destaque.

```html
<button sSpotlightStep="meu-botao">Salvar</button>
```

O valor do atributo é o `stepId`, que deve corresponder ao `stepId` definido nos passos do tour.
Quando o valor do binding muda dinamicamente, a diretiva desregistra o ID anterior e registra o novo automaticamente.

### `SpotlightTourService`

Serviço singleton que controla o estado do tour. Injete-o no componente que inicia o tour.

---

## Tour (múltiplos passos)

### 1. Marque os elementos no template

```html
<input sSpotlightStep="produto-nome" ... />
<select sSpotlightStep="produto-categoria" ... />
<button sSpotlightStep="salvar-btn">Salvar</button>
```

### 2. Inicie o tour pelo serviço

```typescript
import { SpotlightTourService, SpotlightStep } from '@seniorsistemas/angular-components/spotlight';

@Component({ ... })
export class MeuComponent {
  private readonly tourService = inject(SpotlightTourService);

  private readonly passos: SpotlightStep[] = [
    {
      stepId: 'produto-nome',
      title: 'Nome do produto',
      message: 'Informe o nome completo do produto.',
      position: 'bottom-center',
    },
    {
      stepId: 'produto-categoria',
      title: 'Categoria',
      message: 'Selecione a categoria correta.',
      position: 'bottom-end',
      beforeNext: () => {
        // Executado antes de avançar — útil para trocar abas, expandir seções, etc.
        this.activeTab.set('detalhes');
      },
    },
    {
      stepId: 'salvar-btn',
      title: 'Salvar',
      message: 'Clique aqui para salvar as alterações.',
      position: 'top-center',
    },
  ];

  public iniciarTour(): void {
    this.tourService.start(this.passos);
  }
}
```

### 3. Reaja aos eventos do tour

```typescript
private readonly destroyRef = inject(DestroyRef);

public constructor() {
  this.tourService.stopped$
    .pipe(takeUntilDestroyed(this.destroyRef))
    .subscribe((event) => {
      // Salve o estado para persistência
      localStorage.setItem('tour-onboarding', JSON.stringify({
        reason: event.reason,           // 'completed' | 'interrupted'
        doNotShowAgain: event.doNotShowAgain,
        stepIndex: event.stepIndex,
      }));
    });
}
```

---

## Destaque Simples

Use `tourService.spotlight()` para exibir um destaque em um único elemento.

```html
<div sSpotlightStep="novo-recurso">...</div>
```

```typescript
import { SpotlightTourService, SpotlightConfig } from '@seniorsistemas/angular-components/spotlight';

@Component({ ... })
export class MeuComponent {
  private readonly tourService = inject(SpotlightTourService);

  public mostrarDica(): void {
    const config: SpotlightConfig = {
      title: 'Novidade!',
      message: 'Este recurso foi adicionado recentemente.',
      position: 'bottom-center',
      // showDoNotShowAgain é true por padrão — passe false para suprimir o checkbox
      dismissible: true,
    };

    this.tourService.spotlight('novo-recurso', config);
  }
}
```

Ao encerrar, `stopped$` emite com `reason: 'dismissed'` (usuário fechou) ou `reason: 'completed'` (clicou em "Entendi"). Quando `doNotShowAgain` é `true`, salve essa preferência para não exibir novamente.

---

## `SpotlightStep`

| Propriedade | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `stepId` | `string` | ✓ | Identificador único do passo. Deve corresponder ao valor do `sSpotlightStep` no template. |
| `title` | `string \| TemplateRef<any>` | ✓ | Título do popover (máx. recomendado: 30 caracteres). |
| `message` | `string \| TemplateRef<any>` | ✓ | Texto descritivo (máx. recomendado: 75 caracteres). |
| `position` | `SpotlightPosition` | — | Posição preferida do popover. Padrão: `'bottom-center'`. |
| `content` | `TemplateRef<any> \| null` | — | Conteúdo visual customizado (imagem, gif, vídeo) exibido acima da mensagem. |
| `actions` | `SpotlightStepAction[]` | — | Botões de ação customizados exibidos antes dos botões de navegação. |
| `showBackdrop` | `boolean` | — | Exibe o backdrop escurecido ao redor do elemento destacado. Padrão: `true`. |
| `dismissible` | `boolean` | — | Permite fechar o spotlight clicando fora ou pressionando Escape. Padrão: `true`. |
| `beforeNext` | `() => void \| Promise<void>` | — | Callback executado antes de avançar para o próximo passo. Suporta async/await. |
| `beforePrevious` | `() => void \| Promise<void>` | — | Callback executado antes de voltar ao passo anterior. Suporta async/await. |

## `SpotlightConfig`

Equivalente ao `SpotlightStep` sem `stepId`, `beforeNext` e `beforePrevious`. Usado exclusivamente com `tourService.spotlight()`. Inclui a propriedade adicional:

| Propriedade | Tipo | Descrição |
|---|---|---|
| `showDoNotShowAgain` | `boolean` | Exibe o checkbox "Não mostrar novamente". Padrão: `true`. Passe `false` para suprimir. |

## `SpotlightStepAction`

| Propriedade | Tipo | Descrição |
|---|---|---|
| `label` | `string` | Texto do botão. |
| `handler` | `() => void` | Função executada ao clicar no botão. |

---

## `SpotlightTourService`

### Propriedades

| Propriedade | Tipo | Descrição |
|---|---|---|
| `isActive` | `Signal<boolean>` | `true` enquanto o tour estiver ativo. |
| `currentStep` | `Signal<SpotlightStep \| undefined>` | Passo atualmente exibido. |
| `currentIndex` | `Signal<number>` | Índice do passo atual (base 0). |
| `totalSteps` | `Signal<number>` | Total de passos do tour. |
| `stopped$` | `Observable<SpotlightStopEvent>` | Emite quando o tour é encerrado. |
| `stepChanged$` | `Observable<SpotlightStepChangedEvent>` | Emite ao mudar de passo (incluindo o primeiro). |

### Métodos

| Método | Descrição |
|---|---|
| `start(steps: SpotlightStep[])` | Inicia o tour com a lista de passos fornecida. Ignora arrays vazios. Se houver um tour ativo, encerra-o (emitindo `stopped$`) antes de iniciar o novo. |
| `spotlight(stepId, config: SpotlightConfig)` | Exibe um destaque simples em um único elemento. |
| `next(doNotShowAgain?: boolean)` | Avança para o próximo passo ou conclui o tour. |
| `previous()` | Volta ao passo anterior. |
| `stop(event?: Partial<SpotlightStopEvent>)` | Encerra o tour programaticamente. |
| `registerElement(stepId, el)` | Registra um elemento (usado internamente pela diretiva). |
| `unregisterElement(stepId)` | Remove o registro de um elemento. |
| `getElement(stepId)` | Retorna o `ElementRef` registrado para o `stepId`. |

---

## Eventos (`SpotlightStopEvent`)

Emitido por `stopped$` ao encerrar o tour.

| Campo | Tipo | Descrição |
|---|---|---|
| `reason` | `SpotlightStopReason` | Razão do encerramento. |
| `doNotShowAgain` | `boolean` | `true` se o usuário marcou "Não mostrar novamente". |
| `stepId` | `string` | `stepId` do passo em que o tour foi encerrado. |
| `stepIndex` | `number` | Índice (base 0) do passo em que o tour foi encerrado. |
| `totalSteps` | `number` | Total de passos do tour. |

### `SpotlightStopReason`

| Valor | Quando ocorre | Comportamento recomendado |
|---|---|---|
| `'completed'` | Usuário clicou em "Concluído" ou "Entendi" | Nunca reexibir |
| `'interrupted'` | Usuário fechou o tour antes de concluir (X, Escape, clique fora) | Reexibir na próxima sessão |
| `'dismissed'` | Usuário fechou um destaque simples | Reexibir após cooldown |

### `SpotlightStepChangedEvent`

Emitido por `stepChanged$` a cada mudança de passo.

| Campo | Tipo | Descrição |
|---|---|---|
| `stepId` | `string` | `stepId` do novo passo. |
| `stepIndex` | `number` | Índice (base 0) do novo passo. |
| `totalSteps` | `number` | Total de passos do tour. |

---

## Posições Disponíveis (`SpotlightPosition`)

| Valor | Descrição |
|---|---|
| `'top-start'` | Acima do elemento, alinhado à esquerda |
| `'top-center'` | Acima do elemento, centralizado |
| `'top-end'` | Acima do elemento, alinhado à direita |
| `'bottom-start'` | Abaixo do elemento, alinhado à esquerda |
| `'bottom-center'` | Abaixo do elemento, centralizado |
| `'bottom-end'` | Abaixo do elemento, alinhado à direita |
| `'left-start'` | À esquerda do elemento, alinhado ao topo |
| `'left-center'` | À esquerda do elemento, centralizado |
| `'left-end'` | À esquerda do elemento, alinhado à base |
| `'right-start'` | À direita do elemento, alinhado ao topo |
| `'right-center'` | À direita do elemento, centralizado |
| `'right-end'` | À direita do elemento, alinhado à base |

> O CDK ajusta automaticamente a posição quando não há espaço suficiente, garantindo que o popover sempre permaneça visível.

---

## Persistência

A persistência do estado (se o tour já foi visto, preferência "Não mostrar novamente", etc.) é responsabilidade do produto consumidor. Use os dados emitidos por `stopped$` para implementar a estratégia adequada:

```typescript
this.tourService.stopped$.subscribe((event) => {
  const estado = {
    reason: event.reason,
    doNotShowAgain: event.doNotShowAgain,
    contentVersion: 'v2', // versão do conteúdo do tour, controlada pelo produto
  };
  localStorage.setItem('tour-onboarding', JSON.stringify(estado));
});

// Na inicialização:
const salvo = JSON.parse(localStorage.getItem('tour-onboarding') ?? 'null');
const deveExibir =
  !salvo ||
  salvo.contentVersion !== 'v2' ||          // novo conteúdo → reexibir
  salvo.reason === 'interrupted';            // não concluiu → reexibir

if (deveExibir && !salvo?.doNotShowAgain) {
  // Chamar tourService.start(passos) no momento adequado
}
```

---

## Telemetria

Use os observables do serviço para enviar eventos ao seu sistema de analytics:

```typescript
// Tour iniciado (step 0)
this.tourService.stepChanged$
  .pipe(filter((e) => e.stepIndex === 0 && e.totalSteps > 1))
  .subscribe(() => analytics.track('tour_viewed'));

// Tour concluído
this.tourService.stopped$
  .pipe(filter((e) => e.reason === 'completed' && e.totalSteps > 1))
  .subscribe(() => analytics.track('tour_completed'));

// Tour interrompido
this.tourService.stopped$
  .pipe(filter((e) => e.reason === 'interrupted'))
  .subscribe(() => analytics.track('tour_interrupted'));

// Destaque simples fechado
this.tourService.stopped$
  .pipe(filter((e) => e.reason === 'dismissed'))
  .subscribe(() => analytics.track('spotlight_dismissed'));

// "Não mostrar novamente" marcado
this.tourService.stopped$
  .pipe(filter((e) => e.doNotShowAgain))
  .subscribe(() => analytics.track('spotlight_do_not_show_again_checked'));
```
