/**
 * Content-Security-Policy – eine einzige Quelle der Wahrheit.
 *
 * Die Richtlinie erreicht den Renderer auf zwei Wegen, und beide gelten
 * gleichzeitig. Chromium wertet mehrere Richtlinien nebeneinander aus, jede
 * für sich; erlaubt bleibt nur, was allen genügt – die Schnittmenge.
 *
 *  1. als `<meta http-equiv="Content-Security-Policy">` im Renderer-HTML
 *     (eingesetzt beim Bau, siehe `cspPlugin` in `electron.vite.config.ts`),
 *  2. als HTTP-Kopf über `onHeadersReceived` im Main-Prozess
 *     (siehe `cspHeaderSetzen` in `src/main/sicherheit.ts`).
 *
 * ## Warum es zwei Fassungen gibt
 *
 * Drei Direktiven ignoriert jeder Browser, wenn sie über ein `<meta>`-Element
 * kommen: `frame-ancestors`, `report-uri` und `sandbox`. Sie richten sich an
 * den einbettenden Kontext bzw. an den Ladevorgang selbst – beides steht fest,
 * bevor das HTML geparst ist. Chromium schreibt dann bei jedem Start einen
 * Konsolenfehler: „The Content Security Policy directive 'frame-ancestors' is
 * ignored when delivered via a <meta> element.“
 *
 * Genau das tat dieses Programm, weil `frame-ancestors 'none'` in beiden
 * Fassungen stand. Eine Direktive, die dasteht und nicht gilt, ist schlimmer
 * als keine: Zwei Tests prüften ihren Wortlaut und meldeten grün.
 *
 * Die `<meta>`-Fassung lässt diese Direktiven deshalb weg ({@link NUR_IM_KOPF}).
 * Im Kopf bleiben sie stehen – dort gelten sie.
 *
 * ## Warum der Schutz dabei nicht verloren geht – nachgemessen
 *
 * Hier stand früher, der Kopf-Weg sei nur für den Vite-Dev-Server da und
 * `file://` hänge allein am `<meta>`-Element. Das stimmt nicht:
 * `onHeadersReceived` greift in Electron auch für `file://`-Antworten.
 *
 * Nachgemessen am gebauten Programm (`out/main/index.js`, Electron 43): Ein
 * absichtlich ausgelöster Verstoß gegen `img-src` meldete **zwei**
 * `securitypolicyviolation`-Ereignisse – eines mit dem Wortlaut der
 * Kopf-Fassung, eines mit dem der `<meta>`-Fassung. Der Kopf kommt also im
 * Renderer an, und mit ihm `frame-ancestors`. Dieselbe Messung führt
 * `e2e/anwendung.spec.ts` bei jedem Lauf im echten Fenster durch, damit die
 * Zusicherung nicht wieder zur Behauptung wird.
 *
 * ## Beide Fassungen dürfen sonst nicht auseinanderlaufen
 *
 * Wäre die Kopf-Fassung strenger als die `<meta>`-Fassung, bräche der
 * Dev-Modus unbemerkt an der Schnittmenge. `tests/sicherheit.test.ts` hält
 * deshalb fest: `<meta>`-Fassung == Kopf-Fassung ohne die Nur-Kopf-Direktiven,
 * Zeichen für Zeichen.
 */

export type CspModus = 'development' | 'production';

/** Über welchen Weg die Richtlinie ausgeliefert wird. */
export type CspZiel = 'meta' | 'kopf';

/**
 * Direktiven, die ausschließlich als HTTP-Kopf wirken.
 *
 * Über ein `<meta>`-Element werden sie ignoriert – mit Konsolenfehler. Wer
 * hier etwas ergänzt, nimmt es damit zugleich aus der `<meta>`-Fassung heraus.
 * Vollständig laut CSP Level 3: `frame-ancestors`, `report-uri`, `sandbox`.
 * Aufgeführt sind nur die, die diese Anwendung tatsächlich setzt.
 */
const NUR_IM_KOPF: readonly string[] = ['frame-ancestors'];

/**
 * Im Produktionsbuild gilt eine strenge Richtlinie ohne `unsafe-*`.
 * Im Dev-Modus sind zusätzlich nötig:
 *  - `'unsafe-inline'` für Skripte: Vite injiziert die React-Refresh-Präambel
 *    als Inline-Skript in das HTML.
 *  - `'unsafe-inline'` für Styles: HMR fügt `<style>`-Elemente zur Laufzeit ein.
 *  - `ws:` in `connect-src` für den HMR-WebSocket.
 *
 * `ziel` ist mit Absicht ein Pflichtargument: Jede Aufrufstelle muss sagen,
 * über welchen Weg sie ausliefert – ein stiller Vorgabewert hätte die
 * `<meta>`-Fassung genau so wieder mit `frame-ancestors` beliefert.
 */
export function cspRichtlinie(modus: CspModus, ziel: CspZiel): string {
  const entwicklung = modus === 'development';

  const direktiven: Record<string, readonly string[]> = {
    'default-src': ["'none'"],
    'script-src': entwicklung ? ["'self'", "'unsafe-inline'"] : ["'self'"],
    'style-src': entwicklung ? ["'self'", "'unsafe-inline'"] : ["'self'"],
    'img-src': ["'self'", 'data:'],
    'font-src': ["'self'"],
    'media-src': ["'self'"],
    'connect-src': entwicklung ? ["'self'", 'ws:', 'http://localhost:*'] : ["'self'"],
    'worker-src': ["'self'"],
    'manifest-src': ["'self'"],
    'base-uri': ["'none'"],
    'form-action': ["'none'"],
    /* Wirkt auch als `<meta>`: verbietet der eigenen Seite, irgendetwas
       einzubetten. Nicht zu verwechseln mit `frame-ancestors`, das umgekehrt
       das Eingebettetwerden verbietet – und nur im Kopf gilt. */
    'frame-src': ["'none'"],
    'frame-ancestors': ["'none'"],
    'object-src': ["'none'"],
  };

  return Object.entries(direktiven)
    .filter(([direktive]) => ziel === 'kopf' || !NUR_IM_KOPF.includes(direktive))
    .map(([direktive, werte]) => `${direktive} ${werte.join(' ')}`)
    .join('; ');
}

/** Platzhalter in `src/renderer/index.html`, den das Vite-Plugin ersetzt. */
export const CSP_PLATZHALTER = '%CSP%';
