# -*- coding: utf-8 -*-
"""
public_holidays.py — feriados NACIONAIS por pais, calculados (nunca tabelados por ano).

Nasceu como `feriados_br.py` em 15/08/2026 e virou multi-pais no mesmo dia, por decisao do
Eduardo: *"The holidays should be configured from each country, the EN package we could use
USA holidays by default. (...) use Brazil is not a good idea for EN version."* — o pacote em
ingles estava escrevendo feriado brasileiro no calendario de um aluno que pode estar em
qualquer lugar.

    from public_holidays import feriados_no_periodo, pais_padrao
    for d, nome, tipo in feriados_no_periodo(ini, fim, pais="US"):
        ...   # datetime.date, nome JA' no idioma do curso, "Fixo"|"Movel"

NAO ha' tabela por ano. Uma lista "2026, 2027, 2028" envelhece calada e um dia entrega ao
aluno um calendario errado sem avisar ninguem. A unica entrada e' o ano; o resto e' regra:
data fixa, deslocamento da Pascoa, ou a n-esima semana do mes.

QUEM ESCOLHE O PAIS: o aluno, pela chave `COUNTRY` do .env. Sem ela, vale o padrao do
IDIOMA do pacote — PT -> BR, EN -> US. Um pais desconhecido nao quebra nada: devolve lista
vazia e o documento fica com a linha "► VOCE DECIDE", que e' a verdade.

ACRESCENTAR UM PAIS e' escrever uma entrada em PAISES. Nada mais no codigo precisa mudar.
"""
import datetime

FIXO, PASCOA, NESIMA = "fixo", "pascoa", "nesima"


def _f(mes, dia, pt, en):
    return (FIXO, (mes, dia), pt, en)


def _p(offset, pt, en):
    return (PASCOA, offset, pt, en)


def _n(mes, semana, dia_semana, pt, en):
    """semana 1..5 = a n-esima do mes; semana -1 = a ULTIMA. dia_semana: 0=segunda."""
    return (NESIMA, (mes, semana, dia_semana), pt, en)


PAISES = {
    # ── BRASIL ──────────────────────────────────────────────────────────────────
    # A Lei 14.759/2023 tornou o 20 de novembro (Consciencia Negra) feriado NACIONAL.
    # Antes dela era estadual/municipal — por isso a data falta em tabelas antigas.
    "BR": {
        "nome_pt": "Brasil", "nome_en": "Brazil",
        "feriados": [
            _f(1, 1, "Confraternização Universal", "New Year's Day"),
            _p(-48, "Carnaval (segunda-feira)", "Carnival (Monday)"),
            _p(-47, "Carnaval (terça-feira)", "Carnival (Tuesday)"),
            _p(-46, "Quarta-feira de Cinzas (até as 14h)", "Ash Wednesday (until 2pm)"),
            _p(-2, "Sexta-feira Santa (Paixão de Cristo)", "Good Friday"),
            _f(4, 21, "Tiradentes", "Tiradentes Day"),
            _f(5, 1, "Dia do Trabalho", "Labour Day"),
            _p(60, "Corpus Christi", "Corpus Christi"),
            _f(9, 7, "Independência do Brasil", "Independence Day (Brazil)"),
            _f(10, 12, "Nossa Senhora Aparecida", "Our Lady of Aparecida"),
            _f(11, 2, "Finados", "All Souls' Day"),
            _f(11, 15, "Proclamação da República", "Republic Day"),
            _f(11, 20, "Dia Nacional de Zumbi e da Consciência Negra",
               "Black Awareness Day"),
            _f(12, 25, "Natal", "Christmas Day"),
        ],
    },
    # ── ESTADOS UNIDOS ──────────────────────────────────────────────────────────
    # Os 11 feriados FEDERAIS (5 U.S.C. 6103). Juneteenth entrou em 2021 — por isso ele
    # falta em tabelas antigas, do mesmo jeito que o 20/11 falta nas brasileiras.
    # Sao os federais, nao os estaduais: um feriado do Texas nao vale no Maine, exatamente
    # como o feriado de Sao Paulo nao vale no Recife.
    "US": {
        "nome_pt": "Estados Unidos", "nome_en": "United States",
        "feriados": [
            _f(1, 1, "Ano-Novo", "New Year's Day"),
            _n(1, 3, 0, "Dia de Martin Luther King Jr.", "Martin Luther King Jr. Day"),
            _n(2, 3, 0, "Dia dos Presidentes", "Washington's Birthday"),
            _n(5, -1, 0, "Memorial Day", "Memorial Day"),
            _f(6, 19, "Juneteenth", "Juneteenth National Independence Day"),
            _f(7, 4, "Dia da Independência", "Independence Day"),
            _n(9, 1, 0, "Dia do Trabalho", "Labor Day"),
            _n(10, 2, 0, "Dia de Colombo", "Columbus Day"),
            _f(11, 11, "Dia dos Veteranos", "Veterans Day"),
            _n(11, 4, 3, "Ação de Graças", "Thanksgiving Day"),
            _f(12, 25, "Natal", "Christmas Day"),
        ],
    },
}

# Sem COUNTRY no .env, o IDIOMA do pacote decide. Nao e' chute: o pacote PT e' vendido no
# Brasil e o EN nasceu para o publico de fora. Continua sendo so' um PADRAO — o aluno troca.
PADRAO_POR_IDIOMA = {"PT": "BR", "EN": "US"}


def pascoa(ano):
    """Domingo de Pascoa (algoritmo de Meeus/Jones/Butcher, calendario gregoriano)."""
    a = ano % 19
    b, c = divmod(ano, 100)
    d, e = divmod(b, 4)
    f = (b + 8) // 25
    g = (b - f + 1) // 3
    h = (19 * a + b - d - g + 15) % 30
    i, k = divmod(c, 4)
    l = (32 + 2 * e + 2 * i - h - k) % 7
    m = (a + 11 * h + 22 * l) // 451
    mes, dia = divmod(h + l - 7 * m + 114, 31)
    return datetime.date(ano, mes, dia + 1)


def _nesimo_dia(ano, mes, semana, dia_semana):
    """A n-esima ocorrencia de um dia da semana no mes (semana -1 = a ultima)."""
    if semana < 0:
        d = datetime.date(ano, mes, 1)
        proximo = datetime.date(ano + (mes == 12), (mes % 12) + 1, 1)
        d = proximo - datetime.timedelta(days=1)
        while d.weekday() != dia_semana:
            d -= datetime.timedelta(days=1)
        return d
    d = datetime.date(ano, mes, 1)
    while d.weekday() != dia_semana:
        d += datetime.timedelta(days=1)
    return d + datetime.timedelta(days=7 * (semana - 1))


def paises():
    """Os codigos disponiveis, em ordem — para o passo montar o menu de escolha."""
    return sorted(PAISES)


def nome_do_pais(pais, idioma="PT"):
    p = PAISES.get((pais or "").upper().strip())
    if not p:
        return (pais or "").upper().strip()
    return p["nome_en"] if (idioma or "PT").upper() == "EN" else p["nome_pt"]


def pais_padrao(idioma="PT", do_env=None):
    """O pais a usar: o do .env (COUNTRY) se houver, senao o padrao do idioma."""
    escolhido = (do_env or "").upper().strip()
    if escolhido in PAISES:
        return escolhido
    return PADRAO_POR_IDIOMA.get((idioma or "PT").upper(), "BR")


def feriados_nacionais(ano, pais="BR", idioma="PT"):
    """[(date, nome, "Fixo"|"Móvel")] do ano, em ordem. Pais desconhecido -> []."""
    spec = PAISES.get((pais or "").upper().strip())
    if not spec:
        return []
    en = (idioma or "PT").upper() == "EN"
    saida = []
    for regra, arg, nome_pt, nome_en in spec["feriados"]:
        nome = nome_en if en else nome_pt
        if regra == FIXO:
            saida.append((datetime.date(ano, arg[0], arg[1]), nome, "Fixo"))
        elif regra == PASCOA:
            saida.append((pascoa(ano) + datetime.timedelta(days=arg), nome, "Móvel"))
        else:
            saida.append((_nesimo_dia(ano, *arg), nome, "Móvel"))
    return sorted(saida, key=lambda x: x[0])


def feriados_no_periodo(inicio, fim, pais="BR", idioma="PT"):
    """Os feriados entre duas datas (inclusive) — atravessa a virada do ano.

    E' esta a funcao que o gerador de calendario usa: um projeto de 18 meses pega dois
    Natais, e quem pedisse `feriados_nacionais(ano_de_inicio)` perderia o segundo.
    """
    if not inicio or not fim or fim < inicio:
        return []
    saida = []
    for ano in range(inicio.year, fim.year + 1):
        saida += [f for f in feriados_nacionais(ano, pais, idioma)
                  if inicio <= f[0] <= fim]
    return sorted(saida, key=lambda x: x[0])


def eh_dia_util(data, feriados=None, dias_semana=(0, 1, 2, 3, 4), pais="BR"):
    """True se a data cai na jornada da semana e nao e' feriado nacional do pais.

    `dias_semana` segue o `weekday()` do Python (0=segunda ... 6=domingo), para quem
    trabalha sabado poder dizer isso sem que nada aqui mude.
    """
    if data.weekday() not in dias_semana:
        return False
    datas = {f[0] for f in (feriados if feriados is not None
                            else feriados_nacionais(data.year, pais))}
    return data not in datas
