/**
 * Coeficiente de pressão interna (Cpi) — NBR 6123:2023, sec. 6.3
 *
 * Implementa:
 *  - Método simplificado (6.3.2)
 *  - Método detalhado (6.3.3) — somatório de vazões
 *
 * Limites normativos:
 *  - Todas as combinações devem estar em [-0,9 ; +0,9]
 *  - Índice de permeabilidade ≤ 30% (caso geral)
 *  - Abertura dominante: área ≥ soma das demais aberturas
 */

export type PermeabilityCase =
  | 'two-opposite-permeable'
  | 'four-equally-permeable'
  | 'dominant-windward'
  | 'dominant-leeward'
  | 'dominant-lateral'
  | 'airtight';

export interface SimplifiedCpiInput {
  case: PermeabilityCase;
  /** Razão da área da abertura dominante / área total de aberturas em faces com sucção externa (apenas para dominant-lateral com sucção) */
  ratio?: number;
  /** Direção do vento: 0 ou 90 (apenas para two-opposite-permeable) */
  windAngle?: 0 | 90;
}

/**
 * Cpi simplificado (6.3.2)
 *
 * Casos:
 *  - two-opposite-permeable: vento ⊥ face permeável → Cpi = +0,2;
 *                            vento ⊥ face impermeável → Cpi = -0,3
 *  - four-equally-permeable: Cpi = -0,3 ou 0 (considerar o mais nocivo)
 *  - dominant-windward: Cpi conforme tabela em 6.3.2.1-c
 *  - dominant-leeward: Cpi = Ce da face de sotavento (informado externamente)
 *  - dominant-lateral: Cpi conforme tabela em 6.3.2.1-c-2 ou =Ce da zona
 *  - airtight: Cpi = -0,2 ou 0
 */
export function computeCpiSimplified(input: SimplifiedCpiInput): number {
  switch (input.case) {
    case 'two-opposite-permeable':
      return input.windAngle === 0 ? 0.2 : -0.3;

    case 'four-equally-permeable':
      return 0;

    case 'dominant-windward': {
      const r = input.ratio ?? 1;
      if (r < 0.5) return 0.1;
      if (r < 1.5) return 0.3;
      if (r < 2.5) return 0.5;
      if (r < 3) return 0.6;
      return 0.8;
    }

    case 'dominant-leeward':
      // Caller deve fornecer Ce externo via input.ratio como Ce;
      // retornamos o próprio Ce como aproximação segura.
      return input.ratio ?? -0.3;

    case 'dominant-lateral': {
      const r = input.ratio ?? 1;
      if (r < 0.375) return -0.4;
      if (r < 0.625) return -0.5;
      if (r < 0.875) return -0.6;
      if (r < 1.25) return -0.7;
      if (r < 2.25) return -0.8;
      return -0.8;
    }

    case 'airtight':
      return -0.2;
  }
}

/** Cilindro sem aberturas e topo aberto (sec. 6.3.2.3) */
export function computeCpiCylinderOpenTop(hOverD: number): number {
  if (hOverD >= 0.3) return -0.8;
  return -0.5;
}

/** Aplica os limites normativos [-0,9 ; +0,9] */
export function clampCpi(cpi: number): number {
  return Math.max(-0.9, Math.min(0.9, cpi));
}

/**
 * Cpi detalhado (6.3.3) — método da vazão.
 *
 * Resolve por aproximação sucessiva:
 *   Σ Aᵢ · √|Cpeᵢ − Cpi| · sinal(Cpeᵢ − Cpi) = 0
 *
 * @param aberturas Lista de aberturas com área e Cpe médio na periferia
 * @param cpiInicial Chute inicial (default 0)
 * @param tol Tolerância do somatório (default 1e-6)
 * @param maxIter Máximo de iterações (default 200)
 */
export interface OpeningCpiInput {
  area: number;
  cpe: number;
}

export function computeCpiDetailed(
  aberturas: readonly OpeningCpiInput[],
  cpiInicial = 0,
  tol = 1e-6,
  maxIter = 200,
): number {
  let cpi = cpiInicial;
  for (let iter = 0; iter < maxIter; iter++) {
    let sum = 0;
    for (const a of aberturas) {
      const diff = a.cpe - cpi;
      if (Math.abs(diff) < 1e-9) continue;
      const sign = diff > 0 ? 1 : -1;
      sum += sign * a.area * Math.sqrt(Math.abs(diff));
    }
    if (Math.abs(sum) < tol) break;

    // Newton-like: ajusta cpi na direção do zero
    // df/dCpi = Σ Aᵢ / (2·√|Cpeᵢ − Cpi|) · (−1)
    let deriv = 0;
    for (const a of aberturas) {
      const diff = a.cpe - cpi;
      if (Math.abs(diff) < 1e-9) continue;
      deriv += -a.area / (2 * Math.sqrt(Math.abs(diff)));
    }
    if (Math.abs(deriv) < 1e-12) break;
    cpi -= sum / deriv;
  }
  return clampCpi(cpi);
}