/* eslint-disable @typescript-eslint/no-var-requires */
/**
 * VoIP-PushKit + CallKit Bridge im iOS-AppDelegate.
 *
 * Patcht beim `expo prebuild`:
 *   1) ReBreak-Bridging-Header.h
 *      → exposed RNVoipPushNotificationManager + RNCallKeep an Swift.
 *   2) AppDelegate.swift
 *      → `import PushKit`
 *      → PKPushRegistry init in didFinishLaunchingWithOptions
 *      → PKPushRegistryDelegate-Extension: didUpdatePushCredentials forwarded an
 *        RNVoipPushNotificationManager; didReceiveIncomingPushWithPayload meldet
 *        sofort eingehenden Anruf an RNCallKeep (iOS 13+ Anforderung: MUSS in
 *        derselben run-loop CXProvider.reportNewIncomingCall aufrufen, sonst
 *        killt iOS die App).
 *
 * react-native-voip-push-notification hat KEIN config-plugin → ohne diesen
 * Patch greift `pnpm expo prebuild --clean` jedes Mal die Edits ab.
 *
 * Idempotent via Marker. Läuft als withDangerousMod('ios'), nach prebuild,
 * vor pod install.
 */
const { withDangerousMod } = require('@expo/config-plugins');
const fs = require('fs');
const path = require('path');

const MARKER = '// REBREAK_VOIP_PUSHKIT';

// ───────────────────────────────────────────────────── Bridging-Header

const BRIDGING_PATCH = `
${MARKER}
#import "RNVoipPushNotificationManager.h"
#import "RNCallKeep.h"
${MARKER}
`;

// ───────────────────────────────────────────────────── AppDelegate.swift

const IMPORT_PATCH = `${MARKER}
import PushKit
${MARKER}
`;

// In didFinishLaunchingWithOptions — direkt vor `return super.application(...)`.
const REGISTRY_INIT = `
    ${MARKER}
    // PushKit-Registry für VoIP-Push (CallKit). MUSS in didFinishLaunching
    // initialisiert werden, sonst kommt der erste Push nach App-Cold-Start nicht
    // an. Als Property gehalten (self.voipRegistry, siehe Property-Deklaration
    // unter dem class-Header) damit sie nicht out-of-scope dealloziert wird —
    // sonst kommt die Token-Registration noch durch, aber didReceiveIncomingPush
    // feuert NIE → iPhone wacht im Background nicht auf.
    let registry = PKPushRegistry(queue: .main)
    registry.delegate = self
    registry.desiredPushTypes = [.voIP]
    self.voipRegistry = registry
    ${MARKER}
`;

// Class-Property für den Registry-Halter — muss innerhalb der AppDelegate-Klasse
// stehen. Wird direkt nach `var reactNativeFactory:` eingefügt.
const CLASS_PROPERTY = `
  ${MARKER}
  var voipRegistry: PKPushRegistry?
  ${MARKER}
`;

// PKPushRegistryDelegate-Extension am Ende der Datei.
const DELEGATE_EXTENSION = `

${MARKER}
// MARK: - VoIP PushKit + CallKit Bridge
extension AppDelegate: PKPushRegistryDelegate {

  // Device-Token vom System nach voIP-Registrierung.
  public func pushRegistry(
    _ registry: PKPushRegistry,
    didUpdate pushCredentials: PKPushCredentials,
    for type: PKPushType
  ) {
    let hex = pushCredentials.token.map { String(format: "%02hhx", $0) }.joined()
    NSLog("[VoIP] didUpdate token (len=%d) prefix=%@", pushCredentials.token.count, String(hex.prefix(16)))
    RNVoipPushNotificationManager.didUpdate(pushCredentials, forType: type.rawValue)
  }

  // Token-Invalidierung — aktuell no-op; Client refresht beim nächsten Boot.
  public func pushRegistry(
    _ registry: PKPushRegistry,
    didInvalidatePushTokenFor type: PKPushType
  ) {
    NSLog("[VoIP] didInvalidatePushTokenFor type=%@", type.rawValue)
  }

  // Eingehender VoIP-Push. MUSS auf iOS 13+ in derselben run-loop
  // CXProvider.reportNewIncomingCall aufrufen, sonst killt iOS die App
  // ("VoIP push received but app didn't post incoming call").
  public func pushRegistry(
    _ registry: PKPushRegistry,
    didReceiveIncomingPushWith payload: PKPushPayload,
    for type: PKPushType,
    completion: @escaping () -> Void
  ) {
    let dict = payload.dictionaryPayload
    let callId = (dict["callId"] as? String) ?? UUID().uuidString
    let callerName = (dict["callerName"] as? String) ?? "ReBreak"
    let handle = (dict["handle"] as? String) ?? callerName
    // callId (z.B. "1780632453911-8jeln4xg") ist KEIN valides UUID. CXProvider.
    // reportNewIncomingCall parst den String via NSUUID → nil → NSInvalidArgument
    // Exception → App-Crash → iOS-VoIP-Drossel. Deterministisch konvertieren wie
    // der JS-Layer (lib/callkit.ts:callIdToUuid), damit CallKit + JS dieselbe UUID
    // nutzen (accept/end matchen sonst nicht).
    let callUUID = AppDelegate.callIdToUuid(callId)
    NSLog("[VoIP] didReceiveIncomingPush callId=%@ uuid=%@ callerName=%@", callId, callUUID, callerName)

    // 1) Sofort an CallKit melden (iOS-Pflicht). uuid MUSS deterministisch sein,
    //    damit der JS-Layer den Call mit demselben UUID antworten/auflegen kann.
    RNCallKeep.reportNewIncomingCall(
      callUUID,
      handle: handle,
      handleType: "generic",
      hasVideo: false,
      localizedCallerName: callerName,
      supportsHolding: false,
      supportsDTMF: false,
      supportsGrouping: false,
      supportsUngrouping: false,
      fromPushKit: true,
      payload: dict,
      withCompletionHandler: completion
    )

    // 2) Forward an JS — wenn die App schon im Vordergrund war, kann sie auf
    //    didLoadWithEvents reagieren (Auto-Navigation /call).
    RNVoipPushNotificationManager.didReceiveIncomingPush(with: payload, forType: type.rawValue)
  }

  // Deterministische callId→UUID-Abbildung. MUSS exakt lib/callkit.ts:callIdToUuid
  // entsprechen, sonst nutzen CallKit (nativ) und der JS-Layer verschiedene UUIDs
  // und accept/end greifen ins Leere.
  static func callIdToUuid(_ callId: String) -> String {
    var hex = ""
    for scalar in callId.unicodeScalars {
      if hex.count >= 32 { break }
      let code = Int(scalar.value)
      hex.append(String((code >> 4) & 0xf, radix: 16))
      if hex.count < 32 { hex.append(String(code & 0xf, radix: 16)) }
    }
    while hex.count < 32 { hex.append("0") }
    let c = Array(hex.prefix(32))
    func sub(_ a: Int, _ b: Int) -> String { String(c[a..<b]) }
    // UUID v4 Layout: xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx (Index 12 + 16 werden
    // durch die Literale '4'/'8' ersetzt — exakt wie im JS-Original).
    return "\\(sub(0, 8))-\\(sub(8, 12))-4\\(sub(13, 16))-8\\(sub(17, 20))-\\(sub(20, 32))"
  }
}
${MARKER}
`;

// ─────────────────────────────────────────────────────────────────────

function patchBridgingHeader(iosRoot) {
  // Bridging-Header heißt analog zum Target — bei uns "ReBreak-Bridging-Header.h".
  const candidates = fs
    .readdirSync(iosRoot, { withFileTypes: true })
    .filter((d) => d.isDirectory())
    .flatMap((d) => {
      const p = path.join(iosRoot, d.name);
      return fs.existsSync(p)
        ? fs
            .readdirSync(p)
            .filter((f) => f.endsWith('-Bridging-Header.h'))
            .map((f) => path.join(p, f))
        : [];
    });

  if (candidates.length === 0) {
    console.warn('[with-voip-pushkit-ios] no *-Bridging-Header.h found — skipping bridging patch');
    return;
  }

  for (const headerPath of candidates) {
    let header = fs.readFileSync(headerPath, 'utf-8');
    if (header.includes(MARKER)) continue;
    header += BRIDGING_PATCH;
    fs.writeFileSync(headerPath, header);
    console.log('[with-voip-pushkit-ios] patched bridging header:', path.basename(headerPath));
  }
}

function patchAppDelegate(iosRoot) {
  const candidates = fs
    .readdirSync(iosRoot, { withFileTypes: true })
    .filter((d) => d.isDirectory())
    .flatMap((d) => {
      const p = path.join(iosRoot, d.name);
      const file = path.join(p, 'AppDelegate.swift');
      return fs.existsSync(file) ? [file] : [];
    });

  if (candidates.length === 0) {
    console.warn('[with-voip-pushkit-ios] AppDelegate.swift not found — skipping');
    return;
  }

  for (const appDelegatePath of candidates) {
    let src = fs.readFileSync(appDelegatePath, 'utf-8');
    if (src.includes(MARKER)) continue;

    // 1) Top-of-file: import PushKit (nach `import React` einfügen).
    if (!src.includes('import PushKit')) {
      src = src.replace(/(import React\n)/, `$1${IMPORT_PATCH}`);
    }

    // 2a) Class-Property `var voipRegistry: PKPushRegistry?` einfügen \u2014 muss
    //     gehalten werden, sonst dealloziert iOS die Registry nach
    //     didFinishLaunching und Pushes erreichen den Delegate nie.
    const classPropRe = /(\n\s+var reactNativeFactory:[^\n]+\n)/;
    if (classPropRe.test(src) && !src.includes('var voipRegistry')) {
      src = src.replace(classPropRe, `$1${CLASS_PROPERTY}`);
    }

    // 2b) didFinishLaunching: PKPushRegistry direkt vor dem return-super injizieren.
    const returnSuperRe = /(\n\s+return super\.application\(application, didFinishLaunchingWithOptions: launchOptions\))/;
    if (returnSuperRe.test(src)) {
      src = src.replace(returnSuperRe, `${REGISTRY_INIT}$1`);
    } else {
      console.warn('[with-voip-pushkit-ios] kein didFinishLaunching-return gefunden — Registry-Init NICHT eingefügt');
    }

    // 3) Delegate-Extension am Ende der Datei anhängen.
    src += DELEGATE_EXTENSION;

    fs.writeFileSync(appDelegatePath, src);
    console.log('[with-voip-pushkit-ios] patched AppDelegate.swift');
  }
}

module.exports = function withVoipPushKitIOS(config) {
  return withDangerousMod(config, [
    'ios',
    async (cfg) => {
      const iosRoot = cfg.modRequest.platformProjectRoot;
      try {
        patchBridgingHeader(iosRoot);
        patchAppDelegate(iosRoot);
      } catch (e) {
        console.error('[with-voip-pushkit-ios] patch failed:', e);
      }
      return cfg;
    },
  ]);
};
