import 'dart:math' as math;
/// Solar term (节气) calculator for 1900–2100
/// Uses astronomical approximation sufficient for Bazi month boundaries.
/// © 2026 Jason Li. All rights reserved.

class SolarTerm {
  final String name;
  final DateTime utcApprox;

  const SolarTerm(this.name, this.utcApprox);
}

class SolarTerms {
  /// 24 solar terms in order; month branch uses 节 (odd indices starting from 立春)
  static const names = [
    '小寒', '大寒', '立春', '雨水', '惊蛰', '春分',
    '清明', '谷雨', '立夏', '小满', '芒种', '夏至',
    '小暑', '大暑', '立秋', '处暑', '白露', '秋分',
    '寒露', '霜降', '立冬', '小雪', '大雪', '冬至',
  ];

  /// Month branch for each 节 (立春=寅 ... 大雪=子, 小寒属于丑月继续)
  /// Index of 节 within year terms that start a new month:
  /// 立春寅, 惊蛰卯, 清明辰, 立夏巳, 芒种午, 小暑未,
  /// 立秋申, 白露酉, 寒露戌, 立冬亥, 大雪子, 小寒丑
  static const jieMonthBranch = {
    '立春': '寅',
    '惊蛰': '卯',
    '清明': '辰',
    '立夏': '巳',
    '芒种': '午',
    '小暑': '未',
    '立秋': '申',
    '白露': '酉',
    '寒露': '戌',
    '立冬': '亥',
    '大雪': '子',
    '小寒': '丑',
  };

  /// Approximate Beijing-time solar term using Centurial formula
  /// Reference: known 2000 values + mean motion
  static DateTime termBeijing(int year, int termIndex) {
    return _accurateTerm(year, termIndex);
  }

  static DateTime _accurateTerm(int year, int termIndex) {
    // Julian century calculation for apparent solar longitude
    // Target longitudes: 285°, 300°, 315°, ... for 小寒 onwards (each +15°)
    final targetLon = (285 + termIndex * 15) % 360;
    // Binary search around estimated date
    var jd = _estimateJd(year, termIndex);
    for (var i = 0; i < 10; i++) {
      final lon = _solarLongitude(jd);
      var diff = (targetLon - lon);
      while (diff > 180) {
        diff -= 360;
      }
      while (diff < -180) {
        diff += 360;
      }
      jd += diff / 0.985647; // ~deg per day
    }
    return _jdToBeijing(jd);
  }

  static double _estimateJd(int year, int termIndex) {
    const approxDay = [
      5.5, 20.5, 4.5, 19.5, 6.0, 21.0,
      5.0, 20.5, 6.0, 21.5, 6.5, 22.0,
      7.5, 23.5, 8.0, 23.5, 8.0, 23.5,
      8.5, 24.0, 8.0, 22.5, 7.5, 22.0,
    ];
    final m = ((termIndex + 1) ~/ 2) % 12 + 1;
    final day = approxDay[termIndex];
    return _julianDay(year, m == 0 ? 12 : m, day.floor(),
        ((day % 1) * 24).round());
  }

  static double _julianDay(int y, int m, int d, int hour) {
    if (m <= 2) {
      y -= 1;
      m += 12;
    }
    final A = y ~/ 100;
    final B = 2 - A + A ~/ 4;
    return (365.25 * (y + 4716)).floorToDouble() +
        (30.6001 * (m + 1)).floorToDouble() +
        d +
        hour / 24.0 +
        B -
        1524.5;
  }

  static double _solarLongitude(double jd) {
    final T = (jd - 2451545.0) / 36525.0;
    final L0 = _norm360(280.46646 + 36000.76983 * T + 0.0003032 * T * T);
    final M = _norm360(
        357.52911 + 35999.05029 * T - 0.0001537 * T * T);
    final Mrad = M * math.pi / 180;
    final C = (1.914602 - 0.004817 * T - 0.000014 * T * T) * math.sin(Mrad) +
        (0.019993 - 0.000101 * T) * math.sin(2 * Mrad) +
        0.000289 * math.sin(3 * Mrad);
    final trueLong = L0 + C;
    // Aberration + nutation approx
    final omega = 125.04 - 1934.136 * T;
    final lambda = trueLong -
        0.00569 -
        0.00478 * math.sin(omega * math.pi / 180);
    return _norm360(lambda);
  }

  static double _norm360(double x) {
    var v = x % 360;
    if (v < 0) v += 360;
    return v;
  }

  static DateTime _jdToBeijing(double jd) {
    // JD to UTC then +8h for Beijing civil approx (子午原点东经120)
    final z = (jd + 0.5).floor();
    final f = jd + 0.5 - z;
    var A = z;
    if (z >= 2299161) {
      final alpha = ((z - 1867216.25) / 36524.25).floor();
      A = z + 1 + alpha - alpha ~/ 4;
    }
    final B = A + 1524;
    final C = ((B - 122.1) / 365.25).floor();
    final D = (365.25 * C).floor();
    final E = ((B - D) / 30.6001).floor();
    final day = B - D - (30.6001 * E).floor();
    final month = E < 14 ? E - 1 : E - 13;
    final year = month > 2 ? C - 4716 : C - 4715;
    final hours = f * 24;
    final h = hours.floor();
    final min = ((hours - h) * 60).floor();
    final sec = ((((hours - h) * 60) - min) * 60).round();
    final utc = DateTime.utc(year, month, day, h, min, sec);
    return utc.add(const Duration(hours: 8));
  }

  /// All 24 terms for a year in Beijing time
  static List<SolarTerm> yearTerms(int year) {
    return List.generate(24, (i) {
      // Terms spanning year: 小寒/大寒 of year, then 立春...冬至
      // For month finding we need previous 小寒 of this year and next year's 小寒
      return SolarTerm(names[i], termBeijing(year, i));
    });
  }

  /// Find month branch (节气区间) for a Beijing datetime
  static String monthBranch(DateTime beijing) {
    // Build sequence of 节 from previous year's 大雪/小寒 through next 立春
    final y = beijing.year;
    final candidates = <MapEntry<DateTime, String>>[];

    for (final yr in [y - 1, y, y + 1]) {
      for (final entry in jieMonthBranch.entries) {
        final idx = names.indexOf(entry.key);
        final t = termBeijing(yr, idx);
        candidates.add(MapEntry(t, entry.value));
      }
    }
    candidates.sort((a, b) => a.key.compareTo(b.key));

    String branch = '丑';
    for (final c in candidates) {
      if (!beijing.isBefore(c.key)) {
        branch = c.value;
      } else {
        break;
      }
    }
    return branch;
  }

  /// Whether lichun of [year] has passed at beijing time
  static bool afterLiChun(DateTime beijing, int year) {
    final lichun = termBeijing(year, 2); // 立春
    return !beijing.isBefore(lichun);
  }

  /// Li Chun datetime
  static DateTime liChun(int year) => termBeijing(year, 2);
}
