/**
 * Prompt-injection detection for AI inputs.
 *
 * Defense-in-depth only — the real authorization boundary is the per-tool
 * permission check (`requirePermission` on each assistant tool). This guard
 * exists so deliberate injection attempts are (a) logged / alerted on and
 * (b) blocked for hot-wired paths (e.g. DALL-E prompt concat) that don't
 * run through tool-calls. It WILL be bypassed by a motivated attacker.
 *
 * Normalisation before regex:
 *   1) Unicode NFKC — collapses compatibility forms (`ｉｇｎｏｒｅ` → `ignore`).
 *   2) Strip zero-width + directional control chars (ZWSP, ZWJ, LRM, RLM …).
 *   3) Strip combining marks (diacritics etc.) after NFKC splits them.
 *   4) Map a small set of Cyrillic / Greek homoglyphs to ASCII.
 *
 * EGAI 4.6.3.2 — Prompt Injection Detection
 */

const INJECTION_PATTERNS = [
  /ignore\s+(all\s+)?previous\s+instructions/i,
  /disregard\s+(all\s+)?prior/i,
  /you\s+are\s+now\s+/i,
  /forget\s+(everything|all|your)\s+(instructions|rules|guidelines)/i,
  /system\s*:\s*/i,
  /\[INST\]/i,
  /<<SYS>>/i,
  /\bDAN\b.*\bmode\b/i,
  /jailbreak/i,
  /bypass\s+(security|filter|restriction)/i,
  /pretend\s+you\s+(are|have)\s+no\s+(rules|restrictions)/i,
  /act\s+as\s+(if|though)\s+you\s+(have|are)\s+no/i,
];

// Zero-width + directional formatting characters that let an attacker insert
// `ignore` into text without the substring appearing contiguous to a regex.
const INVISIBLE_RE = /[\u200B-\u200F\u202A-\u202E\u2060-\u2064\uFEFF\u00AD]/g;

// Combining-mark block — stripped after NFKC so `n\u0303` → `n`.
const COMBINING_MARK_RE = /[\u0300-\u036F]/g;

// Minimal homoglyph fold: Cyrillic / Greek letters that render identically to
// ASCII in common fonts. Not exhaustive — a full confusables table would be
// multi-KB; this covers the realistic bypass set for our patterns.
const HOMOGLYPHS: Record<string, string> = {
  "\u0430": "a",
  "\u0410": "A",
  "\u0435": "e",
  "\u0415": "E",
  "\u043E": "o",
  "\u041E": "O",
  "\u0440": "p",
  "\u0420": "P",
  "\u0441": "c",
  "\u0421": "C",
  "\u0445": "x",
  "\u0425": "X",
  "\u0443": "y",
  "\u0456": "i",
  "\u0406": "I",
  "\u03BF": "o",
  "\u0391": "A",
  "\u0392": "B",
  "\u0395": "E",
  "\u0397": "H",
  "\u0399": "I",
  "\u039A": "K",
  "\u039C": "M",
  "\u039D": "N",
  "\u039F": "O",
  "\u03A1": "P",
  "\u03A4": "T",
  "\u03A7": "X",
  "\u03A5": "Y",
  "\u03A2": "Z",
};

function foldHomoglyphs(input: string): string {
  let out = "";
  for (const ch of input) {
    out += HOMOGLYPHS[ch] ?? ch;
  }
  return out;
}

export function normalizeForGuard(input: string): string {
  // NFKD (decomposed, compatibility) instead of NFKC so that pre-composed
  // diacritics like "é" split into base + combining mark; the mark is then
  // removed together with attacker-inserted padding. NFKD also handles
  // compatibility forms (e.g. fullwidth letters).
  const nfkd = input.normalize("NFKD");
  const stripped = nfkd.replace(INVISIBLE_RE, "").replace(COMBINING_MARK_RE, "");
  return foldHomoglyphs(stripped);
}

export interface PromptGuardResult {
  safe: boolean;
  matchedPattern?: string;
}

export function checkPromptInjection(input: string): PromptGuardResult {
  const normalized = normalizeForGuard(input);
  for (const pattern of INJECTION_PATTERNS) {
    if (pattern.test(normalized)) {
      return { safe: false, matchedPattern: pattern.source };
    }
  }
  return { safe: true };
}
