/**
 * TypeScript wrapper around the ITM (Longley-Rice) WebAssembly module.
 * Algorithm ref: NTIA Report 82-100 + 1985 Hufford memo. Source: github.com/NTIA/itm @ v1.4 (2021-04-26).
 * Lazy-loaded to keep ~150-300 KB WASM out of the initial bundle.
 */

// Dynamic import keeps WASM out of the main bundle (Vite splits it).
type ItmFactory = typeof import("../../../generated/itm/itm.js").default;

// Enums mirror include/itm.h
export enum Climate {
  Equatorial = 1,
  ContinentalSubtropical = 2,
  MaritimeTropical = 3,
  Desert = 4,
  ContinentalTemperate = 5,
  MaritimeTemperateOverLand = 6,
  MaritimeTemperateOverSea = 7,
}

export enum Polarization {
  Horizontal = 0,
  Vertical = 1,
}

/** `mdvar` base mode (NTIA C API packs it with output-format bits). */
export enum ModeOfVariability {
  SingleMessage = 0,
  IndividualOrAccidental = 1,
  Mobile = 2,
  Broadcast = 3,
}

/** Collapsed propagation mode from ITM's intermediate-values struct. */
export enum PropagationMode {
  LineOfSight = "line_of_sight",
  Diffraction = "diffraction",
  Troposcatter = "troposcatter",
}

// 0 = success, 1 = success-with-warnings, 1000-1022 = errors (see upstream ERRORS_AND_WARNINGS.md).
export const ITM_SUCCESS = 0;
export const ITM_SUCCESS_WITH_WARNINGS = 1;

/** Warning bit-field; bit values match NTIA/itm ERRORS_AND_WARNINGS.md (v1.4). */
export enum ItmWarningFlag {
  TxTerminalHeightLow = 0x0001,
  TxTerminalHeightHigh = 0x0002,
  RxTerminalHeightLow = 0x0004,
  RxTerminalHeightHigh = 0x0008,
  FrequencyLow = 0x0010,
  FrequencyHigh = 0x0020,
  SurfaceRefractivityLow = 0x0040,
  SurfaceRefractivityHigh = 0x0080,
  GroundConductivityLow = 0x0100,
  DielectricLow = 0x0200,
  EffectiveHeightLow = 0x0400,
  PathDistanceLow = 0x0800,
  PathDistanceHigh = 0x1000,
  PathDistanceHigher = 0x2000,
  TransHorizonLow = 0x4000,
}

export interface P2PInput {
  /** TX antenna AGL (m), 0.5-3000. */
  txHeightM: number;
  /** RX antenna AGL (m), 0.5-3000. */
  rxHeightM: number;
  /** Terrain profile (m) along great-circle TX→RX at even spacing; ≥2 samples, [0]=TX, last=RX. */
  profileM: ArrayLike<number>;
  /** Profile sample spacing (m). */
  pointSpacingM: number;
  climate: Climate;
  /** N-units at sea level; 300-315 continental temperate, 360 maritime tropical. Range 250-400. */
  surfaceRefractivityN: number;
  /** MHz, 20-20000. */
  freqMhz: number;
  /** H or V only (no circular). */
  polarization: Polarization;
  /** ε_r (~15 for average ground). */
  groundDielectric: number;
  /** σ in S/m (~0.005 typical). */
  groundConductivity: number;
  mdvar?: ModeOfVariability;
  /** TLS % of time (0-100), typically 50. */
  time?: number;
  /** TLS % of locations (0-100), typically 50. */
  location?: number;
  /** TLS % of situations (0-100), typically 50. */
  situation?: number;
}

export interface IntermediateValues {
  /** Horizon take-off angles (rad) [tx, rx]. */
  thetaHorizonRad: [number, number];
  /** Horizon distances (m) [tx, rx]. */
  horizonDistanceM: [number, number];
  /** Effective antenna heights (m) [tx, rx]. */
  effectiveHeightM: [number, number];
  surfaceRefractivityN: number;
  /** Terrain irregularity Δh (m). */
  deltaHM: number;
  /** Reference attenuation dB (no variability). */
  aRefDb: number;
  /** Free-space loss dB at same distance. */
  aFreeSpaceDb: number;
  distanceKm: number;
  mode: PropagationMode;
}

export interface P2PResult {
  /** Basic transmission loss (dB). */
  lossDb: number;
  /** 0 = ok, 1 = ok-with-warnings, ≥1000 = error. */
  returnCode: number;
  warnings: ItmWarningFlag[];
  intermediate: IntermediateValues;
}

let modulePromise: Promise<ItmModuleLoaded> | null = null;

interface ItmModuleLoaded {
  _malloc: (size: number) => number;
  _free: (ptr: number) => void;
  /** Fast variant: loss only; used by the per-pixel hot loop. */
  _ITM_P2P_TLS: (
    h_tx_m: number, h_rx_m: number, pfl_ptr: number,
    climate: number, n0: number, f_mhz: number, pol: number,
    epsilon: number, sigma: number, mdvar: number,
    time_pct: number, location_pct: number, situation_pct: number,
    a_db_ptr: number, warnings_ptr: number,
  ) => number;
  /** With intermediate struct; used by `computeP2PLoss`. */
  _ITM_P2P_TLS_Ex: (
    h_tx_m: number, h_rx_m: number, pfl_ptr: number,
    climate: number, n0: number, f_mhz: number, pol: number,
    epsilon: number, sigma: number, mdvar: number,
    time_pct: number, location_pct: number, situation_pct: number,
    a_db_ptr: number, warnings_ptr: number, inter_ptr: number,
  ) => number;
  HEAPF64: Float64Array;
  HEAP32: Int32Array;
  HEAPU32: Uint32Array;
}

/** Load and cache the ITM WebAssembly module (concurrent-safe). */
async function loadItm(): Promise<ItmModuleLoaded> {
  if (!modulePromise) {
    modulePromise = (async () => {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore — generated by `yarn build:wasm`; see wasm/itm/README.md
      const { default: createItm } = (await import(
        /* @vite-ignore */ "../../../generated/itm/itm.js"
      )) as { default: ItmFactory };
      const mod = await createItm();
      return mod as unknown as ItmModuleLoaded;
    })();
  }
  return modulePromise;
}

/** Build PFL array: pfl[0]=n-1, pfl[1]=spacing_m, pfl[2..]=elevations. Caller must _free(). */
function allocPfl(mod: ItmModuleLoaded, profileM: ArrayLike<number>, spacingM: number): number {
  const n = profileM.length;
  if (n < 2) throw new Error("profile must have ≥2 samples");
  const totalDoubles = 2 + n;
  const ptr = mod._malloc(totalDoubles * 8);
  const idx = ptr / 8; // HEAPF64 is double-indexed
  mod.HEAPF64[idx] = n - 1;
  mod.HEAPF64[idx + 1] = spacingM;
  for (let i = 0; i < n; i++) {
    mod.HEAPF64[idx + 2 + i] = profileM[i];
  }
  return ptr;
}

/** Decode the warnings bitfield to an array of named flags. */
function decodeWarnings(bits: number): ItmWarningFlag[] {
  const out: ItmWarningFlag[] = [];
  for (const value of Object.values(ItmWarningFlag)) {
    if (typeof value !== "number") continue;
    if ((bits & value) !== 0) out.push(value);
  }
  return out;
}

/** NTIA ITM internal mode codes (upstream FORTRAN): 0=none, 1=LoS, 2=diffraction, 3=troposcatter. */
function decodeMode(code: number): PropagationMode {
  switch (code) {
    case 1: return PropagationMode.LineOfSight;
    case 2: return PropagationMode.Diffraction;
    case 3: return PropagationMode.Troposcatter;
    default: return PropagationMode.LineOfSight;
  }
}

// Layout of the IntermediateValues C struct (from include/itm.h):
//   double theta_hzn[2];         //  0, 8
//   double d_hzn__meter[2];      // 16, 24
//   double h_e__meter[2];        // 32, 40
//   double N_s;                  // 48
//   double delta_h__meter;       // 56
//   double A_ref__db;            // 64
//   double A_fs__db;             // 72
//   double d__km;                // 80
//   int    mode;                 // 88 (padded)
// Total: 96 bytes (with 4-byte int + 4-byte pad = 8 bytes for alignment).
const INTERMEDIATE_STRUCT_SIZE = 96;

function readIntermediate(
  mod: ItmModuleLoaded,
  ptr: number,
): IntermediateValues {
  const f = (offsetBytes: number) => mod.HEAPF64[(ptr + offsetBytes) / 8];
  const i = (offsetBytes: number) => mod.HEAP32[(ptr + offsetBytes) / 4];
  return {
    thetaHorizonRad: [f(0), f(8)],
    horizonDistanceM: [f(16), f(24)],
    effectiveHeightM: [f(32), f(40)],
    surfaceRefractivityN: f(48),
    deltaHM: f(56),
    aRefDb: f(64),
    aFreeSpaceDb: f(72),
    distanceKm: f(80),
    mode: decodeMode(i(88)),
  };
}

/** P2P loss via TLS variability; "normal" call returning median loss at 50/50/50. */
export async function computeP2PLoss(input: P2PInput): Promise<P2PResult> {
  const mod = await loadItm();
  const {
    txHeightM, rxHeightM, profileM, pointSpacingM,
    climate, surfaceRefractivityN, freqMhz, polarization,
    groundDielectric, groundConductivity,
    mdvar = ModeOfVariability.SingleMessage,
    time = 50, location = 50, situation = 50,
  } = input;

  const pflPtr = allocPfl(mod, profileM, pointSpacingM);
  const aDbPtr = mod._malloc(8); // double
  const warnPtr = mod._malloc(4); // long (32-bit on WASM)
  const interPtr = mod._malloc(INTERMEDIATE_STRUCT_SIZE);
  // Zero-init to avoid stale memory
  for (let o = 0; o < INTERMEDIATE_STRUCT_SIZE; o += 8) {
    mod.HEAPF64[(interPtr + o) / 8] = 0;
  }

  try {
    const rc = mod._ITM_P2P_TLS_Ex(
      txHeightM, rxHeightM, pflPtr,
      climate, surfaceRefractivityN, freqMhz, polarization,
      groundDielectric, groundConductivity, mdvar,
      time, location, situation,
      aDbPtr, warnPtr, interPtr,
    );
    const lossDb = mod.HEAPF64[aDbPtr / 8];
    const warnBits = mod.HEAP32[warnPtr / 4];
    const intermediate = readIntermediate(mod, interPtr);
    return {
      lossDb,
      returnCode: rc,
      warnings: decodeWarnings(warnBits),
      intermediate,
    };
  } finally {
    mod._free(pflPtr);
    mod._free(aDbPtr);
    mod._free(warnPtr);
    mod._free(interPtr);
  }
}

/** Whether the WASM module is buildable/loadable (for UI guards). */
export async function isItmAvailable(): Promise<boolean> {
  try {
    await loadItm();
    return true;
  } catch {
    return false;
  }
}

/** Loaded WASM + pre-allocated scratch for repeated ITM calls (alloc-free hot loop). */
export interface ItmContext {
  readonly mod: ItmModuleLoaded;
  readonly pflPtr: number;
  /** Max elevation samples that fit in pflPtr. */
  readonly pflCapacity: number;
  /** Output: loss dB (double). */
  readonly aDbPtr: number;
  /** Output: warning bit-field (long). */
  readonly warnPtr: number;
}

/** Pre-allocate scratch for up to `maxProfileSamples` elevations per call. 128 gives headroom. */
export async function loadItmContext(
  maxProfileSamples = 128,
): Promise<ItmContext> {
  const mod = await loadItm();
  const pflDoubles = 2 + maxProfileSamples;
  const pflPtr = mod._malloc(pflDoubles * 8);
  const aDbPtr = mod._malloc(8);
  const warnPtr = mod._malloc(4);
  return { mod, pflPtr, pflCapacity: maxProfileSamples, aDbPtr, warnPtr };
}

/** Free scratch buffers. */
export function disposeItmContext(ctx: ItmContext): void {
  ctx.mod._free(ctx.pflPtr);
  ctx.mod._free(ctx.aDbPtr);
  ctx.mod._free(ctx.warnPtr);
}

/** Per-pixel input for `computeP2PLossFast`; same fields as P2PInput, positional for speed. */
export interface FastP2PInput {
  txHeightM: number;
  rxHeightM: number;
  profileM: ArrayLike<number>;
  pointSpacingM: number;
  climate: Climate;
  surfaceRefractivityN: number;
  freqMhz: number;
  polarization: Polarization;
  groundDielectric: number;
  groundConductivity: number;
  mdvar: number;
  time: number;
  location: number;
  situation: number;
}

/** Hot-loop P2P loss using a shared context (sync, alloc-free). Use computeP2PLoss for intermediates. */
export function computeP2PLossFast(ctx: ItmContext, input: FastP2PInput): number {
  const { mod, pflPtr, aDbPtr, warnPtr, pflCapacity } = ctx;
  const n = input.profileM.length;
  if (n > pflCapacity) {
    throw new Error(
      `profile length ${n} exceeds ItmContext capacity ${pflCapacity}`,
    );
  }
  const idx = pflPtr / 8;
  mod.HEAPF64[idx] = n - 1;
  mod.HEAPF64[idx + 1] = input.pointSpacingM;
  for (let i = 0; i < n; i++) {
    mod.HEAPF64[idx + 2 + i] = input.profileM[i];
  }
  mod._ITM_P2P_TLS(
    input.txHeightM, input.rxHeightM, pflPtr,
    input.climate, input.surfaceRefractivityN,
    input.freqMhz, input.polarization,
    input.groundDielectric, input.groundConductivity,
    input.mdvar,
    input.time, input.location, input.situation,
    aDbPtr, warnPtr,
  );
  return mod.HEAPF64[aDbPtr / 8];
}
