/**
 * Client for the Entropyk REST API (Axum server in demo/).
 *
 * In development, requests go through the Next.js rewrite proxy
 * (/api/entropyk/* → http://localhost:3030/api/* by default; see next.config.mjs).
 */

import type { ComponentMeta } from "./componentMeta";

const API_BASE =
  process.env.NEXT_PUBLIC_API_URL || "/api/entropyk";

export interface IterationInfo {
  iteration: number;
  residual_norm: number;
  delta_norm: number;
  alpha?: number | null;
  max_residual_index?: number | null;
  max_residual?: number;
}

export interface SimulationResult {
  input: string;
  status: "converged" | "failed" | "error" | string;
  convergence?: {
    iterations?: number;
    final_residual?: number;
    tolerance?: number;
    strategy?: string;
    iteration_history?: IterationInfo[];
    converged?: boolean;
    status?: string;
  };
  iterations?: number;
  state?: Array<{
    edge?: number;
    edge_id?: number;
    pressure_bar?: number;
    pressure_pa?: number;
    enthalpy_kj_kg?: number;
    enthalpy_j_kg?: number;
    mass_flow_kg_s?: number;
    source?: string;
    target?: string;
    source_port?: string;
    target_port?: string;
    temperature_c?: number;
    saturation_temperature_c?: number;
  }>;
  performance?: {
    q_cooling_kw?: number | null;
    q_heating_kw?: number | null;
    compressor_power_kw?: number | null;
    cop?: number | null;
    cooling_capacity_w?: number | null;
    heating_capacity_w?: number | null;
    compressor_power_w?: number | null;
    pump_power_w?: number | null;
    cop_cooling?: number | null;
    cop_heating?: number | null;
  };
  components?: Array<{
    name: string;
    component_type: string;
    circuit: number;
    inlet?: { pressure_pa: number; enthalpy_j_kg: number };
    outlet?: { pressure_pa: number; enthalpy_j_kg: number };
    energy?: { heat_transfer_w: number; work_w: number };
  }>;
  error?: string | null;
  failure_diagnostics?: {
    final_residual_norm?: number;
    last_residual_norm?: number;
    dominant_residual_index?: number;
    dominant_residual_value?: number;
  } | null;
  /** Degrees-of-freedom summary after topology finalize (CLI hard gate). */
  dof?: DofSummary | null;
  /**
   * Named solver-computed unknowns (free actuators + calibration factors like
   * `z_ua`, `opening`, `z_dp`) read out of the raw Newton state vector.
   * Absent/empty for systems with no bounded control variables.
   */
  solved_variables?: SolvedVariable[];
}

/** A named solver-computed unknown with its converged value and bounds. */
export interface SolvedVariable {
  /** Stable id, e.g. "exv__opening". */
  id: string;
  /** Owning component name, or null for globals. */
  component: string | null;
  /** Short human label, e.g. "opening", "z_ua", "z_dp". */
  variable: string;
  value: number;
  min: number;
  max: number;
}

/** Degrees-of-freedom summary returned by the CLI after finalize. */
export interface DofSummary {
  n_equations: number;
  n_unknowns: number;
  balance: string;
  summary: string;
}

export interface SimulateResponse {
  ok: boolean;
  result?: SimulationResult;
  error?: string;
}

export interface MollierPoint {
  t_k: number;
  p_bar: number;
  h_liq_kj_kg: number;
  h_vap_kj_kg: number;
}

export interface MollierResponse {
  ok: boolean;
  fluid: string;
  saturation: MollierPoint[];
  error?: string | null;
}

export async function fetchComponents(): Promise<ComponentMeta[]> {
  const res = await fetch(`${API_BASE}/components`, { cache: "no-store" });
  if (!res.ok) throw new Error(`Failed to fetch components: ${res.status}`);
  return res.json();
}

export interface ModuleInfo {
  name: string;
  ports: string[];
  n_components: number;
  n_instances?: number;
}

export async function fetchModules(): Promise<ModuleInfo[]> {
  try {
    let res = await fetch(`${API_BASE}/modules`, { cache: "no-store" }).catch(() => null);
    if (!res || !res.ok) {
      res = await fetch("http://localhost:3030/api/modules", { cache: "no-store" }).catch(() => null);
    }
    if (res && res.ok) {
      const data = await res.json();
      if (Array.isArray(data)) {
        return data.map((m: ModuleInfo) => {
          if (m.name === "DualCircuit" && m.n_instances === undefined) {
            return { ...m, n_instances: 2 };
          }
          return m;
        });
      }
    }
    return [
      { name: "BaseChiller", ports: ["chw_in", "chw_out", "cond_water_in", "cond_water_out"], n_components: 4, n_instances: 0 },
      { name: "DualCircuit", ports: ["plant_chw_in", "plant_chw_out"], n_components: 0, n_instances: 2 },
    ];
  } catch {
    return [
      { name: "BaseChiller", ports: ["chw_in", "chw_out", "cond_water_in", "cond_water_out"], n_components: 4, n_instances: 0 },
      { name: "DualCircuit", ports: ["plant_chw_in", "plant_chw_out"], n_components: 0, n_instances: 2 },
    ];
  }
}

export async function fetchModule(name: string): Promise<unknown | null> {
  try {
    let res = await fetch(`${API_BASE}/modules/${encodeURIComponent(name)}`, { cache: "no-store" }).catch(() => null);
    if (!res || !res.ok) {
      res = await fetch(`http://localhost:3030/api/modules/${encodeURIComponent(name)}`, { cache: "no-store" }).catch(() => null);
    }
    if (!res || !res.ok) return null;
    return res.json();
  } catch {
    return null;
  }
}

export async function saveModule(name: string, content: unknown): Promise<{ ok: boolean; error?: string }> {
  const body = JSON.stringify({ name, content });
  const headers = { "Content-Type": "application/json" };

  // Prefer same-origin proxy first (avoids CORS / mixed-origin failures).
  const endpoints = [
    `${API_BASE}/modules`,
    `http://localhost:3030/api/modules`,
    `http://localhost:3030/api/modules/${encodeURIComponent(name)}`,
  ];

  let lastError = "Serveur non joignable. Démarre ui-server sur :3030.";

  for (const url of endpoints) {
    try {
      const payload = url.includes("/modules/") && !url.endsWith("/modules")
        ? JSON.stringify(content)
        : body;
      const res = await fetch(url, { method: "POST", headers, body: payload });
      if (!res.ok) {
        lastError = `HTTP ${res.status}: ${(await res.text().catch(() => "")).slice(0, 200)}`;
        continue;
      }
      const data = (await res.json().catch(() => ({ ok: true }))) as {
        ok?: boolean;
        error?: string;
      };
      if (data.ok === false) {
        return { ok: false, error: data.error || "Échec de sauvegarde" };
      }
      return { ok: true };
    } catch (e) {
      lastError = e instanceof Error ? e.message : String(e);
    }
  }

  return { ok: false, error: lastError };
}

export async function simulate(config: unknown): Promise<SimulateResponse> {
  const healthy = await checkHealth();
  if (!healthy) {
    throw new Error(
      `Entropyk API server is not responding on ${API_BASE}/health. Run: cargo run -p entropyk-demo --bin ui-server`,
    );
  }

  let res: Response;
  try {
    res = await fetch(`${API_BASE}/simulate`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(config),
    });
  } catch (e) {
    const detail = e instanceof Error ? e.message : String(e);
    throw new Error(
      `Cannot reach Entropyk API server at ${API_BASE}. The Rust ui-server likely died (not CoolProp — the server is multitask with proper locking). Restart: cargo run -p entropyk-demo --bin ui-server. Detail: ${detail}`,
    );
  }
  if (!res.ok) {
    const body = (await res.text().catch(() => "")).trim();
    // A bare 500 with Next's generic "Internal Server Error" body means the
    // Rust process died mid-request or is busy on a long solve (the proxy
    // gives up) — it is NOT a config rejection. Solver rejections come back
    // as HTTP 200 with `ok:false` and a real message.
    const isProxyDeath =
      res.status === 500 && /^internal server error$/i.test(body);
    const hint = isProxyDeath
      ? " — the Rust ui-server crashed or is stuck on a long solve. Restart it (tools/start-ui.bat) and retry; the last payload was dumped to target/tmp/last_simulate_request.json for reproduction."
      : res.status === 500
        ? " — solver rejected the configuration (under/over-constrained, missing fluid, etc.). Check the canvas and the diagnostics tab."
        : res.status === 502 || res.status === 504
          ? " — proxy timeout. The Rust server may be restarting; retry in a few seconds."
          : "";
    throw new Error(
      `Simulate failed: HTTP ${res.status}${hint}${body && !isProxyDeath ? `\n${body.slice(0, 500)}` : ""}`,
    );
  }
  return res.json();
}

export async function fetchMollier(fluid: string): Promise<MollierResponse> {
  const res = await fetch(`${API_BASE}/mollier?fluid=${encodeURIComponent(fluid)}`, { cache: "no-store" });
  if (!res.ok) throw new Error(`Failed to fetch Mollier data: ${res.status}`);
  return res.json();
}

export async function checkHealth(): Promise<boolean> {
  try {
    const res = await fetch(`${API_BASE}/health`, { cache: "no-store" });
    return res.ok;
  } catch {
    return false;
  }
}
