/**
 * Privacy Settings contract.
 *
 * Privacy settings control player opt-in/out for automation features that affect
 * their on-screen presence. Settings are stored as user flags on the user document.
 *
 * Storage key: game.user.setFlag('scrying-pool', key, value)
 * Shape: { reactionCamEnabled: boolean, customPortraitFallback: string | null }
 *
 * @module contracts/privacy-settings
 */

/**
 * Maximum portrait file size in bytes (5MB).
 * Note: FoundryVTT user flags typically have a ~50KB limit, so images should be optimized.
 * @type {number}
 */
export const MAX_PORTRAIT_SIZE = 5 * 1024 * 1024;

/**
 * Supported portrait image MIME types.
 * @type {string[]}
 */
export const VALID_PORTRAIT_FORMATS = Object.freeze([
  "image/png",
  "image/jpeg",
  "image/webp",
  "image/gif",
]);

/**
 * @typedef {Object} PrivacySettings
 * @property {boolean} reactionCamEnabled - Whether Reaction Cam automation is enabled for this user.
 * @property {string|null} customPortraitFallback - DataURL string for custom portrait fallback image, or null if not set.
 */

export const PRIVACY_SETTINGS_VERSION = 1;

/**
 * Default privacy settings - all features disabled by default (opt-in, not opt-out).
 * @type {PrivacySettings}
 */
export const PRIVACY_SETTINGS_DEFAULT = {
  reactionCamEnabled: false,
  customPortraitFallback: null,
};

/**
 * Canonical list of privacy setting keys.
 * @type {string[]}
 */
export const PRIVACY_SETTING_KEYS = Object.freeze([
  "reactionCamEnabled",
  "customPortraitFallback",
]);

/**
 * Feature name mapping for opt-in checks.
 * @type {Object.<string, string>}
 */
export const FEATURE_NAME_MAP = Object.freeze({
  reactionCam: "reactionCamEnabled",
});

/**
 * Validates a DataURL for portrait images.
 * Accepts DataURLs with supported MIME types or null/undefined/empty string.
 * @param {unknown} dataURL - The DataURL string to validate.
 * @returns {string|null|undefined} The validated DataURL (or null/undefined if valid).
 * @throws {TypeError} If the DataURL format is invalid or uses unsupported MIME type.
 */
export function validatePortraitDataURL(dataURL) {
  // Accept null, undefined, or empty string as valid (no custom portrait)
  if (dataURL === null || dataURL === undefined) {
    return dataURL;
  }
  if (typeof dataURL !== "string") {
    throw new TypeError(`Invalid DataURL: expected string, got ${typeof dataURL}`);
  }
  if (dataURL === "") {
    return dataURL;
  }

  // Validate DataURL format: must start with "data:"
  if (!dataURL.startsWith("data:")) {
    throw new TypeError("Invalid DataURL format: must start with 'data:'");
  }

  // Extract MIME type from DataURL (format: data:<mediatype>;base64,... or data:<mediatype>,...)
  // Match image MIME type after data: (captures the part before ; or ,)
  const mimeMatch = dataURL.match(/^data:(image\/[a-zA-Z0-9+\-.]+)/);
  if (!mimeMatch) {
    throw new TypeError("Invalid DataURL format: missing or invalid MIME type");
  }

  const mimeType = mimeMatch[1].toLowerCase();

  // Validate against supported formats
  if (!VALID_PORTRAIT_FORMATS.includes(mimeType)) {
    throw new TypeError(
      `Unsupported portrait format: ${mimeType}. Supported: ${VALID_PORTRAIT_FORMATS.join(", ")}`
    );
  }

  // Verify actual data content exists after MIME header
  const dataPart = dataURL.substring(dataURL.indexOf(",") + 1);
  if (!dataPart || dataPart.length === 0) {
    throw new TypeError("Invalid DataURL: empty image data");
  }

  return dataURL;
}

/**
 * Creates a new PrivacySettings object with defaults.
 * Only known keys from PRIVACY_SETTINGS_DEFAULT are included; extra properties are ignored.
 * @param {Partial<PrivacySettings>} [overrides={}] - Override default values.
 * @returns {PrivacySettings}
 */
export function createPrivacySettings(overrides = {}) {
  const result = { ...PRIVACY_SETTINGS_DEFAULT };
  for (const key of PRIVACY_SETTING_KEYS) {
    if (key in overrides) {
      result[key] = overrides[key];
    }
  }
  return result;
}

/**
 * Validates a PrivacySettings DTO. Throws TypeError on any violation.
 * Backward compatible: accepts settings without customPortraitFallback key.
 * @param {unknown} data - Value to validate.
 * @returns {PrivacySettings} The validated settings.
 * @throws {TypeError} If data fails validation.
 */
export function isValidPrivacySettings(data) {
  if (data === null) {
    throw new TypeError("PrivacySettings: must be an object");
  }
  if (typeof data !== "object" || Array.isArray(data)) {
    throw new TypeError("PrivacySettings: must be an object");
  }
  const obj = /** @type {Record<string, unknown>} */ (data);

  // Check for unknown keys (keys not in PRIVACY_SETTING_KEYS)
  const knownKeys = new Set(PRIVACY_SETTING_KEYS);
  const unknownKeys = Object.keys(obj).filter((k) => !knownKeys.has(k));
  if (unknownKeys.length > 0) {
    throw new TypeError(
      `PrivacySettings: unknown keys: ${unknownKeys.join(", ")}`
    );
  }

  // Validate each known key if present
  if ("reactionCamEnabled" in obj) {
    if (typeof obj.reactionCamEnabled !== "boolean") {
      throw new TypeError(
        `PrivacySettings: reactionCamEnabled must be a boolean, got ${typeof obj.reactionCamEnabled}`
      );
    }
  }
  if ("customPortraitFallback" in obj) {
    if (obj.customPortraitFallback !== null && typeof obj.customPortraitFallback !== "string") {
      throw new TypeError(
        `PrivacySettings: customPortraitFallback must be a string or null, got ${typeof obj.customPortraitFallback}`
      );
    }
    // If it's a string, validate it's a valid DataURL
    if (typeof obj.customPortraitFallback === "string") {
      try {
        validatePortraitDataURL(obj.customPortraitFallback);
      } catch (e) {
        throw new TypeError(
          `PrivacySettings: customPortraitFallback ${e.message}`
        );
      }
    }
  }

  return /** @type {PrivacySettings} */ (obj);
}

/**
 * Validates a single privacy setting key.
 * @param {string} key - The setting key to validate.
 * @returns {string} The validated key.
 * @throws {TypeError} If key is invalid.
 */
export function validateSettingKey(key) {
  if (typeof key !== "string" || key.length === 0) {
    throw new TypeError("Setting key must be a non-empty string");
  }
  if (!PRIVACY_SETTING_KEYS.includes(key)) {
    throw new TypeError(
      `PrivacySettings: unknown key '${key}'. Valid keys: ${PRIVACY_SETTING_KEYS.join(
        ", "
      )}`
    );
  }
  return key;
}

/**
 * Validates a single privacy setting value.
 * @param {unknown} value - The value to validate.
 * @returns {boolean} The validated value.
 * @throws {TypeError} If value is invalid.
 */
export function validateSettingValue(value) {
  if (typeof value !== "boolean") {
    throw new TypeError(
      `PrivacySettings: value must be a boolean, got ${typeof value}`
    );
  }
  return value;
}

/**
 * Validates a feature name for opt-in checks.
 * @param {string} feature - The feature name to validate.
 * @returns {string} The validated feature name.
 * @throws {TypeError} If feature is invalid.
 */
export function validateFeatureName(feature) {
  if (typeof feature !== "string" || feature.length === 0) {
    throw new TypeError("Feature name must be a non-empty string");
  }
  const validFeatures = Object.keys(FEATURE_NAME_MAP);
  if (!validFeatures.includes(feature)) {
    throw new TypeError(
      `PrivacySettings: unknown feature '${feature}'. Valid features: ${validFeatures.join(
        ", "
      )}`
    );
  }
  return feature;
}
