/**
 * Brand metadata helpers — multi-location + categorized service arrays.
 *
 * The Brand model carries both legacy single-string fields
 * (`location`, `serviceType`) and the newer array fields
 * (`locations`, `primaryServices`, `secondaryServices`). During the
 * rollout window, consumers should read via these helpers so they
 * see the array values when present and fall back to the legacy
 * single-string fields when the arrays are still empty (e.g. a brand
 * that pre-dates the migration whose backfill hasn't run yet).
 *
 * Once every consumer has migrated, the legacy fields can be dropped
 * (BACKLOG-002).
 */

/** Sanity caps applied at every API boundary that accepts brand
 *  array fields. Catches accidental paste-bombs and oversized
 *  values without trimming legitimate brand metadata. */
export const MAX_BRAND_ARRAY_ENTRIES = 20;
export const MAX_BRAND_ARRAY_ENTRY_LEN = 200;

export type SanitizeResult =
  | { ok: true; value: string[] }
  | { ok: false; error: string };

/**
 * Trim, drop empties, dedupe (case-sensitive), and validate caps on
 * a candidate string array. Returns a normalized array on success or
 * a structured error on failure. Used by every endpoint that accepts
 * locations / primaryServices / secondaryServices to keep validation
 * consistent.
 */
export function sanitizeBrandArray(input: unknown, fieldName: string): SanitizeResult {
  if (input == null) return { ok: true, value: [] };
  if (!Array.isArray(input)) {
    return { ok: false, error: `${fieldName} must be an array of strings` };
  }
  if (input.length > MAX_BRAND_ARRAY_ENTRIES) {
    return { ok: false, error: `${fieldName} must have at most ${MAX_BRAND_ARRAY_ENTRIES} entries` };
  }
  const seen = new Set<string>();
  const out: string[] = [];
  for (const raw of input) {
    if (typeof raw !== "string") {
      return { ok: false, error: `${fieldName} must contain only strings` };
    }
    const trimmed = raw.trim();
    if (!trimmed) continue;
    if (trimmed.length > MAX_BRAND_ARRAY_ENTRY_LEN) {
      return { ok: false, error: `${fieldName} entry exceeds ${MAX_BRAND_ARRAY_ENTRY_LEN} characters` };
    }
    if (seen.has(trimmed)) continue;
    seen.add(trimmed);
    out.push(trimmed);
  }
  return { ok: true, value: out };
}

/** Minimal shape these helpers need from a Brand row. Kept narrow so
 *  callers can pass partial selects without a type widen. */
export interface BrandLocationFields {
  locations?: string[] | null;
  location?: string | null;
}

export interface BrandServiceFields {
  primaryServices?: string[] | null;
  secondaryServices?: string[] | null;
  serviceType?: string | null;
}

/**
 * Read the brand's locations as an array. Prefers `locations` when
 * non-empty; falls back to wrapping the legacy `location` string in
 * an array; returns [] when neither is set.
 */
export function getBrandLocations(brand: BrandLocationFields): string[] {
  if (brand.locations && brand.locations.length > 0) return brand.locations;
  if (brand.location && brand.location.trim()) return [brand.location.trim()];
  return [];
}

/**
 * Read the brand's services split into primary / secondary buckets,
 * with a flat `combined` view for convenience. Prefers the array
 * fields when at least one is non-empty. When both arrays are empty
 * but the legacy `serviceType` is set, splits it on commas — the
 * first non-empty token becomes the primary entry and the rest go
 * into secondary, mirroring the rule the backfill endpoint uses.
 */
export function getBrandServices(
  brand: BrandServiceFields,
): { primary: string[]; secondary: string[]; combined: string[] } {
  const primary = brand.primaryServices && brand.primaryServices.length > 0
    ? brand.primaryServices
    : [];
  const secondary = brand.secondaryServices && brand.secondaryServices.length > 0
    ? brand.secondaryServices
    : [];
  if (primary.length > 0 || secondary.length > 0) {
    return { primary, secondary, combined: [...primary, ...secondary] };
  }
  if (brand.serviceType && brand.serviceType.trim()) {
    const parts = brand.serviceType.split(",").map((s) => s.trim()).filter(Boolean);
    return {
      primary: parts.slice(0, 1),
      secondary: parts.slice(1),
      combined: parts,
    };
  }
  return { primary: [], secondary: [], combined: [] };
}

/**
 * Derive the legacy single-string `location` value from a (possibly
 * already-sanitized) locations array. First entry wins; null when
 * empty. Used at every write boundary to keep the legacy field in
 * sync with the array source of truth.
 */
export function deriveLegacyLocation(
  locations: string[],
  fallback: string | null | undefined,
): string | null {
  if (locations.length > 0) return locations[0];
  const trimmed = (fallback ?? "").trim();
  return trimmed || null;
}

/**
 * Derive the legacy single-string `serviceType` value from a
 * (possibly already-sanitized) primaryServices array. We deliberately
 * IGNORE secondaryServices here — legacy consumers reading
 * `Brand.serviceType` expected "the brand's main service offerings",
 * which maps to primary. Joining secondary into the legacy string
 * would over-broaden every legacy query. Falls back to the provided
 * legacy string when the array is empty; null when both are empty.
 */
export function deriveLegacyServiceType(
  primaryServices: string[],
  fallback: string | null | undefined,
): string | null {
  if (primaryServices.length > 0) return primaryServices.join(", ");
  const trimmed = (fallback ?? "").trim();
  return trimmed || null;
}
