/**
 * @zync/modules — Dependency graph helpers.
 *
 * Pure functions — no DB access. Consumed by both the API (server authority)
 * and the frontend app (UI preview / store).
 */
import { type ModuleId, MODULE_BY_ID } from './manifest'

/**
 * Depth-first traversal collecting every module that hard-depends (transitively)
 * on `moduleId`, restricted to modules currently in `enabledModules`.
 * Excludes `moduleId` itself. Order = DFS discovery order.
 */
export function getCascadeDisables(
  moduleId: ModuleId,
  enabledModules: ModuleId[],
): ModuleId[] {
  const enabledSet = new Set(enabledModules)
  const result: ModuleId[] = []
  const visited = new Set<ModuleId>([moduleId])

  function dfs(current: ModuleId): void {
    // Walk all module definitions and find those with a hard dep on `current`
    for (const id of enabledModules) {
      if (visited.has(id)) continue
      const def = MODULE_BY_ID[id]
      if (!def) continue
      const hasHardDep = def.dependencies.some(
        (dep) => dep.kind === 'hard' && dep.moduleId === current,
      )
      if (hasHardDep && enabledSet.has(id)) {
        visited.add(id)
        result.push(id)
        dfs(id)
      }
    }
  }

  dfs(moduleId)
  return result
}

/**
 * Collect all currently-enabled modules that lose functionality (soft deps)
 * across the target module plus all transitive cascade modules.
 *
 * De-duplicates by dependent `moduleId` (first impact wins).
 * Excludes any module that is itself in the hard-cascade disable set —
 * a module being auto-disabled must never also appear as a soft "loses functionality" entry.
 */
export function getSoftImpacts(
  moduleId: ModuleId,
  enabledModules: ModuleId[],
): Array<{ moduleId: ModuleId; impactDescription: string }> {
  const enabledSet = new Set(enabledModules)
  // The full set of modules being disabled (target + cascade)
  const cascadeSet = new Set([moduleId, ...getCascadeDisables(moduleId, enabledModules)])

  const seen = new Set<ModuleId>()
  const result: Array<{ moduleId: ModuleId; impactDescription: string }> = []

  // For each module being disabled, find enabled modules that soft-depend on it
  for (const disabledId of cascadeSet) {
    for (const id of enabledModules) {
      // Skip if this module is itself being disabled
      if (cascadeSet.has(id)) continue
      if (!enabledSet.has(id)) continue
      if (seen.has(id)) continue

      const def = MODULE_BY_ID[id]
      if (!def) continue

      const softDep = def.dependencies.find(
        (dep) => dep.kind === 'soft' && dep.moduleId === disabledId,
      )
      if (softDep) {
        seen.add(id)
        result.push({ moduleId: id, impactDescription: softDep.impactDescription })
      }
    }
  }

  return result
}

/**
 * Returns whether a module can be enabled given the current `enabledModules` set.
 * A module can be enabled iff all its hard dependency modules are in `enabledModules`.
 */
export function canEnable(
  moduleId: ModuleId,
  enabledModules: ModuleId[],
): { allowed: boolean; missingHardDeps: ModuleId[] } {
  const def = MODULE_BY_ID[moduleId]
  if (!def) return { allowed: false, missingHardDeps: [] }

  const enabledSet = new Set(enabledModules)
  const missingHardDeps: ModuleId[] = def.dependencies
    .filter((dep) => dep.kind === 'hard' && !enabledSet.has(dep.moduleId))
    .map((dep) => dep.moduleId)

  return {
    allowed: missingHardDeps.length === 0,
    missingHardDeps,
  }
}
