#!/usr/bin/env python3
"""
===============================================================================
HDGL HYPER-NESTED COUNTER-ROTATING MANIFOLD
===============================================================================

Combines two pieces:

  1. The HDGL reciprocal substrate:

        T(X)      = 1 + 1/X
        Ω         = Fix(T)              (emergent, iterated — not stored)
        θ+        = +2π i Ω             (counter-rotating branch)
        θ-        = -2π i Ω             (counter-rotating branch)
        ρ(r)      = r / T(r)            (reciprocal radial drain)
        B_i ∈ {0,1}, τ_i ∈ {-1,0,+1}    (simultaneous binary/trinary substrate)

  2. A multi-shell, multi-thread "Hyper-Nested Tesla" fluidic manifold built
     in FreeCAD. Its spiral placement previously used arbitrary constants
     (a fixed "num_cells / 1.1" turn ratio, a hand-tuned additive radial
     growth term). Those are replaced below by the HDGL substrate itself:
     the counter-rotation direction per shell IS the θ+/θ- branch, the
     angular rate is driven by the emergent Ω, and the radius is passed
     through the same reciprocal drain ρ = r / T(r) before being placed —
     which also happens to read naturally as a converging vortex core,
     which is exactly the flow behavior a Tesla-valve-style manifold wants.

     The discrete binary/trinary substrate (defined on integer indices in
     the original field model) is carried over as a continuous analogue —
     sin(π·progress) / sin(2π·progress/3) — so the channel surface stays
     smooth enough to loft into a solid. Physical/manufacturing dimensions
     (channel width, wall thickness, pitch, segment length) are left as
     explicit tunables rather than derived from the substrate, since they
     are real-world units, not field parameters.

Run this inside FreeCAD's Python console to build the solid. Run it
standalone (matplotlib only, no FreeCAD) to inspect the underlying HDGL
field via the diagnostic plots before committing to a boolean cut.
===============================================================================
"""

from pathlib import Path
import math

import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

try:
    import FreeCAD as App
    import Part
    HAVE_FREECAD = True
except ImportError:
    HAVE_FREECAD = False


# ============================================================================
# 1. HDGL CORE SUBSTRATE
# ============================================================================

def T(x):
    """Reciprocal HDGL transformation: T(X) = 1 + 1/X."""
    return 1.0 + 1.0 / x


def emergent_omega(seed=1.5, tol=1e-15, max_iter=1000):
    """
    Iterate T to its fixed point. Fix(T) solves x = 1 + 1/x, i.e.
    x^2 - x - 1 = 0, so this converges to the golden ratio φ regardless of
    seed — emerges from the recursion rather than being hardcoded.
    """
    x = seed
    for _ in range(max_iter):
        y = T(x)
        if abs(y - x) < tol:
            return y
        x = y
    return x


OMEGA = emergent_omega()  # emergent fixed point of T — converges to φ ≈ 1.618...


def omega_orbit(seed=1.5, steps=80):
    values = np.empty(steps + 1, dtype=np.float64)
    values[0] = seed
    for i in range(steps):
        values[i + 1] = T(values[i])
    return values


# ============================================================================
# 2. DIAGNOSTIC PLOTS — inspect the field before cutting any geometry
# ============================================================================

BASE_DIR = Path(__file__).resolve().parent if "__file__" in globals() else Path.cwd()
OUT_DIR = BASE_DIR / "hdgl_graphs"
OUT_DIR.mkdir(parents=True, exist_ok=True)

N_DIAG = 2400


def substrate_states(n):
    i = np.arange(n, dtype=np.int64)
    binary = (i & 1).astype(np.float64)
    trinary = ((i % 3) - 1).astype(np.float64)
    return i, binary, trinary


def build_radial_substrate(n=N_DIAG):
    i, binary, trinary = substrate_states(n)
    r_base = np.sqrt(i + 1.0)
    modulation = 1.0 + 0.075 * binary + 0.050 * trinary
    r = r_base * modulation
    reciprocal = T(r)
    drain_factor = 1.0 / reciprocal
    rho = r * drain_factor
    return i, binary, trinary, r, reciprocal, drain_factor, rho


def build_counter_rotating_phyllotaxis(n=N_DIAG):
    i, binary, trinary, r, reciprocal, drain_factor, rho = build_radial_substrate(n)

    theta_plus = 2.0 * math.pi * i * OMEGA
    theta_minus = -2.0 * math.pi * i * OMEGA

    phase_modulation = 0.075 * binary + 0.050 * trinary
    theta_plus = theta_plus + phase_modulation
    theta_minus = theta_minus - phase_modulation

    x_plus, y_plus = rho * np.cos(theta_plus), rho * np.sin(theta_plus)
    x_minus, y_minus = rho * np.cos(theta_minus), rho * np.sin(theta_minus)

    return (i, binary, trinary, r, reciprocal, drain_factor, rho,
            theta_plus, theta_minus, x_plus, y_plus, x_minus, y_minus)


def plot_counter_rotating_phyllotaxis():
    (i, binary, trinary, r, reciprocal, drain_factor, rho,
     theta_plus, theta_minus, x_plus, y_plus, x_minus, y_minus) = build_counter_rotating_phyllotaxis()

    fig, ax = plt.subplots(figsize=(11, 11))
    ax.scatter(x_plus, y_plus, s=4, alpha=0.45, linewidths=0, label="counter-rotation +Ω")
    ax.scatter(x_minus, y_minus, s=4, alpha=0.45, linewidths=0, label="counter-rotation −Ω")
    ax.scatter([0], [0], s=70, marker="o", label="drain")
    ax.set_aspect("equal", adjustable="box")
    ax.set_title("HDGL COUNTER-ROTATING PHYLLOTAXIS\nSimultaneous ±Ω with Reciprocal Drain")
    ax.set_xlabel("counter-rotating substrate")
    ax.set_ylabel("counter-rotating substrate")
    ax.grid(True, alpha=0.20)
    ax.legend()
    fig.tight_layout()

    path = OUT_DIR / "hdgl_counter_rotating_phyllotaxis.png"
    fig.savefig(path, dpi=180, bbox_inches="tight")
    plt.close(fig)
    return path


def plot_drain_profile():
    i, binary, trinary, r, reciprocal, drain_factor, rho = build_radial_substrate()

    fig, ax = plt.subplots(figsize=(11, 7))
    ax.plot(i, r, linewidth=1.2, label="outward radius r")
    ax.plot(i, rho, linewidth=1.2, label="drained radius ρ")
    ax.set_title("HDGL RECIPROCAL DRAIN\nρ = r / T(r)")
    ax.set_xlabel("substrate index i")
    ax.set_ylabel("radial coordinate")
    ax.grid(True, alpha=0.20)
    ax.legend()
    fig.tight_layout()

    path = OUT_DIR / "hdgl_drain_profile.png"
    fig.savefig(path, dpi=180, bbox_inches="tight")
    plt.close(fig)
    return path


def plot_omega_orbit():
    values = omega_orbit()
    n = np.arange(len(values))

    fig, ax = plt.subplots(figsize=(11, 7))
    ax.plot(n, values, linewidth=1.5, marker="o", markersize=3)
    ax.axhline(OMEGA, linestyle="--", linewidth=1.0)
    ax.set_title("HDGL Ω RECURSION\nΩₙ₊₁ = T(Ωₙ) = 1 + 1/Ωₙ")
    ax.set_xlabel("iteration n")
    ax.set_ylabel("Ωₙ")
    ax.grid(True, alpha=0.20)
    ax.text(0.98, 0.05, f"Emergent Ω ≈ {OMEGA:.15f}", transform=ax.transAxes, ha="right", va="bottom")
    fig.tight_layout()

    path = OUT_DIR / "hdgl_omega_orbit.png"
    fig.savefig(path, dpi=180, bbox_inches="tight")
    plt.close(fig)
    return path


def run_diagnostics():
    print("=" * 79)
    print("HDGL FIELD DIAGNOSTICS")
    print("=" * 79)
    print(f"Emergent Ω ≈ {OMEGA:.15f}  (Fix(T), T(X)=1+1/X — this is φ)")
    print(f"Fixed-point residual: Ω² - Ω - 1 ≈ {OMEGA * OMEGA - OMEGA - 1.0:.3e}")
    paths = [
        plot_counter_rotating_phyllotaxis(),
        plot_drain_profile(),
        plot_omega_orbit(),
    ]
    for p in paths:
        print(f"    [OK] {p}")
    print("=" * 79)
    return paths


# ============================================================================
# 3. HYPER-NESTED MANIFOLD GEOMETRY (FreeCAD) — driven by the HDGL substrate
# ============================================================================

# --- physical / manufacturing parameters: real-world dimensions, kept as
#     explicit tunables rather than derived from the substrate ---
L = 32.0                # local channel segment length
A_base = 12.0           # base channel lateral amplitude
k = 2.6                 # channel re-injection sharpness
num_cells = 6
channel_width = 3.8
pitch_base = 40.0       # vertical climb per full substrate turn

num_shells = 3          # concentric radial layers
threads_per_shell = 3   # multi-start threads per layer
R0_start = 50.0
shell_spacing = 25.0
wall_thickness = 7.0

TOTAL_L = num_cells * L


def map_to_hdgl_spiral(vec, theta_offset, r_shell, shell_idx):
    """
    Places a local channel cross-section onto the manifold using the HDGL
    counter-rotating substrate instead of arbitrary spiral constants:

      - direction alternates by shell: this IS the θ+ / θ- branch choice
      - angular rate is scaled by the emergent Ω (=φ), not a tuned ratio
      - radius is passed through the reciprocal drain ρ = r / T(r) rather
        than being purely additive, pulling flow toward a shared closure
      - the binary/trinary substrate modulation is carried over as its
        smooth continuous analogue so the resulting surface stays
        loft-able into a solid
    """
    direction = 1.0 if shell_idx % 2 == 0 else -1.0

    progress = (vec.x / TOTAL_L) * num_cells   # continuous substrate index
    turns = progress * OMEGA                    # rotation magnitude (direction-free)

    binary_mod = math.sin(math.pi * progress)               # continuous period-2 analogue
    trinary_mod = math.sin(2.0 * math.pi * progress / 3.0)  # continuous period-3 analogue
    phase_modulation = 0.075 * binary_mod + 0.050 * trinary_mod

    angle = (2.0 * math.pi * turns * direction) + theta_offset + (phase_modulation * direction)

    r_growth = (20.0 + (shell_idx * 10)) * turns
    r_raw = r_shell + r_growth + vec.y
    r = r_raw / T(r_raw)   # reciprocal HDGL drain closure

    z = (pitch_base * turns) + vec.z

    return App.Vector(r * math.cos(angle), r * math.sin(angle), z)


def make_tri_face(p1, p2, p3):
    return Part.Face(Part.makePolygon([p1, p2, p3, p1]))


def create_hyper_segment(x_offset, mirror, theta_off, r_shell, s_idx):
    m = -1 if mirror else 1
    num_pts = 30
    paths = [[], [], [], []]

    A = A_base + (s_idx * 4)
    c_height = 6.0 + (s_idx * 2)

    for i in range(num_pts):
        t = i / (num_pts - 1)
        lx = (L + 1.5) * t + x_offset - 0.75
        ly = m * A * math.sin(math.pi * t) * math.exp(-k * t) * (1 - t) ** 1.2

        p = App.Vector(lx, ly, 0)
        corners = [
            App.Vector(p.x, p.y - channel_width / 2, 0),
            App.Vector(p.x, p.y + channel_width / 2, 0),
            App.Vector(p.x, p.y + channel_width / 2, c_height),
            App.Vector(p.x, p.y - channel_width / 2, c_height),
        ]
        for j in range(4):
            paths[j].append(map_to_hdgl_spiral(corners[j], theta_off, r_shell, s_idx))

    faces = []
    for j in range(4):
        p_curr, p_next = paths[j], paths[(j + 1) % 4]
        for i in range(len(p_curr) - 1):
            a, b, c, d = p_curr[i], p_curr[i + 1], p_next[i], p_next[i + 1]
            faces.append(make_tri_face(a, b, d))
            faces.append(make_tri_face(a, d, c))

    s_cap, e_cap = [p[0] for p in paths], [p[-1] for p in paths]
    faces.extend([make_tri_face(s_cap[0], s_cap[1], s_cap[2]), make_tri_face(s_cap[0], s_cap[2], s_cap[3])])
    faces.extend([make_tri_face(e_cap[0], e_cap[1], e_cap[2]), make_tri_face(e_cap[0], e_cap[2], e_cap[3])])
    return Part.Solid(Part.makeShell(faces))


def build_manifold():
    doc = App.activeDocument() or App.newDocument("HDGL_HyperNested_Manifold")

    all_voids = []
    for s_idx in range(num_shells):
        r_shell = R0_start + (s_idx * shell_spacing)
        for t_idx in range(threads_per_shell):
            start_angle = (2 * math.pi / threads_per_shell) * t_idx
            for n in range(num_cells):
                all_voids.append(create_hyper_segment(n * L, False, start_angle, r_shell, s_idx))
                all_voids.append(create_hyper_segment(n * L + (L / 2), True, start_angle, r_shell, s_idx))

    print(f"Weaving {len(all_voids)} HDGL-nested segments (Ω={OMEGA:.6f})...")
    fluid_path = all_voids[0]
    for s in all_voids[1:]:
        fluid_path = fluid_path.fuse(s)

    bb = fluid_path.BoundBox
    outer_r = bb.XMax + wall_thickness
    housing = Part.makeCylinder(outer_r, bb.ZMax + wall_thickness, App.Vector(0, 0, -2))
    inner_core = Part.makeCylinder(R0_start - wall_thickness, bb.ZMax + wall_thickness, App.Vector(0, 0, -2))
    body = housing.cut(inner_core)

    print("Executing final Boolean subtraction...")
    hdgl_manifold = body.cut(fluid_path)

    obj = doc.addObject("Part::Feature", "HDGL_HyperNested_Manifold")
    obj.Shape = hdgl_manifold
    if App.GuiUp:
        obj.ViewObject.ShapeColor = (0.05, 0.05, 0.1)
        obj.ViewObject.Transparency = 70
    doc.recompute()
    print("HDGL-driven nested manifold complete.")
    return obj


# ============================================================================
# MAIN
# ============================================================================

if __name__ == "__main__":
    run_diagnostics()

    if HAVE_FREECAD:
        build_manifold()
    else:
        print()
        print("FreeCAD not available in this environment — ran field diagnostics only.")
        print("Open this script inside FreeCAD's Python console to build the solid.")