# Proposal: Commander Middleware System

> Sistema de middleware/interceptor para `@darksnow-ui/commander` com suporte a filtros granulares.

## Visão Geral

Adicionar sistema de middleware inspirado em Express/Koa/Fastify que permite:

1. **Interceptar** comandos antes/depois da execução
2. **Cancelar** execução baseado em condições
3. **Modificar** input antes de executar
4. **Compor** middlewares reutilizáveis (logging, auth, debug, etc.)
5. **Filtrar** por comando, categoria, pattern ou função customizada

---

## API Proposta

### Registro de Middlewares

```typescript
// ============================================================================
// GLOBAL - roda em TODOS os comandos
// ============================================================================

commander.use(loggingMiddleware)
commander.use([analyticsMiddleware, errorTrackingMiddleware])

// ============================================================================
// POR CATEGORIA
// ============================================================================

commander.use("file", authMiddleware)
commander.use("admin", [authMiddleware, roleMiddleware("admin")])

// ============================================================================
// POR COMANDO ESPECÍFICO
// ============================================================================

commander.use("user:delete", confirmMiddleware)
commander.use("user:delete", [authMiddleware, confirmMiddleware, auditMiddleware])

// ============================================================================
// POR PATTERN (glob-like)
// ============================================================================

commander.use("file:*", auditMiddleware)           // file:save, file:delete, etc
commander.use("admin:*", adminOnlyMiddleware)      // admin:users, admin:settings
commander.use("api:v2:*", rateLimitMiddleware)     // api:v2:users, api:v2:products

// ============================================================================
// POR FILTRO CUSTOMIZADO
// ============================================================================

commander.use(
  (cmd) => cmd.tags?.includes("dangerous"),
  confirmMiddleware
)

commander.use(
  (cmd) => cmd.category === "file" && cmd.owner === "editor",
  [validateMiddleware, auditMiddleware]
)

// ============================================================================
// MÚLTIPLOS MATCHERS
// ============================================================================

commander.use(["file:*", "edit:*"], loggingMiddleware)

commander.use(
  ["user:delete", "data:purge", "system:reset"],
  [authMiddleware, confirmMiddleware]
)

// ============================================================================
// REMOÇÃO
// ============================================================================

commander.unuse(loggingMiddleware)                    // remove de todos os registros
commander.unuse("file:*", auditMiddleware)            // remove só desse matcher
commander.clearMiddlewares()                          // remove todos
commander.clearMiddlewares("admin:*")                 // remove todos de um matcher
```

---

## Tipos

```typescript
// ============================================================================
// MIDDLEWARE CONTEXT
// ============================================================================

export interface MiddlewareContext<TInput = any, TOutput = any> {
  /** O comando sendo executado */
  readonly command: Command<TInput, TOutput>

  /** Input original (imutável) */
  readonly input: TInput

  /** Input modificado (pode ser alterado pelo middleware) */
  modifiedInput?: TInput

  /** Fonte da execução */
  readonly source: "palette" | "shortcut" | "api"

  /** Timestamp do início */
  readonly startTime: Date

  /** Metadados extensíveis (middlewares podem adicionar dados) */
  meta: Record<string, any>

  /** Referência ao Commander */
  readonly commander: Commander
}

// ============================================================================
// MIDDLEWARE FUNCTION
// ============================================================================

export type NextFunction<TOutput = any> = () => Promise<TOutput>

export type Middleware<TInput = any, TOutput = any> = (
  ctx: MiddlewareContext<TInput, TOutput>,
  next: NextFunction<TOutput>
) => Promise<TOutput | MiddlewareCancelResult>

// ============================================================================
// CANCEL RESULT
// ============================================================================

export interface MiddlewareCancelResult {
  readonly cancelled: true
  readonly reason?: string
  readonly data?: any
}

export function isCancelled(result: any): result is MiddlewareCancelResult {
  return result && typeof result === "object" && result.cancelled === true
}

// ============================================================================
// MATCHER TYPES
// ============================================================================

export type MiddlewareMatcher =
  | "*"                                    // global
  | CommandCategory                        // "file", "edit", "admin"
  | CommandKey                             // "file:save"
  | `${string}:*`                          // pattern "file:*"
  | ((command: Command) => boolean)        // função customizada

export type MatcherInput = MiddlewareMatcher | MiddlewareMatcher[]
export type MiddlewareInput = Middleware | Middleware[]

// ============================================================================
// INTERNAL ENTRY
// ============================================================================

interface MiddlewareEntry {
  id: string
  matcher: MiddlewareMatcher
  middleware: Middleware
  priority: number  // para ordenação
}
```

---

## Ordem de Execução

Quando `invoke("file:save", input)` é chamado:

```
┌─────────────────────────────────────────────────────────────────┐
│                    MIDDLEWARE PIPELINE                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. GLOBAL middlewares (ordem de registro)                      │
│     └── loggingMiddleware                                       │
│     └── analyticsMiddleware                                     │
│                                                                 │
│  2. CATEGORY middlewares ("file")                               │
│     └── authMiddleware                                          │
│                                                                 │
│  3. PATTERN middlewares ("file:*")                              │
│     └── auditMiddleware                                         │
│                                                                 │
│  4. SPECIFIC middlewares ("file:save")                          │
│     └── validateMiddleware                                      │
│                                                                 │
│  5. CUSTOM FILTER middlewares (que matcham)                     │
│     └── (middlewares cujo filtro retorna true)                  │
│                                                                 │
│  6. COMMAND HANDLER                                             │
│     └── command.handle(finalInput)                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

**Prioridade de matching (do mais genérico ao mais específico):**

1. `"*"` (global)
2. `CommandCategory` ("file", "admin")
3. `Pattern` ("file:*")
4. `CommandKey` ("file:save")
5. `Function` (filtros customizados)

Dentro de cada grupo, executa na **ordem de registro**.

---

## Implementação

### Commander Class (modificações)

```typescript
export default class Commander {
  // ... propriedades existentes ...

  /** Middleware entries */
  private middlewareEntries: MiddlewareEntry[] = []
  private middlewareIdCounter = 0

  // ==========================================================================
  // MIDDLEWARE REGISTRATION
  // ==========================================================================

  /**
   * Registra middleware(s) com matcher opcional
   */
  use(middleware: MiddlewareInput): this
  use(matcher: MatcherInput, middleware: MiddlewareInput): this
  use(
    matcherOrMiddleware: MatcherInput | MiddlewareInput,
    middleware?: MiddlewareInput
  ): this {
    const matchers = middleware
      ? this.toArray(matcherOrMiddleware as MatcherInput)
      : ["*" as const]

    const middlewares = middleware
      ? this.toArray(middleware)
      : this.toArray(matcherOrMiddleware as MiddlewareInput)

    for (const matcher of matchers) {
      for (const mw of middlewares) {
        this.middlewareEntries.push({
          id: `mw_${++this.middlewareIdCounter}`,
          matcher,
          middleware: mw,
          priority: this.getMatcherPriority(matcher),
        })
      }
    }

    this.emit("middleware:added", { matchers, middlewares })
    return this
  }

  /**
   * Remove middleware(s)
   */
  unuse(middleware: Middleware): this
  unuse(matcher: MatcherInput, middleware: Middleware): this
  unuse(
    matcherOrMiddleware: MatcherInput | Middleware,
    middleware?: Middleware
  ): this {
    if (middleware) {
      // Remove de matchers específicos
      const matchers = this.toArray(matcherOrMiddleware as MatcherInput)
      this.middlewareEntries = this.middlewareEntries.filter(
        (entry) =>
          entry.middleware !== middleware ||
          !matchers.some((m) => this.matchersEqual(m, entry.matcher))
      )
    } else {
      // Remove de todos os registros
      const mw = matcherOrMiddleware as Middleware
      this.middlewareEntries = this.middlewareEntries.filter(
        (entry) => entry.middleware !== mw
      )
    }

    return this
  }

  /**
   * Remove todos os middlewares (opcionalmente de um matcher específico)
   */
  clearMiddlewares(matcher?: MatcherInput): this {
    if (matcher) {
      const matchers = this.toArray(matcher)
      this.middlewareEntries = this.middlewareEntries.filter(
        (entry) => !matchers.some((m) => this.matchersEqual(m, entry.matcher))
      )
    } else {
      this.middlewareEntries = []
    }
    return this
  }

  /**
   * Retorna middlewares registrados
   */
  getMiddlewares(matcher?: MiddlewareMatcher): MiddlewareEntry[] {
    if (matcher) {
      return this.middlewareEntries.filter((e) =>
        this.matchersEqual(matcher, e.matcher)
      )
    }
    return [...this.middlewareEntries]
  }

  // ==========================================================================
  // MIDDLEWARE MATCHING
  // ==========================================================================

  /**
   * Retorna prioridade do matcher (menor = executa primeiro)
   */
  private getMatcherPriority(matcher: MiddlewareMatcher): number {
    if (matcher === "*") return 0
    if (typeof matcher === "function") return 50
    if (matcher.endsWith(":*")) return 20
    if (["system", "file", "edit", "view", "debug", "tools", "custom"].includes(matcher)) return 10
    return 30 // comando específico
  }

  /**
   * Verifica se comando casa com matcher
   */
  private matchesCommand(matcher: MiddlewareMatcher, command: Command): boolean {
    if (matcher === "*") return true

    if (typeof matcher === "function") {
      try {
        return matcher(command)
      } catch {
        return false
      }
    }

    // Pattern: "file:*"
    if (matcher.endsWith(":*")) {
      const prefix = matcher.slice(0, -1) // "file:"
      return command.key.startsWith(prefix)
    }

    // Category match
    if (command.category === matcher) return true

    // Exact key match
    return command.key === matcher
  }

  /**
   * Compara dois matchers
   */
  private matchersEqual(a: MiddlewareMatcher, b: MiddlewareMatcher): boolean {
    if (typeof a === "function" || typeof b === "function") {
      return a === b // referência
    }
    return a === b
  }

  /**
   * Coleta middlewares que se aplicam ao comando
   */
  private getMiddlewaresForCommand(command: Command): Middleware[] {
    return this.middlewareEntries
      .filter((entry) => this.matchesCommand(entry.matcher, command))
      .sort((a, b) => a.priority - b.priority)
      .map((entry) => entry.middleware)
  }

  // ==========================================================================
  // EXECUTION WITH MIDDLEWARE
  // ==========================================================================

  async invoke<T = any>(
    key: CommandKey,
    input?: any,
    source: "palette" | "shortcut" | "api" = "api"
  ): Promise<T> {
    const command = this._commands.get(key)
    if (!command) {
      const error = new CommandNotFoundError(key)
      this.emit("command:error", key, error)
      throw error
    }

    // Build context
    const ctx: MiddlewareContext = {
      command,
      input,
      source,
      startTime: new Date(),
      meta: {},
      commander: this,
    }

    this.emit("command:executing", ctx)

    try {
      // Get applicable middlewares
      const middlewares = this.getMiddlewaresForCommand(command)

      // Execute through pipeline
      const result = await this.executePipeline<T>(ctx, middlewares)

      // Handle cancellation
      if (isCancelled(result)) {
        this.emit("command:cancelled", ctx, result)
        return result as T
      }

      // Track successful execution
      this.trackExecution(ctx, result)
      this.addToRecent(key)

      this.emit("command:completed", ctx, result)
      return result
    } catch (error) {
      const executionError =
        error instanceof Error
          ? new CommandExecutionError(key, error)
          : new CommandExecutionError(key, new Error(String(error)))

      this.emit("command:failed", ctx, executionError)
      throw executionError
    }
  }

  /**
   * Executa o pipeline de middlewares (Koa-style compose)
   */
  private async executePipeline<T>(
    ctx: MiddlewareContext,
    middlewares: Middleware[]
  ): Promise<T> {
    let index = 0

    const dispatch = async (): Promise<T> => {
      if (index < middlewares.length) {
        const middleware = middlewares[index++]
        return middleware(ctx, dispatch) as Promise<T>
      }

      // End of pipeline: execute command
      return this.executeCommand<T>(ctx)
    }

    return dispatch()
  }

  /**
   * Execução real do comando (após middlewares)
   */
  private async executeCommand<T>(ctx: MiddlewareContext): Promise<T> {
    const { command } = ctx

    // Availability check
    if (!(await this.isCommandAvailable(command))) {
      throw new CommandUnavailableError(command.key)
    }

    // Use modified input if available
    const finalInput = ctx.modifiedInput ?? ctx.input

    // Execute with timeout
    return this.executeWithTimeout<T>(command, finalInput)
  }

  // ==========================================================================
  // HELPERS
  // ==========================================================================

  private toArray<T>(value: T | T[]): T[] {
    return Array.isArray(value) ? value : [value]
  }
}
```

---

## Helpers de Middleware

```typescript
// src/middleware/helpers.ts

import type { Middleware, MiddlewareContext, MiddlewareCancelResult } from "../types"

/**
 * Cria middleware que executa ANTES do comando
 */
export function before<TInput = any>(
  fn: (ctx: MiddlewareContext<TInput>) => Promise<void | {
    input?: TInput
    cancel?: boolean
    reason?: string
  }>
): Middleware<TInput> {
  return async (ctx, next) => {
    const result = await fn(ctx)

    if (result?.cancel) {
      return { cancelled: true, reason: result.reason } as MiddlewareCancelResult
    }

    if (result?.input !== undefined) {
      ctx.modifiedInput = result.input
    }

    return next()
  }
}

/**
 * Cria middleware que executa DEPOIS do comando
 */
export function after<TInput = any, TOutput = any>(
  fn: (ctx: MiddlewareContext<TInput, TOutput>, result: TOutput) => Promise<TOutput | void>
): Middleware<TInput, TOutput> {
  return async (ctx, next) => {
    const result = await next()
    const modified = await fn(ctx, result)
    return modified ?? result
  }
}

/**
 * Cria middleware condicional
 */
export function when(
  condition: (ctx: MiddlewareContext) => boolean | Promise<boolean>,
  middleware: Middleware
): Middleware {
  return async (ctx, next) => {
    if (await condition(ctx)) {
      return middleware(ctx, next)
    }
    return next()
  }
}

/**
 * Compõe múltiplos middlewares em um
 */
export function compose(...middlewares: Middleware[]): Middleware {
  return async (ctx, next) => {
    let index = 0

    const dispatch = async (): Promise<any> => {
      if (index < middlewares.length) {
        const mw = middlewares[index++]
        return mw(ctx, dispatch)
      }
      return next()
    }

    return dispatch()
  }
}

/**
 * Middleware de logging
 */
export function logging(
  logger: (msg: string, data?: any) => void = console.log
): Middleware {
  return async (ctx, next) => {
    const start = Date.now()
    logger(`[Commander] → ${ctx.command.key}`, { input: ctx.input, source: ctx.source })

    try {
      const result = await next()
      logger(`[Commander] ✓ ${ctx.command.key} (${Date.now() - start}ms)`)
      return result
    } catch (error) {
      logger(`[Commander] ✗ ${ctx.command.key} (${Date.now() - start}ms)`, { error })
      throw error
    }
  }
}

/**
 * Middleware de analytics
 */
export function analytics(
  tracker: (event: string, data: Record<string, any>) => void
): Middleware {
  return async (ctx, next) => {
    const start = Date.now()

    try {
      const result = await next()
      tracker("command:success", {
        key: ctx.command.key,
        category: ctx.command.category,
        duration: Date.now() - start,
        source: ctx.source,
      })
      return result
    } catch (error) {
      tracker("command:error", {
        key: ctx.command.key,
        category: ctx.command.category,
        duration: Date.now() - start,
        source: ctx.source,
        error: error instanceof Error ? error.message : String(error),
      })
      throw error
    }
  }
}

/**
 * Middleware de rate limiting
 */
export function rateLimit(options: {
  maxPerSecond?: number
  maxPerMinute?: number
  keyFn?: (ctx: MiddlewareContext) => string
}): Middleware {
  const { maxPerSecond = 10, maxPerMinute = 100, keyFn = (ctx) => ctx.command.key } = options
  const counters = new Map<string, { second: number[]; minute: number[] }>()

  return async (ctx, next) => {
    const key = keyFn(ctx)
    const now = Date.now()

    if (!counters.has(key)) {
      counters.set(key, { second: [], minute: [] })
    }

    const counter = counters.get(key)!

    // Clean old entries
    counter.second = counter.second.filter((t) => now - t < 1000)
    counter.minute = counter.minute.filter((t) => now - t < 60000)

    // Check limits
    if (counter.second.length >= maxPerSecond) {
      return { cancelled: true, reason: "rate_limit_second" } as MiddlewareCancelResult
    }
    if (counter.minute.length >= maxPerMinute) {
      return { cancelled: true, reason: "rate_limit_minute" } as MiddlewareCancelResult
    }

    // Track
    counter.second.push(now)
    counter.minute.push(now)

    return next()
  }
}

/**
 * Middleware de confirmação
 */
export function confirm(
  promptFn: (ctx: MiddlewareContext) => Promise<boolean>
): Middleware {
  return async (ctx, next) => {
    const confirmed = await promptFn(ctx)

    if (!confirmed) {
      return { cancelled: true, reason: "user_cancelled" } as MiddlewareCancelResult
    }

    return next()
  }
}

/**
 * Middleware de autenticação
 */
export function auth(
  checkFn: () => boolean | Promise<boolean>,
  onUnauthorized?: (ctx: MiddlewareContext) => void
): Middleware {
  return async (ctx, next) => {
    const isAuthenticated = await checkFn()

    if (!isAuthenticated) {
      onUnauthorized?.(ctx)
      return { cancelled: true, reason: "unauthorized" } as MiddlewareCancelResult
    }

    return next()
  }
}

/**
 * Middleware de role/permission
 */
export function requireRole(
  role: string | string[],
  getRoles: () => string[] | Promise<string[]>
): Middleware {
  const requiredRoles = Array.isArray(role) ? role : [role]

  return async (ctx, next) => {
    const userRoles = await getRoles()
    const hasRole = requiredRoles.some((r) => userRoles.includes(r))

    if (!hasRole) {
      return { cancelled: true, reason: "forbidden", data: { requiredRoles } } as MiddlewareCancelResult
    }

    return next()
  }
}
```

---

## Exemplos de Uso

### 1. Setup Básico

```typescript
import { Commander } from "@darksnow-ui/commander"
import { logging, analytics, auth, confirm } from "@darksnow-ui/commander/middleware"

const commander = new Commander()

// Global: logging em tudo
commander.use(logging())

// Global: analytics
commander.use(analytics((event, data) => {
  mixpanel.track(event, data)
}))
```

### 2. Autenticação por Categoria

```typescript
// Comandos de file precisam de auth
commander.use("file", auth(
  () => !!currentUser,
  () => router.push("/login")
))

// Comandos admin precisam de role
commander.use("admin:*", [
  auth(() => !!currentUser),
  requireRole("admin", () => currentUser?.roles ?? [])
])
```

### 3. Confirmação para Ações Perigosas

```typescript
commander.use(
  ["user:delete", "data:purge", "system:reset"],
  confirm(async (ctx) => {
    return await showConfirmDialog({
      title: "Confirmar ação",
      message: `Deseja executar "${ctx.command.label}"?`,
      confirmText: "Sim, executar",
      cancelText: "Cancelar"
    })
  })
)
```

### 4. Debug Mode

```typescript
let debugEnabled = false

const debugMiddleware: Middleware = async (ctx, next) => {
  if (!debugEnabled) return next()
  if (ctx.command.key === "debug:toggle") return next()

  const action = await showDebugModal({
    command: ctx.command,
    input: ctx.input,
  })

  if (action.cancel) {
    return { cancelled: true, reason: "debug_cancelled" }
  }

  if (action.modifiedInput !== undefined) {
    ctx.modifiedInput = action.modifiedInput
  }

  return next()
}

commander.use(debugMiddleware)

// Comando para toggle
commander.add({
  key: "debug:toggle",
  label: "Toggle Debug Mode",
  shortcut: "alt+shift+d",
  handle: async () => {
    debugEnabled = !debugEnabled
    return { enabled: debugEnabled }
  }
})
```

### 5. Rate Limiting em APIs

```typescript
commander.use(
  "api:*",
  rateLimit({
    maxPerSecond: 5,
    maxPerMinute: 100,
  })
)
```

### 6. Middleware Condicional

```typescript
import { when } from "@darksnow-ui/commander/middleware"

// Só loga comandos em dev
commander.use(
  when(
    () => process.env.NODE_ENV === "development",
    logging()
  )
)

// Confirma só comandos marcados como dangerous
commander.use(
  (cmd) => cmd.tags?.includes("dangerous"),
  confirm(async (ctx) => {
    return await showDangerConfirm(ctx.command.label)
  })
)
```

---

## React Hook: useMiddleware

```typescript
// Hook para adicionar middleware temporário (cleanup no unmount)
export function useMiddleware(
  matcher: MatcherInput | MiddlewareInput,
  middleware?: MiddlewareInput
) {
  const { commander } = useCommander()

  useEffect(() => {
    if (middleware) {
      commander.use(matcher as MatcherInput, middleware)
    } else {
      commander.use(matcher as MiddlewareInput)
    }

    return () => {
      if (middleware) {
        const middlewares = Array.isArray(middleware) ? middleware : [middleware]
        middlewares.forEach((mw) => commander.unuse(matcher as MatcherInput, mw))
      } else {
        const middlewares = Array.isArray(matcher) ? matcher : [matcher as Middleware]
        middlewares.forEach((mw) => commander.unuse(mw))
      }
    }
  }, [commander]) // Sem deps para evitar re-registro
}

// Uso
function ProtectedArea() {
  useMiddleware("admin:*", authMiddleware)

  return <AdminPanel />
}
```

---

## Breaking Changes

**Nenhum!** A API existente continua funcionando:

- `invoke()` mantém mesma assinatura
- Eventos continuam sendo emitidos
- `attempt()` funciona igual
- Sem middlewares registrados = comportamento idêntico ao atual

---

## Checklist de Implementação

- [ ] Adicionar tipos de middleware em `types.ts`
- [ ] Adicionar `MiddlewareEntry` e propriedades no Commander
- [ ] Implementar `use()` com overloads
- [ ] Implementar `unuse()` com overloads
- [ ] Implementar `clearMiddlewares()`
- [ ] Implementar `getMiddlewares()`
- [ ] Implementar `getMatcherPriority()`
- [ ] Implementar `matchesCommand()`
- [ ] Implementar `getMiddlewaresForCommand()`
- [ ] Refatorar `invoke()` para usar pipeline
- [ ] Implementar `executePipeline()`
- [ ] Criar `src/middleware/helpers.ts`
- [ ] Criar `src/middleware/index.ts` (exports)
- [ ] Adicionar evento `command:cancelled`
- [ ] Testes unitários para middleware system
- [ ] Testes de integração
- [ ] Hook `useMiddleware`
- [ ] Documentação
- [ ] Exemplos

---

## Estrutura de Arquivos

```
src/
├── commander.ts          # Modificado com middleware system
├── types.ts              # Novos tipos de middleware
├── middleware/
│   ├── index.ts          # Exports públicos
│   ├── helpers.ts        # before, after, when, compose
│   ├── logging.ts        # logging middleware
│   ├── analytics.ts      # analytics middleware
│   ├── auth.ts           # auth, requireRole
│   ├── rateLimit.ts      # rate limiting
│   └── confirm.ts        # confirmation dialogs
├── hooks/
│   ├── ...existing...
│   └── useMiddleware.ts  # Novo hook
└── index.ts              # Atualizar exports
```

---

*Atualizado em: 2025-12-03*
*Autor: Anderson / Claude*
