/**
 * Registry of every settings field whose stored value is a credential.
 *
 * The admin API never returns these values. `sanitizeSettings` strips each one
 * and publishes a masked preview instead (`_meta.credentials[path]`), so the
 * admin UI can confirm *which* value is stored without ever receiving it.
 * Shared by the server (sanitize + save) and the client (settings tabs), so
 * this module must stay free of server-only imports.
 */

export interface CredentialFieldMeta {
  /** A non-empty value is stored in the database. */
  set: boolean;
  /** Masked preview of the stored value; absent in demo mode. */
  hint?: string;
}

export type CredentialMetaMap = Record<string, CredentialFieldMeta>;

/**
 * Dot-paths of credential fields, grouped by the settings section that owns
 * them. Identifiers (publishable keys, client IDs, wallet IDs…) are included
 * alongside outright secrets: they are still account-identifying values that
 * should not sit in plain sight on an admin screen.
 */
export const CREDENTIAL_FIELD_PATHS: readonly string[] = [
  // Payments
  "payment.stripe.publishableKey",
  "payment.stripe.secretKey",
  "payment.stripe.webhookSecret",
  "payment.paypal.clientId",
  "payment.paypal.clientSecret",
  "payment.paypal.webhookId",
  "payment.razorpay.keyId",
  "payment.razorpay.keySecret",
  "payment.razorpay.webhookSecret",
  "payment.paystack.publicKey",
  "payment.paystack.secretKey",
  "payment.pesapal.consumerKey",
  "payment.pesapal.consumerSecret",
  "payment.pesapal.ipnId",
  "payment.iotec.clientId",
  "payment.iotec.clientSecret",
  "payment.iotec.walletId",
  // OAuth / social login
  "security.googleClientId",
  "security.googleClientSecret",
  "security.facebookAppId",
  "security.facebookAppSecret",
  // Object storage
  "storage.accountId",
  "storage.accessKeyId",
  "storage.secretAccessKey",
  // Email delivery
  "email.smtp.password",
  "email.apiKey",
  // Analytics — the tracking IDs are account identifiers, so they are masked
  // like any other credential. (The storefront injects them from its own
  // cached settings getter, not from this payload, so tracking is unaffected.)
  "analytics.googleAnalyticsId",
  "analytics.googleTagManagerId",
  "analytics.facebookPixelId",
  "analytics.tiktokPixelId",
  "analytics.plausibleApiKey",
  // AI authoring
  "aiAuthoring.apiKey",
];

/**
 * Credential paths that are safe to echo back on save as an empty string. The
 * save handler drops these when blank so an untouched form never clears a
 * stored credential (the field renders empty by design — its value lives only
 * in the masked hint).
 */
export function credentialPathsForSection(section: string): string[] {
  const prefix = `${section}.`;
  return CREDENTIAL_FIELD_PATHS.filter((path) => path.startsWith(prefix)).map(
    (path) => path.slice(prefix.length),
  );
}

function walk(
  target: Record<string, unknown>,
  path: string,
): { parent: Record<string, unknown>; key: string } | null {
  const keys = path.split(".");
  const key = keys.pop();
  if (!key) return null;

  let current: unknown = target;
  for (const segment of keys) {
    if (typeof current !== "object" || current === null) return null;
    current = (current as Record<string, unknown>)[segment];
  }
  if (typeof current !== "object" || current === null) return null;
  return { parent: current as Record<string, unknown>, key };
}

/** Read a dot-path value, or undefined when any segment is missing. */
export function readCredentialPath(
  target: Record<string, unknown>,
  path: string,
): unknown {
  const found = walk(target, path);
  return found ? found.parent[found.key] : undefined;
}

/** Delete a dot-path value in place; a no-op when the path is absent. */
export function deleteCredentialPath(
  target: Record<string, unknown>,
  path: string,
): void {
  const found = walk(target, path);
  if (found) delete found.parent[found.key];
}

/**
 * Classify a gateway key as test or live from its own prefix. Computed on the
 * server because the raw key never reaches the browser.
 */
export function detectKeyMode(
  value: unknown,
): "test" | "live" | undefined {
  if (typeof value !== "string" || !value.trim()) return undefined;
  const key = value.toLowerCase();
  if (key.includes("test")) return "test";
  if (key.includes("live")) return "live";
  return undefined;
}
