/**
 * Runtime brand attachment + predicates. The companion to the phantom-type
 * brands defined in `./brands.ts`: at compile time the brand is a structural
 * intersection; at runtime it is a non-enumerable, non-writable,
 * non-configurable property with the same name.
 *
 * Why non-enumerable: a wrapped function should not leak the marker through
 * `Object.keys`, `JSON.stringify`, `for…in`, or spread. The marker only
 * shows up to explicit lookups via `Reflect.has` or direct property access.
 *
 * Why non-writable + non-configurable: the marker is meant to be permanent
 * once attached. The wrapper is the only caller; the marker is a one-shot
 * commitment, not a mutable flag.
 */

import type { Analyzed, ConsentChecked, RateLimited } from "./brands";

type Brand =
  | "__instrumented"
  | "__captured"
  | "__audited"
  | "__analyzed"
  | "__consentChecked"
  | "__rateLimited";

/**
 * Attaches the brand as a non-enumerable property on the given function.
 * Returns the same reference (no allocation). Calling twice with the same
 * brand on the same fn is a no-op (matching descriptors) — the engine silently
 * accepts a redundant `defineProperty` call when every descriptor attribute
 * matches the existing one. Redefining with different attributes (e.g. flipping
 * `configurable`) would throw, but wrappers never do that.
 */
export function attachBrand<F extends object>(fn: F, brand: Brand): F {
  Object.defineProperty(fn, brand, {
    value: true,
    enumerable: false,
    writable: false,
    configurable: false,
  });
  return fn;
}

function hasBrand(fn: unknown, brand: Brand): boolean {
  if (typeof fn !== "function" && (typeof fn !== "object" || fn === null)) {
    return false;
  }
  return (fn as Record<string, unknown>)[brand] === true;
}

export function isInstrumented(fn: unknown): boolean {
  return hasBrand(fn, "__instrumented");
}

export function isCaptured(fn: unknown): boolean {
  return hasBrand(fn, "__captured");
}

export function isAudited(fn: unknown): boolean {
  return hasBrand(fn, "__audited");
}

export function isAnalyzed<F extends object>(fn: unknown): fn is Analyzed<F> {
  return hasBrand(fn, "__analyzed");
}

export function isConsentChecked<F extends object>(
  fn: unknown,
): fn is ConsentChecked<F> {
  return hasBrand(fn, "__consentChecked");
}

export function isRateLimited<F extends object>(
  fn: unknown,
): fn is RateLimited<F> {
  return hasBrand(fn, "__rateLimited");
}
