// ── Admin · Shard visibility ───────────────────────────────────────────────
//
// Read/write the per-feature audience config that gates every shard-derived
// surface. Admin-only: this decides what anonymous visitors can see, so it is
// not part of the moderator tier.
//
// The policy itself (the ladder, the feature catalog, which fields are locked)
// lives in utils/shardVisibility.js. This controller only validates input
// against that policy and persists it.

const model = require('../../../model/shardVisibility/shardVisibility.model')
const visibility = require('../../../utils/shardVisibility')
const log = require('../../../utils/logger')('admin-shard-visibility')

// GET /admin/shard/visibility — the effective config (defaults merged with any
// stored overrides), plus the vocabulary the admin UI needs to render itself:
// the ladder, and which fields each feature exposes as configurable.
async function getVisibility(req, res) {
  try {
    const config = await visibility.getConfig()
    return res.json({
      ladder: visibility.LADDER,
      lockedFields: Object.keys(visibility.LOCKED_FIELDS),
      defaults: visibility.compileDefaults(),
      features: config,
    })
  } catch (err) {
    log.error('getVisibility', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// PUT /admin/shard/visibility — replace the settings for one or more features.
// Body: { features: { <name>: { enabled, audience, stream, fieldRules } } }
//
// Rejects unknown feature names, unknown rungs, and any attempt to configure a
// locked field — a 400 rather than a silent drop, so an admin who tries to make
// `acct` public learns that it is not negotiable.
async function putVisibility(req, res) {
  try {
    const incoming = req.body?.features
    if (!incoming || typeof incoming !== 'object' || Array.isArray(incoming)) {
      return res.status(400).json({ message: 'features object required' })
    }

    const entries = []
    for (const [name, patch] of Object.entries(incoming)) {
      if (!visibility.isFeature(name)) {
        return res.status(400).json({ message: `Unknown feature: ${name}` })
      }
      if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
        return res.status(400).json({ message: `Invalid settings for ${name}` })
      }
      if (patch.audience != null && !visibility.isLevel(patch.audience)) {
        return res.status(400).json({ message: `Unknown audience for ${name}: ${patch.audience}` })
      }

      const fieldRules = {}
      for (const [field, level] of Object.entries(patch.fieldRules || {})) {
        // Matches flattened spellings too (`ownerAcct`, `leaderWebId`), so the
        // rejection covers every way the field can be named rather than the two
        // canonical keys.
        if (visibility.isLockedField(field)) {
          return res.status(400).json({ message: `Field '${field}' is admin-only and cannot be configured` })
        }
        if (!visibility.isLevel(level)) {
          return res.status(400).json({ message: `Unknown rung for ${name}.${field}: ${level}` })
        }
        fieldRules[field] = level
      }

      const current = (await visibility.getConfig())[name]
      entries.push({
        feature: name,
        enabled: patch.enabled == null ? current.enabled : !!patch.enabled,
        audience: patch.audience ?? current.audience,
        stream: patch.stream == null ? current.stream : !!patch.stream,
        fieldRules,
        updatedBy: req.user?.id ?? null,
      })
    }

    for (const entry of entries) await model.upsert(entry)
    visibility.invalidate()

    log.info('shard visibility updated', {
      by: req.user?.id,
      features: entries.map((e) => e.feature),
    })

    return res.json({ features: await visibility.getConfig() })
  } catch (err) {
    log.error('putVisibility', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

module.exports = { getVisibility, putVisibility }
