from datetime import date, datetime

# Chinese lunar calendar data, 1900-2100.
# This compact table is commonly used for lunar date conversion.
LUNAR_INFO = [
    0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
    0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
    0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
    0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
    0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
    0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,
    0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
    0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6,
    0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
    0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,
    0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
    0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
    0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
    0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
    0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,
    0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,
    0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,
    0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,
    0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,
    0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,
    0x0d520,
]

CH_MONTHS = ["正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "冬月", "腊月"]
CH_DAYS_PREFIX = ["初", "十", "廿", "三"]
CH_DAYS_NUM = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十"]
STEMS = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"]
BRANCHES = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"]
ZODIAC = ["鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"]


def _year_days(year):
    total = 348
    info = LUNAR_INFO[year - 1900]
    mask = 0x8000
    while mask > 0x8:
        if info & mask:
            total += 1
        mask >>= 1
    return total + _leap_days(year)


def _leap_month(year):
    return LUNAR_INFO[year - 1900] & 0xF


def _leap_days(year):
    if _leap_month(year):
        return 30 if (LUNAR_INFO[year - 1900] & 0x10000) else 29
    return 0


def _month_days(year, month):
    return 30 if (LUNAR_INFO[year - 1900] & (0x10000 >> month)) else 29


def _chinese_day(day):
    if day == 10:
        return "初十"
    if day == 20:
        return "二十"
    if day == 30:
        return "三十"
    return CH_DAYS_PREFIX[(day - 1) // 10] + CH_DAYS_NUM[(day - 1) % 10]


def _cyclical_year(year):
    return STEMS[(year - 4) % 10] + BRANCHES[(year - 4) % 12]


def solar_to_lunar(value):
    """Return lunar info for date/datetime/ISO string. Supported range: 1900-01-31 to 2100-12-31."""
    if isinstance(value, str):
        solar = datetime.strptime(value, "%Y-%m-%d").date()
    elif isinstance(value, datetime):
        solar = value.date()
    elif isinstance(value, date):
        solar = value
    else:
        raise TypeError("value must be date, datetime, or YYYY-MM-DD string")

    base = date(1900, 1, 31)
    if solar < base or solar.year > 2100:
        return None

    offset = (solar - base).days
    lunar_year = 1900

    while lunar_year <= 2100:
        days = _year_days(lunar_year)
        if offset < days:
            break
        offset -= days
        lunar_year += 1

    leap_month = _leap_month(lunar_year)
    is_leap = False
    lunar_month = 1

    while lunar_month <= 12:
        if leap_month and lunar_month == leap_month + 1 and not is_leap:
            lunar_month -= 1
            is_leap = True
            days = _leap_days(lunar_year)
        else:
            days = _month_days(lunar_year, lunar_month)

        if offset < days:
            break

        offset -= days
        if is_leap and lunar_month == leap_month:
            is_leap = False
        lunar_month += 1

    lunar_day = offset + 1
    month_name = ("闰" if is_leap else "") + CH_MONTHS[lunar_month - 1]
    day_name = _chinese_day(lunar_day)

    return {
        "year": lunar_year,
        "month": lunar_month,
        "day": lunar_day,
        "is_leap": is_leap,
        "month_name": month_name,
        "day_name": day_name,
        "cyclical_year": _cyclical_year(lunar_year),
        "zodiac": ZODIAC[(lunar_year - 4) % 12],
        "cn": f"{month_name}{day_name}",
    }


def lunar_cell_text(value, lang_code="zh_CN"):
    info = solar_to_lunar(value)
    if not info:
        return ""
    if lang_code == "zh_CN":
        return info["month_name"] if info["day"] == 1 else info["day_name"]
    prefix = "L"
    month = f"M{info['month']}"
    day = f"D{info['day']}"
    if info["is_leap"]:
        month = f"Leap {month}" if lang_code == "en_US" else f"Kabisat {month}"
    return month if info["day"] == 1 else day


def lunar_full_text(value, lang_code="zh_CN"):
    info = solar_to_lunar(value)
    if not info:
        return ""
    if lang_code == "zh_CN":
        return f"农历 {info['cyclical_year']}年（{info['zodiac']}）{info['month_name']}{info['day_name']}"
    if lang_code == "id_ID":
        leap = " Kabisat" if info["is_leap"] else ""
        return f"Lunar {info['year']} · Bulan {info['month']}{leap} Hari {info['day']}"
    leap = " Leap" if info["is_leap"] else ""
    return f"Lunar {info['year']} · Month {info['month']}{leap} Day {info['day']}"
