// Uploaded brand-asset overrides — the `brand_assets` settings row.
//
//   { "logo": "/uploads/1234-ab.png", "hero": null, "favicon": null }
//
// Each field, once set, holds a stored upload URL; a null or absent field falls
// back to brand.logo / brand.hero / brand.favicon from BRAND_* env. Uploading a
// logo does not force the admin to also pick a hero
// (docs/website/THEMING_AND_NAV.md §6.3).
//
// These values are the only part of the settings store that is written straight
// into HTML the browser then fetches — an <img src>, a <link rel="icon">, an
// og:image. So the accepted shape is deliberately narrow: a same-origin path
// under one of the three directories this app serves, and nothing else. No
// scheme, no protocol-relative `//host`, no `..`. The upload route only ever
// produces `/uploads/…`, so the other two prefixes exist for an admin who wants
// to point at an asset already baked into the image or mounted at /brand.
//
// Same strict-on-write / forgiving-on-read asymmetry as the theme
// (utils/themeResolve.js): a bad write is rejected with the field named, while a
// bad *stored* value is dropped field by field so a hand-edited row degrades to
// the env default instead of rendering a broken page.

// The three overridable assets, in the order the admin UI shows them.
const SLOTS = ['logo', 'hero', 'favicon']

// Directories this server actually serves: /uploads (UPLOAD_DIR), /brand
// (BRAND_DIR, optional) and /assets (the built SPA's static files).
const ALLOWED_PREFIXES = ['/uploads/', '/brand/', '/assets/']

/**
 * Is this a value we are willing to emit as a URL into the page?
 * @param {unknown} value
 * @returns {boolean}
 */
function isSafeAssetPath(value) {
  if (typeof value !== 'string' || value === '') return false
  // A leading `//` is protocol-relative and would load from another origin
  // despite looking like a path; `..` could climb out of the served directory.
  if (value.startsWith('//') || value.includes('..')) return false
  // Whitespace and control characters have no place in a stored path and are the
  // raw material for `javascript:` smuggling past a naive prefix check.
  if (/[\s<>"'\\]/.test(value)) return false
  return ALLOWED_PREFIXES.some((prefix) => value.startsWith(prefix))
}

/**
 * Validate a brand_assets object for WRITING. Strict: names the offending field.
 * @param {unknown} value  the parsed object (or null to clear every slot)
 * @returns {{ok: true} | {ok: false, message: string}}
 */
function validateBrandAssets(value) {
  if (value === null || value === undefined) return { ok: true }
  if (typeof value !== 'object' || Array.isArray(value)) {
    return { ok: false, message: 'brand_assets must be a JSON object' }
  }
  for (const [slot, url] of Object.entries(value)) {
    if (!SLOTS.includes(slot)) {
      return { ok: false, message: `Unknown brand asset '${slot}'` }
    }
    // null/'' is how a slot is cleared back to the env default — allowed, and
    // stripped by the caller so the stored row never carries dead fields.
    if (url === null || url === '') continue
    if (!isSafeAssetPath(url)) {
      return {
        ok: false,
        message: `brand_assets.${slot} must be an uploaded path under /uploads/, /brand/ or /assets/`,
      }
    }
  }
  return { ok: true }
}

/**
 * Keep only the slots that hold a usable path. Serves both directions on
 * purpose:
 *
 *   • writing — an admin who removes their logo stores `{}` (and the caller
 *     deletes the row entirely) rather than a row full of nulls, which would
 *     read as "set to nothing" rather than "never set";
 *   • reading — an unusable stored field is dropped and its neighbours kept, so
 *     one bad slot cannot cost the admin the other two.
 *
 * @param {object|null} value  an object, or a parseJsonSetting result
 * @returns {{logo?: string, hero?: string, favicon?: string}}
 */
function resolveBrandAssets(value) {
  const out = {}
  if (!value || typeof value !== 'object') return out
  for (const slot of SLOTS) {
    if (isSafeAssetPath(value[slot])) out[slot] = value[slot]
  }
  return out
}

module.exports = { SLOTS, ALLOWED_PREFIXES, isSafeAssetPath, validateBrandAssets, resolveBrandAssets }
