/**
 * Mudança de rugosidade do terreno — NBR 6123:2023, sec. 5.5
 *
 * Implementa o perfil de velocidades (fatores S₂) na zona de transição
 * entre duas categorias de rugosidade.
 */

import type { TerrainCategory } from './wind-kernel';
import { Z0_BY_CATEGORY, ZG_BY_CATEGORY } from './nbr-tables/table-5';

export interface RoughnessTransitionInput {
  /** Categoria da edificação (terreno próximo) */
  near: TerrainCategory;
  /** Categoria do terreno a barlavento (mais afastado) */
  far: TerrainCategory;
  /** Distância horizontal x a partir da linha de mudança (m) */
  distance: number;
  /** Altura z acima do terreno (m) */
  height: number;
}

export interface RoughnessTransitionResult {
  z1: number;
  z2: number;
  /** S₂ equivalente na posição (x, z) */
  s2: number;
}

/** Calcula A conforme sec. 5.5.1 ou 5.5.2 */
function coefficientA(z02: number, z01: number, case2to1: boolean): number {
  if (case2to1) {
    return 0.63 - 0.03 * Math.log(z02 / z01);
  }
  return 0.73 - 0.03 * Math.log(z02 / z01);
}

/**
 * Cálculo da altura z₁ (topo da camada de transição — perfil da
 * categoria mais próxima). Caso 5.5.1: z_{02} > z_{01}.
 */
function computeZ1(z02: number, z01: number, x: number, case2to1: boolean): number {
  const A = coefficientA(z02, z01, case2to1);
  return A * z02 * Math.pow(x / z02, 0.8);
}

/** Caso 5.5.2: z_{02} < z_{01} — calcula z₁ diferente */
function computeZ1Case5_5_2(z02: number, x: number): number {
  const A = 0.73 - 0.03 * Math.log(z02 / 0.07);
  return A * z02 * Math.pow(x / z02, 0.8);
}

/** S₂ da categoria para uma altura z (usa tabela de parâmetros) */
function s2FromCategory(cat: TerrainCategory, z: number, params: Map<TerrainCategory, { b: number; p: number; fr: number }>): number {
  const p = params.get(cat);
  if (!p) throw new Error(`Categoria inválida: ${cat}`);
  const zEff = Math.max(5, Math.min(z, ZG_BY_CATEGORY[cat]));
  return p.b * p.fr * Math.pow(zEff / 10, p.p);
}

/**
 * Calcula S₂ efetivo considerando mudança de rugosidade.
 * Requer tabela de parâmetros já construída pelo chamador (vide
 * `getDefaultParams` em wind-kernel).
 */
export function applyRoughnessChange(
  input: RoughnessTransitionInput,
  params: Map<TerrainCategory, { b: number; p: number; fr: number }>,
): RoughnessTransitionResult {
  const { near, far, distance, height } = input;
  const z01 = Z0_BY_CATEGORY[near];
  const z02 = Z0_BY_CATEGORY[far];
  const x = Math.max(0, distance);

  const case2to1 = z02 > z01;

  let z1: number;
  if (case2to1) {
    z1 = computeZ1(z02, z01, x, true);
  } else {
    z1 = computeZ1Case5_5_2(z02, x);
  }

  const s2Far = s2FromCategory(far, height, params);
  const s2Near = s2FromCategory(near, height, params);

  let s2: number;
  if (height >= z1) {
    s2 = s2Far;
  } else {
    const z2 = case2to1
      ? 0.36 * z02 * Math.pow(x / z02, 0.8)
      : 0.36 * z02 * Math.pow(x / z02, 0.8);
    if (height <= z2) {
      s2 = s2Near;
    } else {
      const t = (height - z2) / (z1 - z2);
      s2 = s2Near + t * (s2Far - s2Near);
    }
  }

  return { z1, z2: 0.36 * Z0_BY_CATEGORY[far] * Math.pow(x / Z0_BY_CATEGORY[far], 0.8), s2 };
}