// ---------------------------------------------------------------------------
// FileMaker global API
// ---------------------------------------------------------------------------

/** The FileMaker object injected by the runtime into web viewers. */
export interface FileMakerAPI {
  PerformScript(script: string, parameter?: string): void
  PerformScriptWithOption(script: string, parameter?: string, option?: ScriptOption): void
}

declare global {
  /** Available only inside a FileMaker web viewer. Always guard with `typeof FileMaker !== 'undefined'`. */
  const FileMaker: FileMakerAPI | undefined
  interface Window {
    resolveFileMakerCallback: typeof resolveFileMakerCallback
  }
}

// ---------------------------------------------------------------------------
// Script execution options
// ---------------------------------------------------------------------------

/**
 * Controls how a currently running FileMaker script is handled when a new
 * script is started via `FileMaker.PerformScriptWithOption`.
 *
 * | Value | Behaviour                                                        |
 * |-------|------------------------------------------------------------------|
 * | "0"   | Pause the current script, run the new one, then resume          |
 * | "1"   | Abort the current script and run the new one                    |
 * | "2"   | Exit the current script and run the new one                     |
 * | "3"   | Run the new script concurrently (default async behaviour)       |
 * | "4"   | Trigger script (same as 3 but fires as a script trigger would)  |
 * | "5"   | Suspend the current script; resume it after the new one exits   |
 */
export type ScriptOption = '0' | '1' | '2' | '3' | '4' | '5';

// ---------------------------------------------------------------------------
// Helper utilities
// ---------------------------------------------------------------------------

/**
 * Type-safe wrapper that calls `FileMaker.PerformScript` only when the
 * runtime is available (i.e. the page is running inside a web viewer).
 *
 * @returns `true` if the script was dispatched, `false` otherwise.
 *
 * @example
 * performScript("Delete Record", String(recordId));
 */
export function performScript(script: string, parameter?: string): boolean {
  if (typeof FileMaker === 'undefined') return false;
  FileMaker.PerformScript(script, parameter);
  return true;
}

/**
 * Type-safe wrapper around `FileMaker.PerformScriptWithOption`.
 *
 * @returns `true` if the script was dispatched, `false` otherwise.
 */
export function performScriptWithOption(
  script: string,
  parameter?: string,
  option?: ScriptOption,
): boolean {
  if (typeof FileMaker === 'undefined') return false;
  FileMaker.PerformScriptWithOption(script, parameter, option);
  return true;
}

// ---------------------------------------------------------------------------
// Callback-based async bridge
// ---------------------------------------------------------------------------

/**
 * A pending callback registered by {@link callFileMakerScript}.
 * @internal
 */
interface PendingCallback {
  resolve: (data: string) => void;
  reject: (error: FileMakerScriptError) => void;
}

/** Error thrown when a FileMaker script signals failure. */
export interface FileMakerScriptError {
  /** The callback ID that was active when the error occurred. */
  callbackId: number;
  /** Optional error message forwarded from the FileMaker script. */
  message?: string;
}

/**
 * Pending callbacks keyed by an auto-incrementing integer ID.
 * @internal
 */
const _pendingCallbacks = new Map<number, PendingCallback>();
let _nextCallbackId = 1;

/**
 * The shape of the payload that `callFileMakerScript` wraps around the
 * caller-supplied parameter before sending it to FileMaker.
 * @internal
 */
interface WrappedPayload {
  callbackId: number;
  parameter: string;
}

/**
 * Invokes a FileMaker script and returns a `Promise` that resolves when
 * FileMaker calls back into JavaScript via {@link resolveFileMakerCallback}.
 *
 * **FileMaker side:** the script must eventually call
 * *Perform JavaScript in Web Viewer* targeting `resolveFileMakerCallback`
 * and pass back the same `callbackId` together with the result data.
 *
 * ```
 * // FileMaker script (pseudocode)
 * Set Variable [$payload ; Value: Get(ScriptParameter)]
 * Set Variable [$id     ; Value: JSONGetElement($payload; "callbackId")]
 * Set Variable [$result ; Value: \* … your work … *\]
 * Perform JavaScript in Web Viewer [
 *   Object Name: "MyWebViewer"
 *   Function:    "resolveFileMakerCallback"
 *   Parameters:  $id, $result
 * ]
 * ```
 *
 * @param script    - FileMaker script name.
 * @param parameter - Arbitrary string payload for the script.
 * @param timeout   - Optional ms before the promise is rejected automatically
 *                    (defaults to no timeout).
 *
 * @example
 * const json = await callFileMakerScript("Get Customer", String(customerId));
 * const customer = JSON.parse(json);
 */
export function callFileMakerScript(
  script: string,
  parameter = '',
  timeout?: number,
): Promise<string> {
  return new Promise<string>((resolve, reject) => {
    if (typeof FileMaker === 'undefined') {
      reject({ callbackId: -1, message: 'FileMaker runtime not available' });
      return;
    }

    const callbackId = _nextCallbackId++;
    _pendingCallbacks.set(callbackId, { resolve, reject });

    const payload: WrappedPayload = { callbackId, parameter };
    FileMaker.PerformScript(script, JSON.stringify(payload));

    if (timeout !== undefined) {
      setTimeout(() => {
        if (_pendingCallbacks.has(callbackId)) {
          _pendingCallbacks.delete(callbackId);
          reject({ callbackId, message: `Timed out after ${timeout}ms` });
        }
      }, timeout);
    }
  });
}

/**
 * Must be exposed as a global function so that FileMaker's
 * *Perform JavaScript in Web Viewer* script step can invoke it.
 *
 * Call this from your FileMaker script to resolve a promise created by
 * {@link callFileMakerScript}.
 *
 * @param callbackId - The numeric ID forwarded from the script parameter.
 * @param result     - The string result to hand back to JavaScript.
 * @param isError    - When truthy, the promise is rejected instead.
 *
 * @example
 * // Expose globally so FileMaker can reach it
 * (window as any).resolveFileMakerCallback = resolveFileMakerCallback;
 */
export function resolveFileMakerCallback(callbackId: string, result = '', isError?: boolean): void {
  const callbackNumId = parseInt(callbackId);
  const pending = _pendingCallbacks.get(callbackNumId);
  if (!pending) return;
  _pendingCallbacks.delete(callbackNumId);

  if (isError) {
    pending.reject({ callbackId: callbackNumId, message: result });
  } else {
    pending.resolve(result);
  }
}

export function waitForFileMaker(
  callback: () => void,
  onError?: () => void,
  maxAttempts = 10, // 1 Sekunde bei 100ms Intervall
  attempt = 0,
) {
  if (isFileMakerEnvironment()) {
    callback();
  } else if (attempt >= maxAttempts) {
    onError?.(); // Fehler-Callback aufrufen
  } else {
    setTimeout(() => waitForFileMaker(callback, onError, maxAttempts, attempt + 1), 100);
  }
}

// ---------------------------------------------------------------------------
// Environment detection
// ---------------------------------------------------------------------------

/**
 * Returns `true` when the page is running inside a FileMaker web viewer.
 *
 * @example
 * if (isFileMakerEnvironment()) {
 *   FileMaker!.PerformScript("On Load");
 * }
 */
export function isFileMakerEnvironment(): boolean {
  return typeof FileMaker !== 'undefined';
}
