# -*- coding: utf-8 -*-
"""
Human Design Engine — berechnet aus Geburtsdaten (Datum, Uhrzeit UT, Ort)
den kompletten Human-Design-Chart:
Tore + Linien (Personality & Design), Kanäle, definierte Zentren,
Typ, Autorität, Profil, Definition, Inkarnationskreuz.

Basis: Swiss Ephemeris (pyswisseph), True Node.
Design-Berechnung: Sonne exakt 88° Sonnenbogen vor der Geburt.
"""

import swisseph as swe

# ---------------------------------------------------------------
# 1. DAS HD-MANDALA: Tor-Reihenfolge im Tierkreis
#    Anker: Tor 41 beginnt bei 02°00'00" Wassermann (= 302.0°)
#    Jedes Tor = 5.625°, jede Linie = 0.9375°
# ---------------------------------------------------------------
GATE_WHEEL = [41, 19, 13, 49, 30, 55, 37, 63, 22, 36, 25, 17, 21, 51,
              42, 3, 27, 24, 2, 23, 8, 20, 16, 35, 45, 12, 15, 52,
              39, 53, 62, 56, 31, 33, 7, 4, 29, 59, 40, 64, 47, 6,
              46, 18, 48, 57, 32, 50, 28, 44, 1, 43, 14, 34, 9, 5,
              26, 11, 10, 58, 38, 54, 61, 60]

WHEEL_START = 302.0   # 2° Aquarius
GATE_SPAN = 5.625
LINE_SPAN = GATE_SPAN / 6.0


def lon_to_gate_line(lon):
    """Ekliptische Länge -> (Tor, Linie, Farbe, Ton)"""
    offset = (lon - WHEEL_START) % 360.0
    idx = int(offset // GATE_SPAN)
    gate = GATE_WHEEL[idx]
    within = offset - idx * GATE_SPAN
    line = int(within // LINE_SPAN) + 1
    within_line = within - (line - 1) * LINE_SPAN
    color = int(within_line // (LINE_SPAN / 6.0)) + 1
    within_color = within_line - (color - 1) * (LINE_SPAN / 6.0)
    tone = int(within_color // (LINE_SPAN / 36.0)) + 1
    return gate, line, color, tone


# ---------------------------------------------------------------
# 2. ZENTREN & KANÄLE
# ---------------------------------------------------------------
CENTERS = {
    'Kopf':        [64, 61, 63],
    'Ajna':        [47, 24, 4, 17, 43, 11],
    'Kehle':       [62, 23, 56, 16, 35, 12, 45, 20, 31, 8, 33],
    'G':           [1, 13, 25, 46, 2, 15, 10, 7],
    'Herz':        [21, 26, 51, 40],
    'Sakral':      [34, 5, 14, 29, 59, 9, 3, 42, 27],
    'Milz':        [48, 57, 44, 50, 32, 28, 18],
    'Solarplexus': [36, 22, 37, 6, 49, 55, 30],
    'Wurzel':      [53, 60, 52, 19, 39, 41, 58, 38, 54],
}
GATE_CENTER = {g: c for c, gates in CENTERS.items() for g in gates}

CHANNELS = [
    (1, 8), (2, 14), (3, 60), (4, 63), (5, 15), (6, 59), (7, 31),
    (9, 52), (10, 20), (10, 34), (10, 57), (11, 56), (12, 22),
    (13, 33), (16, 48), (17, 62), (18, 58), (19, 49), (20, 34),
    (20, 57), (21, 45), (23, 43), (24, 61), (25, 51), (26, 44),
    (27, 50), (28, 38), (29, 46), (30, 41), (32, 54), (34, 57),
    (35, 36), (37, 40), (39, 55), (42, 53), (47, 64),
]

CHANNEL_NAMES = {
    (1, 8): 'Inspiration', (2, 14): 'Der Schlag', (3, 60): 'Mutation',
    (4, 63): 'Logik', (5, 15): 'Rhythmus', (6, 59): 'Intimität',
    (7, 31): 'Alpha', (9, 52): 'Konzentration', (10, 20): 'Erwachen',
    (10, 34): 'Erforschung', (10, 57): 'Perfektionierte Form',
    (11, 56): 'Neugier', (12, 22): 'Offenheit', (13, 33): 'Der verlorene Sohn',
    (16, 48): 'Talent', (17, 62): 'Akzeptanz', (18, 58): 'Urteilsvermögen',
    (19, 49): 'Synthese', (20, 34): 'Charisma', (20, 57): 'Hellhörigkeit',
    (21, 45): 'Geldlinie', (23, 43): 'Strukturierung', (24, 61): 'Bewusstheit',
    (25, 51): 'Initiation', (26, 44): 'Übergabe', (27, 50): 'Bewahrung',
    (28, 38): 'Kampf', (29, 46): 'Entdeckung', (30, 41): 'Erkennen',
    (32, 54): 'Verwandlung', (34, 57): 'Kraft', (35, 36): 'Vergänglichkeit',
    (37, 40): 'Gemeinschaft', (39, 55): 'Emotionalität', (42, 53): 'Reifung',
    (47, 64): 'Abstraktion',
}

MOTORS = {'Sakral', 'Herz', 'Solarplexus', 'Wurzel'}

# ---------------------------------------------------------------
# 3. INKARNATIONSKREUZ-NAMEN (Schlüssel: Personality-Sonnen-Tor)
#    (Rechtwinkliges Kreuz / Juxtaposition / Linkswinkliges Kreuz)
# ---------------------------------------------------------------
CROSS_NAMES = {
    1: ('Sphinx', 'Selbstausdruck', 'Trotz'), 2: ('Sphinx', 'Lenker', 'Trotz'),
    3: ('Gesetze', 'Mutation', 'Wünsche'), 4: ('Erklärung', 'Formulierung', 'Revolution'),
    5: ('Bewusstsein', 'Gewohnheiten', 'Trennung'), 6: ('Eden', 'Konflikt', 'Ebene'),
    7: ('Sphinx', 'Interaktion', 'Masken'), 8: ('Ansteckung', 'Beitrag', 'Ungewissheit'),
    9: ('Planung', 'Fokus', 'Identifikation'), 10: ('Gefäß der Liebe', 'Verhalten', 'Verhinderung'),
    11: ('Eden', 'Ideen', 'Bildung'), 12: ('Eden', 'Artikulation', 'Bildung'),
    13: ('Sphinx', 'Zuhören', 'Masken'), 14: ('Ansteckung', 'Ermächtigung', 'Ungewissheit'),
    15: ('Gefäß der Liebe', 'Extreme', 'Verhinderung'), 16: ('Planung', 'Experimentieren', 'Identifikation'),
    17: ('Dienst', 'Meinungen', 'Umbruch'), 18: ('Dienst', 'Korrektur', 'Umbruch'),
    19: ('Vier Wege', 'Bedürfnis', 'Verfeinerung'), 20: ('Schlafender Phönix', 'Das Jetzt', 'Dualität'),
    21: ('Spannung', 'Kontrolle', 'Bestreben'), 22: ('Herrschaft', 'Anmut', 'Information'),
    23: ('Erklärung', 'Assimilation', 'Hingabe'), 24: ('Vier Wege', 'Rationalisierung', 'Inkarnation'),
    25: ('Gefäß der Liebe', 'Unschuld', 'Heilung'), 26: ('Herrschaft', 'Trickster', 'Konfrontation'),
    27: ('Das Unerwartete', 'Fürsorge', 'Ausrichtung'), 28: ('Das Unerwartete', 'Risiken', 'Ausrichtung'),
    29: ('Ansteckung', 'Verpflichtung', 'Fleiß'), 30: ('Ansteckung', 'Gefühle', 'Fleiß'),
    31: ('Das Unerwartete', 'Einfluss', 'Alpha'), 32: ('Maya', 'Bewahrung', 'Begrenzung'),
    33: ('Vier Wege', 'Rückzug', 'Verfeinerung'), 34: ('Schlafender Phönix', 'Kraft', 'Dualität'),
    35: ('Bewusstsein', 'Erfahrung', 'Trennung'), 36: ('Eden', 'Krise', 'Ebene'),
    37: ('Planung', 'Abmachungen', 'Migration'), 38: ('Spannung', 'Opposition', 'Individualismus'),
    39: ('Spannung', 'Provokation', 'Individualismus'), 40: ('Planung', 'Verweigerung', 'Migration'),
    41: ('Das Unerwartete', 'Fantasie', 'Alpha'), 42: ('Maya', 'Vollendung', 'Begrenzung'),
    43: ('Erklärung', 'Einsicht', 'Hingabe'), 44: ('Vier Wege', 'Wachsamkeit', 'Inkarnation'),
    45: ('Herrschaft', 'Besitz', 'Konfrontation'), 46: ('Gefäß der Liebe', 'Glücksfund', 'Heilung'),
    47: ('Herrschaft', 'Unterdrückung', 'Information'), 48: ('Spannung', 'Tiefe', 'Bestreben'),
    49: ('Erklärung', 'Prinzipien', 'Revolution'), 50: ('Gesetze', 'Werte', 'Wünsche'),
    51: ('Durchdringung', 'Schock', 'Fanfare'), 52: ('Dienst', 'Stille', 'Forderungen'),
    53: ('Durchdringung', 'Anfänge', 'Zyklen'), 54: ('Durchdringung', 'Ehrgeiz', 'Zyklen'),
    55: ('Schlafender Phönix', 'Stimmungen', 'Geist'), 56: ('Gesetze', 'Stimulation', 'Ablenkung'),
    57: ('Durchdringung', 'Intuition', 'Fanfare'), 58: ('Dienst', 'Vitalität', 'Forderungen'),
    59: ('Schlafender Phönix', 'Strategie', 'Geist'), 60: ('Gesetze', 'Begrenzung', 'Ablenkung'),
    61: ('Maya', 'Denken', 'Verschleierung'), 62: ('Maya', 'Detail', 'Verschleierung'),
    63: ('Bewusstsein', 'Zweifel', 'Herrschaftsgebiet'), 64: ('Bewusstsein', 'Verwirrung', 'Herrschaftsgebiet'),
}

RIGHT_ANGLE_PROFILES = {(1, 3), (1, 4), (2, 4), (2, 5), (3, 5), (3, 6), (4, 6)}
JUXTAPOSITION_PROFILES = {(4, 1)}
LEFT_ANGLE_PROFILES = {(5, 1), (5, 2), (6, 2), (6, 3)}

# ---------------------------------------------------------------
# 3b. VARIABLES (PHS / Rave Psychologie)
#     Determination = Design-Sonne (Farbe/Ton)
#     Environment   = Design-Knoten
#     Motivation    = Personality-Sonne
#     Perspective   = Personality-Knoten
#     Pfeil: Ton 1-3 = links, Ton 4-6 = rechts
#     (validiert gegen Dirks offiziellen Chart: 5/2 links, 4/4 rechts,
#      4/3 links, 3/4 rechts)
# ---------------------------------------------------------------
DETERMINATION_COLORS = {1: 'Appetit', 2: 'Geschmack', 3: 'Durst',
                        4: 'Berührung', 5: 'Klang', 6: 'Licht'}
ENVIRONMENT_COLORS = {1: 'Höhlen', 2: 'Märkte', 3: 'Küchen',
                      4: 'Berge', 5: 'Täler', 6: 'Ufer'}
MOTIVATION_COLORS = {1: 'Angst', 2: 'Hoffnung', 3: 'Begehren',
                     4: 'Bedürfnis', 5: 'Schuld', 6: 'Unschuld'}
PERSPECTIVE_COLORS = {1: 'Überleben', 2: 'Möglichkeit', 3: 'Kraft',
                      4: 'Wollen', 5: 'Wahrscheinlichkeit', 6: 'Persönlich'}

PLANET_ORDER = ['Sonne', 'Erde', 'Mond', 'Nordknoten', 'Suedknoten',
                'Merkur', 'Venus', 'Mars', 'Jupiter', 'Saturn',
                'Uranus', 'Neptun', 'Pluto']

PLANET_SYMBOLS = {'Sonne': '\u2609', 'Erde': '\u2295', 'Mond': '\u263D',
                  'Nordknoten': '\u260A', 'Suedknoten': '\u260B',
                  'Merkur': '\u263F', 'Venus': '\u2640', 'Mars': '\u2642',
                  'Jupiter': '\u2643', 'Saturn': '\u2644',
                  'Uranus': '\u2645', 'Neptun': '\u2646', 'Pluto': '\u2647'}


def planet_longitudes(jd_ut):
    """Alle 13 HD-Positionen für einen Zeitpunkt (UT, Julianisches Datum)."""
    flags = swe.FLG_SWIEPH | swe.FLG_SPEED
    res = {}
    sun = swe.calc_ut(jd_ut, swe.SUN, flags)[0][0]
    res['Sonne'] = sun
    res['Erde'] = (sun + 180.0) % 360.0
    res['Mond'] = swe.calc_ut(jd_ut, swe.MOON, flags)[0][0]
    nn = swe.calc_ut(jd_ut, swe.TRUE_NODE, flags)[0][0]
    res['Nordknoten'] = nn
    res['Suedknoten'] = (nn + 180.0) % 360.0
    for name, pl in [('Merkur', swe.MERCURY), ('Venus', swe.VENUS),
                     ('Mars', swe.MARS), ('Jupiter', swe.JUPITER),
                     ('Saturn', swe.SATURN), ('Uranus', swe.URANUS),
                     ('Neptun', swe.NEPTUNE), ('Pluto', swe.PLUTO)]:
        res[name] = swe.calc_ut(jd_ut, pl, flags)[0][0]
    return res


def find_design_jd(jd_birth):
    """Zeitpunkt, an dem die Sonne exakt 88° vor der Geburtssonne stand."""
    sun_birth = swe.calc_ut(jd_birth, swe.SUN)[0][0]
    target = (sun_birth - 88.0) % 360.0
    # Startschätzung: ~88 Tage zurück, dann Newton-Iteration
    jd = jd_birth - 88.0
    for _ in range(60):
        sun = swe.calc_ut(jd, swe.SUN, swe.FLG_SWIEPH | swe.FLG_SPEED)
        lon, speed = sun[0][0], sun[0][3]
        diff = (target - lon + 180.0) % 360.0 - 180.0
        if abs(diff) < 1e-9:
            break
        jd += diff / speed
    return jd


def compute_chart(year, month, day, ut_hour, ut_min, name=''):
    """Kompletter HD-Chart. Uhrzeit in UT (Weltzeit)!"""
    jd_birth = swe.julday(year, month, day, ut_hour + ut_min / 60.0)
    jd_design = find_design_jd(jd_birth)

    activations = {'personality': {}, 'design': {}}
    for side, jd in [('personality', jd_birth), ('design', jd_design)]:
        lons = planet_longitudes(jd)
        for planet in PLANET_ORDER:
            g, l, c, t = lon_to_gate_line(lons[planet])
            activations[side][planet] = {
                'gate': g, 'line': l, 'color': c, 'tone': t,
                'lon': lons[planet]
            }

    # Aktive Tore (beide Seiten)
    gates_p = {a['gate'] for a in activations['personality'].values()}
    gates_d = {a['gate'] for a in activations['design'].values()}
    all_gates = gates_p | gates_d

    # Komplette Kanäle
    active_channels = [(a, b) for (a, b) in CHANNELS
                       if a in all_gates and b in all_gates]

    # Definierte Zentren
    defined = set()
    for (a, b) in active_channels:
        defined.add(GATE_CENTER[a])
        defined.add(GATE_CENTER[b])

    # Verbundene Gruppen (Definition)
    adj = {c: set() for c in defined}
    for (a, b) in active_channels:
        ca, cb = GATE_CENTER[a], GATE_CENTER[b]
        adj[ca].add(cb)
        adj[cb].add(ca)
    components = []
    seen = set()
    for c in defined:
        if c in seen:
            continue
        comp, stack = set(), [c]
        while stack:
            x = stack.pop()
            if x in comp:
                continue
            comp.add(x)
            stack.extend(adj[x] - comp)
        seen |= comp
        components.append(comp)

    n_comp = len(components)
    definition = {0: 'Keine Definition', 1: 'Single Definition',
                  2: 'Split Definition', 3: 'Triple Split Definition',
                  4: 'Quadruple Split Definition'}[n_comp]

    # Typ
    sacral = 'Sakral' in defined
    throat_motor = any('Kehle' in comp and (comp & MOTORS)
                       for comp in components)
    if not defined:
        hd_type = 'Reflektor'
    elif sacral and throat_motor:
        hd_type = 'Manifestierender Generator'
    elif sacral:
        hd_type = 'Generator'
    elif throat_motor:
        hd_type = 'Manifestor'
    else:
        hd_type = 'Projektor'

    # Autorität
    if 'Solarplexus' in defined:
        authority = 'Emotional (Solarplexus)'
    elif sacral:
        authority = 'Sakral'
    elif 'Milz' in defined:
        authority = 'Milz (Splenisch)'
    elif 'Herz' in defined and throat_motor:
        authority = 'Ego (Herz)'
    elif 'G' in defined and any('Kehle' in comp and 'G' in comp
                                for comp in components):
        authority = 'Selbst-projiziert'
    elif hd_type == 'Reflektor':
        authority = 'Lunar (Mondzyklus)'
    else:
        authority = 'Mental (Umgebung/Sounding Board)'

    # Profil
    p_line = activations['personality']['Sonne']['line']
    d_line = activations['design']['Sonne']['line']
    profile = (p_line, d_line)
    profile_names = {1: 'Forscher', 2: 'Eremit', 3: 'Märtyrer',
                     4: 'Opportunist', 5: 'Ketzer', 6: 'Rollenmodell'}
    profile_str = f"{p_line}/{d_line} ({profile_names[p_line]}/{profile_names[d_line]})"

    # Inkarnationskreuz
    ps = activations['personality']['Sonne']['gate']
    pe = activations['personality']['Erde']['gate']
    ds = activations['design']['Sonne']['gate']
    de = activations['design']['Erde']['gate']
    if profile in RIGHT_ANGLE_PROFILES:
        angle, name_idx = 'Rechtwinkliges Kreuz', 0
    elif profile in JUXTAPOSITION_PROFILES:
        angle, name_idx = 'Juxtapositionskreuz', 1
    else:
        angle, name_idx = 'Linkswinkliges Kreuz', 2
    cross = f"{angle} – {CROSS_NAMES[ps][name_idx]} ({ps}/{pe} | {ds}/{de})"

    # Variables (PHS): Farbe/Ton der vier Schlüssel-Aktivierungen
    def variable(act, names):
        c, t = act['color'], act['tone']
        return {'color': c, 'tone': t,
                'arrow': 'links' if t <= 3 else 'rechts',
                'name': names[c]}

    variables = {
        'determination': variable(activations['design']['Sonne'],
                                  DETERMINATION_COLORS),
        'environment': variable(activations['design']['Nordknoten'],
                                ENVIRONMENT_COLORS),
        'motivation': variable(activations['personality']['Sonne'],
                               MOTIVATION_COLORS),
        'perspective': variable(activations['personality']['Nordknoten'],
                                PERSPECTIVE_COLORS),
    }

    return {
        'name': name,
        'activations': activations,
        'gates_personality': gates_p,
        'gates_design': gates_d,
        'all_gates': all_gates,
        'channels': active_channels,
        'defined_centers': defined,
        'components': components,
        'definition': definition,
        'type': hd_type,
        'authority': authority,
        'profile': profile_str,
        'cross': cross,
        'variables': variables,
        'jd_birth': jd_birth,
        'jd_design': jd_design,
    }


def print_chart(chart):
    print('=' * 64)
    print(f"HD-CHART: {chart['name']}")
    print('=' * 64)
    print(f"Typ:        {chart['type']}")
    print(f"Autorität:  {chart['authority']}")
    print(f"Profil:     {chart['profile']}")
    print(f"Definition: {chart['definition']}")
    print(f"Kreuz:      {chart['cross']}")
    print('-' * 64)
    print(f"{'Planet':<12} {'Design':>8}   {'Personality':>8}")
    for p in PLANET_ORDER:
        d = chart['activations']['design'][p]
        pe = chart['activations']['personality'][p]
        print(f"{p:<12} {d['gate']:>5}.{d['line']}   {pe['gate']:>8}.{pe['line']}")
    print('-' * 64)
    print(f"Definierte Zentren: {', '.join(sorted(chart['defined_centers']))}")
    print("Kanäle:")
    for (a, b) in chart['channels']:
        print(f"  {a}-{b}  {CHANNEL_NAMES[(a, b)]}")
    print('=' * 64)
