<?php

declare(strict_types=1);

/**
 * Serviço de cálculo do Dashboard (Orçado x Realizado).
 * Não faz saída HTML/JSON: apenas retorna um array com os dados calculados.
 */

// ============================
// PESOS POR DIA DA SEMANA (para projeção ponderada)
// ============================

/**
 * Pesos padrão por dia da semana.
 * Índice: 0=Domingo, 1=Segunda, 2=Terça, 3=Quarta, 4=Quinta, 5=Sexta, 6=Sábado
 */
function obterPesosPadraoDiaSemana(): array {
    return [
        0 => 1.3,  // Domingo
        1 => 0.8,  // Segunda
        2 => 0.9,  // Terça
        3 => 1.0,  // Quarta
        4 => 1.1,  // Quinta
        5 => 1.3,  // Sexta
        6 => 1.6,  // Sábado
    ];
}

/**
 * Verifica o parâmetro UTILIZAR_PESO_DIA na tabela back.parametro.
 * @return bool True se valor = 'SIM', False caso contrário.
 */
function obterParametroUsarPesoDia(PDO $pdo): bool {
    try {
        $st = $pdo->prepare("SELECT valor FROM back.parametro WHERE dspadrao = 'UTILIZAR_PESO_DIA' LIMIT 1");
        $st->execute();
        $val = $st->fetchColumn();
        return (strtolower(trim((string)$val)) === 'sim');
    } catch (Throwable $e) {
        return false;
    }
}

/**
 * Calcula a soma dos pesos dos dias dentro de um intervalo de datas.
 * Útil para projeção ponderada: projValor = (realizado / somaPesosPassados) * somaPesosPeriodo
 *
 * @param int $ano  Ano
 * @param int $mes  Mês (1-12)
 * @param int $diaInicio Dia inicial (inclusive)
 * @param int $diaFim    Dia final (inclusive)
 * @return float Soma dos pesos
 */
function somarPesosDias(int $ano, int $mes, int $diaInicio, int $diaFim): float {
    $pesos = obterPesosPadraoDiaSemana();
    $soma = 0.0;
    for ($d = $diaInicio; $d <= $diaFim; $d++) {
        $dow = (int)date('w', mktime(0, 0, 0, $mes, $d, $ano));
        $soma += ($pesos[$dow] ?? 1.0);
    }
    return $soma;
}

/**
 * Calcula a soma dos pesos para todos os dias de um ano inteiro (ou até determinado dia).
 *
 * @param int $ano       Ano
 * @param int $ateDia    Dia do ano (1-366). Se 0, soma o ano inteiro.
 * @return float Soma dos pesos
 */
function somarPesosDiasAno(int $ano, int $ateDia = 0): float {
    $pesos = obterPesosPadraoDiaSemana();
    $diasNoAno = (int)(date('L', strtotime($ano . '-01-01')) ? 366 : 365);
    if ($ateDia <= 0) $ateDia = $diasNoAno;
    $ateDia = min($ateDia, $diasNoAno);

    $soma = 0.0;
    $dt = new DateTime(sprintf('%04d-01-01', $ano));
    for ($i = 1; $i <= $ateDia; $i++) {
        $dow = (int)$dt->format('w');
        $soma += ($pesos[$dow] ?? 1.0);
        $dt->modify('+1 day');
    }
    return $soma;
}

/**
 * Calcula a soma dos pesos para um range de meses (ex: trimestre).
 * Se $mesAtual e $diaAtual fornecidos, soma até esse dia no mês corrente.
 *
 * @param int $ano           Ano
 * @param int $mesInicio     Mês inicial (1-12)
 * @param int $mesFim        Mês final (1-12)
 * @param int $mesAtual      Mês corrente (para parar no dia corrente). 0 = somar tudo.
 * @param int $diaAtual      Dia corrente dentro do $mesAtual. 0 = somar mês inteiro.
 * @return float Soma dos pesos
 */
function somarPesosDiasMeses(int $ano, int $mesInicio, int $mesFim, int $mesAtual = 0, int $diaAtual = 0): float {
    $soma = 0.0;
    for ($m = $mesInicio; $m <= $mesFim; $m++) {
        $ultimoDia = (int)date('t', mktime(0, 0, 0, $m, 1, $ano));
        if ($mesAtual > 0 && $m > $mesAtual) break;
        $dFim = $ultimoDia;
        if ($mesAtual > 0 && $m === $mesAtual && $diaAtual > 0) {
            $dFim = min($diaAtual, $ultimoDia);
        }
        $soma += somarPesosDias($ano, $m, 1, $dFim);
    }
    return $soma;
}

/**
 * Converte array de inteiros para literal de array Postgres (ex: {1,2,3})
 */
function formatPgIntArray(array $values): string {
    if (empty($values)) {
        return '{}';
    }
    $ints = array_map(static function ($v) {
        return (int)$v;
    }, $values);
    return '{' . implode(',', $ints) . '}';
}

function calcularFormulaDashboardInterno(string $formula, array $valores): float {
    // Cache para não recompilar a fórmula toda vez
    static $rpnCache = [];
    static $prec      = ['+' => 1, '-' => 1, '*' => 2, '/' => 2];

    $formula = trim($formula);
    if ($formula === '') return 0.0;

    // Usa RPN em cache se já compilado
    if (!isset($rpnCache[$formula])) {
        preg_match_all('/\d+|[\+\-\*\/\(\)]/', $formula, $m);
        $tokens = $m[0] ?? [];
        if (empty($tokens)) return 0.0;

        $output = [];
        $ops    = [];
        foreach ($tokens as $t) {
            if (ctype_digit($t)) {
                $output[] = $t;
            } elseif (isset($prec[$t])) {
                while (!empty($ops)) {
                    $top = end($ops);
                    if ($top === '(') break;
                    if ($prec[$top] >= $prec[$t]) {
                        $output[] = array_pop($ops);
                    } else {
                        break;
                    }
                }
                $ops[] = $t;
            } elseif ($t === '(') {
                $ops[] = $t;
            } elseif ($t === ')') {
                while (!empty($ops) && end($ops) !== '(') {
                    $output[] = array_pop($ops);
                }
                array_pop($ops); // remove "("
            }
        }
        while (!empty($ops)) {
            $output[] = array_pop($ops);
        }

        $rpnCache[$formula] = $output;
    }

    // Avalia RPN com os valores informados
    $stack = [];
    foreach ($rpnCache[$formula] as $t) {
        if (ctype_digit($t)) {
            $cid     = (int)$t;
            $stack[] = $valores[$cid] ?? 0.0;
        } elseif (isset($prec[$t])) {
            $b = array_pop($stack) ?? 0.0;
            $a = array_pop($stack) ?? 0.0;
            switch ($t) {
                case '+': $stack[] = $a + $b; break;
                case '-': $stack[] = $a - $b; break;
                case '*': $stack[] = $a * $b; break;
                case '/': $stack[] = ($b == 0.0 ? 0.0 : $a / $b); break;
            }
        }
    }

    return (float)($stack[0] ?? 0.0);
}

/**
 * OTIMIZADO: Busca dados iniciais em uma única consulta (exercício + moeda)
 */
function obterDadosIniciais(PDO $pdo, int $cdempresa): ?array {
    static $cache = [];
    
    if (isset($cache[$cdempresa])) {
        return $cache[$cdempresa];
    }

    $sql = "
        SELECT 
            e.dsempresa,
            (SELECT anoref FROM back.exercicio 
             WHERE cdempresa = :emp AND flativo = 0 
             ORDER BY anoref DESC LIMIT 1) AS anoref,
            m.simbolo,
            m.formato
        FROM back.empresa e
        LEFT JOIN back.moeda m ON m.cdmoeda = e.cdmoeda
        WHERE e.cdempresa = :emp
    ";
    
    $stmt = $pdo->prepare($sql);
    $stmt->execute([':emp' => $cdempresa]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    
    if (!$row) {
        return null;
    }
    
    $cache[$cdempresa] = [
        'dsempresa' => $row['dsempresa'] ?? '',
        'anoref' => (int)($row['anoref'] ?: date('Y')),
        'moeda'  => [
            'simbolo' => $row['simbolo'] ?? 'R$',
            'formato' => $row['formato'] ?? 'decimal'
        ]
    ];
    
    return $cache[$cdempresa];
}

/**
 * OTIMIZADO: Busca variáveis e seções com cache estático, filtradas por empresa
 * Só exibe variáveis que estejam explicitamente vinculadas à empresa em variavel_empresa.
 */
function obterVariaveisDashboard(PDO $pdo, int $cdempresa = 0): array {
    static $cache = [];
    
    $cacheKey = $cdempresa;
    if (isset($cache[$cacheKey])) {
        return $cache[$cacheKey];
    }
    
    $sql = "
        SELECT v.cdvariavel, v.nome, v.tipo, v.ordem AS ordem_variavel, v.modelo, v.grafico,
               v.fl_origem, v.cdintegracao, v.fl_agregar_calc,
               s.cdsecao, s.titulo, s.mascara, s.formula, s.ordem
          FROM back.variavel v
          JOIN back.variavel_secao s ON s.cdvariavel = v.cdvariavel
         WHERE v.tipo   = 'Dashboard'
           AND v.flativo = 0
           AND s.flativo = 0
    ";

    // Só mostra variáveis explicitamente vinculadas à empresa
    if ($cdempresa > 0) {
        $sql .= "
           AND EXISTS (SELECT 1 FROM back.variavel_empresa ve WHERE ve.cdvariavel = v.cdvariavel AND ve.cdempresa = :emp)
        ";
    }

    $sql .= " ORDER BY v.ordem NULLS LAST, v.cdvariavel, s.ordem";

    if ($cdempresa > 0) {
        $stmt = $pdo->prepare($sql);
        $stmt->execute([':emp' => $cdempresa]);
        $cache[$cacheKey] = $stmt->fetchAll(PDO::FETCH_ASSOC);
    } else {
        $cache[$cacheKey] = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
    }

    return $cache[$cacheKey];
}

/**
 * Obtém anos únicos dos exercícios ativos (consolidando múltiplos exercícios por ano)
 */
function obterExercicios(PDO $pdo): array {
    static $cache = null;
    
    if ($cache !== null) {
        return $cache;
    }
    
    $sql = "
        SELECT DISTINCT anoref
          FROM back.exercicio
         WHERE flativo = 0
      ORDER BY anoref DESC
    ";
    
    try {
        $resultado = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
        $cache = array_map(fn($row) => [
            'anoref' => (int)$row['anoref'],
            'ano' => (int)$row['anoref']
        ], $resultado);
    } catch (Throwable $e) {
        $cache = [];
    }
    
    return $cache;
}

/**
 * Obtém empresas que o usuário tem acesso (via back.pessoaempresa)
 */
function obterEmpresasPessoa(PDO $pdo, int $cdpessoa): array {
    $sql = "
                SELECT e.cdempresa, e.dsapelido, e.cdmoeda, e.cdpais
          FROM back.empresa e
          JOIN back.pessoaempresa pe 
            ON pe.cdempresa = e.cdempresa 
           AND pe.cdpessoa = ?
      ORDER BY e.dsapelido
    ";
    
    try {
        $params = [$cdpessoa];
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    } catch (Throwable $e) {
        error_log("Erro em obterEmpresasPessoa: " . $e->getMessage());
        return [];
    }
}

/**
 * Obtém centros de custo (apenas pai, flativo = 0)
 */
function obterCentrosCusto(PDO $pdo): array {
    $sql = "
        SELECT cdcentrocusto, dscentro
          FROM back.centro_custo
         WHERE cdcentropai IS not NULL
           AND flativo = 0
      ORDER BY dscentro
    ";
    
    try {
        $stmt = $pdo->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    } catch (Throwable $e) {
        error_log("Erro em obterCentrosCusto: " . $e->getMessage());
        return [];
    }
}

/**
 * Obtém trimestre atual com rótulo
 */
function obterTrimestres(): array {
    $mesAtual = (int)date('n');
    $trimestreAtual = (int)ceil($mesAtual / 3);
    
    return [
        1 => ['numero' => 1, 'descricao' => 'Q1 (Jan-Mar)', 'ativo' => ($trimestreAtual === 1)],
        2 => ['numero' => 2, 'descricao' => 'Q2 (Abr-Jun)', 'ativo' => ($trimestreAtual === 2)],
        3 => ['numero' => 3, 'descricao' => 'Q3 (Jul-Set)', 'ativo' => ($trimestreAtual === 3)],
        4 => ['numero' => 4, 'descricao' => 'Q4 (Out-Dez)', 'ativo' => ($trimestreAtual === 4)],
    ];
}

/**
 * Obtém países acessíveis ao usuário (baseado nas empresas que tem acesso)
 */
function obterPaisesAcessiveis(PDO $pdo, int $cdpessoa): array {
    $sql = "
        SELECT DISTINCT p.cdpais, p.dspais
          FROM back.pais p
          JOIN back.empresa e ON e.cdpais = p.cdpais
          JOIN back.pessoaempresa pe ON pe.cdempresa = e.cdempresa 
         AND pe.cdpessoa = ?
      ORDER BY p.dspais
    ";
    
    try {
        $params = [$cdpessoa];
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    } catch (Throwable $e) {
        error_log("Erro em obterPaisesAcessiveis: " . $e->getMessage());
        return [];
    }
}

/**
 * Obtém moedas ativas para o filtro (flativo = 0)
 */
function obterMoedasAtivas(PDO $pdo): array {
    $sql = "
        SELECT cdmoeda, simbolo, dsmoeda
          FROM back.moeda
         WHERE flativo = 0
      ORDER BY dsmoeda
    ";

    try {
        $stmt = $pdo->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    } catch (Throwable $e) {
        error_log('Erro em obterMoedasAtivas: ' . $e->getMessage());
        return [];
    }
}

function obterDadosDashboard(PDO $pdo, int $cdempresa = 0, int $cdcentrocusto = 0, int $ano = 0, int $trimestre = 0, array $meses = [], array $empresasSelecionadas = [], int $cdempresaSessao = 0, int $cdpais = 0): array {
    $profiling = isset($_GET['profile']) && (string)$_GET['profile'] === '1';
    $profileSteps = [];
    $t0 = microtime(true);
    $mark = function (string $label) use (&$profileSteps, &$t0) {
        $now = microtime(true);
        $profileSteps[] = [
            'label' => $label,
            'ms' => round(($now - $t0) * 1000, 2),
        ];
        $t0 = $now;
    };

    // Se empresas selecionadas, usar lista; senão usar empresa do parâmetro ou da sessão
    if (empty($empresasSelecionadas)) {
        if ($cdempresa <= 0) {
            $cdempresa = (int)($_SESSION['cdempresa'] ?? 0);
        }
        if ($cdempresa <= 0) {
            return [
                'ok'  => false,
                'msg' => 'Empresa não encontrada na sessão.',
            ];
        }
        $empresasSelecionadas = [$cdempresa];
    } else {
        // Validar empresas selecionadas (deve ser array de inteiros)
        $empresasSelecionadas = array_map('intval', $empresasSelecionadas);
        $empresasSelecionadas = array_filter($empresasSelecionadas, fn($e) => $e > 0);
        if (empty($empresasSelecionadas)) {
            return [
                'ok'  => false,
                'msg' => 'Nenhuma empresa selecionada.',
            ];
        }
    }
    
    // Para EXIBIÇÃO (ao lado de "Dashboard"), usar empresa da sessão
    // Para CÁLCULOS, usar empresas selecionadas
    $cdempresaExibicao = $cdempresaSessao > 0 ? $cdempresaSessao : $empresasSelecionadas[0];

    if ($profiling) { $mark('init'); }

    // Parâmetro: usar peso por dia da semana nas projeções?
    $usarPesoDia = obterParametroUsarPesoDia($pdo);

    // OTIMIZADO: Dados iniciais (exercício + moeda) - 1 query com cache estático
    // Usar empresa da sessão para EXIBIÇÃO (nome, ano)
    $dadosIniciais = obterDadosIniciais($pdo, $cdempresaExibicao);
    if (!$dadosIniciais) {
        return [
            'ok'  => false,
            'msg' => 'Dados da empresa não encontrados.',
        ];
    }

    if ($profiling) { $mark('obterDadosIniciais'); }
    
    $anoref = $ano > 0 ? $ano : $dadosIniciais['anoref'];
    $moeda  = $dadosIniciais['moeda'];

    // OTIMIZADO: Variáveis do dashboard - 1 query com cache estático, filtradas por empresa
    // Usa a empresa da sessão para determinar quais variáveis mostrar
    $cdempresaFiltroVar = $cdempresaSessao > 0 ? $cdempresaSessao : ($cdempresa > 0 ? $cdempresa : $empresasSelecionadas[0]);
    $rows = obterVariaveisDashboard($pdo, $cdempresaFiltroVar);
    if (!$rows) {
        return [
            'ok'  => false,
            'msg' => 'Nenhuma variável de Dashboard ativa configurada.',
        ];
    }

    if ($profiling) { $mark('obterVariaveisDashboard'); }

    $dashboards   = [];
    $contasUsadas = [];

    foreach ($rows as $r) {
        $vid = (int)$r['cdvariavel'];
        if (!isset($dashboards[$vid])) {
            $graficoRaw = $r['grafico'] ?? false;
            $grafico = ($graficoRaw === true
                || $graficoRaw === 1
                || $graficoRaw === '1'
                || $graficoRaw === 't'
                || $graficoRaw === 'true'
                || $graficoRaw === 'on'
                || $graficoRaw === 'yes');

            $flOrigemRaw = $r['fl_origem'] ?? false;
            $flOrigem = ($flOrigemRaw === true || $flOrigemRaw === 't' || $flOrigemRaw === 'true' || $flOrigemRaw === '1' || $flOrigemRaw === 1);
            $flAgregarRaw = $r['fl_agregar_calc'] ?? false;
            $flAgregar = ($flAgregarRaw === true || $flAgregarRaw === 't' || $flAgregarRaw === 'true' || $flAgregarRaw === '1' || $flAgregarRaw === 1);

            $dashboards[$vid] = [
                'cdvariavel'      => $vid,
                'nome'            => $r['nome'],
                'tipo'            => $r['tipo'] ?? 'Dashboard',
                'modelo'          => $r['modelo'] ?? 'orcamento',
                'grafico'         => $grafico,
                'fl_origem'       => $flOrigem,
                'cdintegracao'    => !empty($r['cdintegracao']) ? (int)$r['cdintegracao'] : null,
                'fl_agregar_calc' => $flAgregar,
                'secoes'          => [],
            ];
        }
        $dashboards[$vid]['secoes'][] = $r;

        if (!empty($r['formula'])) {
            preg_match_all('/\d+/', $r['formula'], $m);
            foreach ($m[0] as $cd) {
                $contasUsadas[(int)$cd] = true;
            }
        }
    }

    $contasFiltradas = array_keys($contasUsadas);
    if (empty($contasFiltradas)) {
        return [
            'ok'  => false,
            'msg' => 'As fórmulas de Dashboard não possuem contas contábeis vinculadas.',
        ];
    }

    // Mapas de valores por conta (totais)
    $valoresRealizadosAno      = array_fill_keys($contasFiltradas, 0.0);
    $valoresRealizadosMes      = array_fill_keys($contasFiltradas, 0.0);
    $valoresOrcadosAno         = array_fill_keys($contasFiltradas, 0.0);
    $valoresOrcadosMes         = array_fill_keys($contasFiltradas, 0.0);
    $valoresRealizadosAnoAnt   = [];
    $valoresRealizadosMesAnt   = [];

    // Mapas específicos do mês do sistema (para projeção quando multi-mês inclui mês corrente)
    $valoresRealizadosMesSistema = array_fill_keys($contasFiltradas, 0.0);
    $valoresOrcadosMesSistema    = array_fill_keys($contasFiltradas, 0.0);
    
    // Mapas de trimestre
    $valoresRealizadosTrimestre = array_fill_keys($contasFiltradas, 0.0);
    $valoresOrcadosTrimestre    = array_fill_keys($contasFiltradas, 0.0);
    $valoresRealizadosTrimestreAnt = array_fill_keys($contasFiltradas, 0.0);

    // Mapa de orçados por mês (para gráfico de tendência)
    $valoresOrcadosMeses = [];
    for ($m = 1; $m <= 12; $m++) {
        $valoresOrcadosMeses[$m] = array_fill_keys($contasFiltradas, 0.0);
    }

    // Meses selecionados (filtro) e parâmetros de tempo
    // IMPORTANTE: o filtro global de meses NÃO deve afetar os períodos "Trimestre" e "Ano".
    // Por isso, separamos:
    // - $mesesSel: meses selecionados para o período "mes"
    // - $mesLimiteConsulta: mês-limite (YTD) usado para consultas e cálculos de trimestre/ano
    $mesSistema     = (int)date('n');
    $anoSistema     = (int)date('Y');
    $mesLimiteConsulta = $mesSistema;
    $mesesSel = array_values(array_unique(array_map('intval', $meses)));
    $mesesSel = array_values(array_filter($mesesSel, fn($m) => $m >= 1 && $m <= 12));
    sort($mesesSel);
    if (empty($mesesSel)) {
        $mesesSel = [$mesSistema];
    }
    // Mês de referência apenas para exibição/contexto do período "mes" (max selecionado)
    $mesRef = (int)max($mesesSel);
    $anorefAnterior = $anoref - 1;

    // Trimestre de referência: usa filtro se informado; senão deriva do mês do sistema (não do filtro de meses)
    $trimestreBase = ($trimestre > 0 && $trimestre <= 4) ? $trimestre : (int)ceil($mesLimiteConsulta / 3);
    $mesesInicioTrimestre = (int)(($trimestreBase - 1) * 3 + 1);
    $mesFinalTrimestre    = (int)($trimestreBase * 3);

    // Mapas mensais para série YTD (ano atual x anterior)
    $valoresRealizadosMeses = [];
    foreach ([$anoref, $anoref - 1] as $anoMap) {
        $valoresRealizadosMeses[$anoMap] = [];
        for ($m = 1; $m <= $mesLimiteConsulta; $m++) {
            $valoresRealizadosMeses[$anoMap][$m] = array_fill_keys($contasFiltradas, 0.0);
        }
    }

    $placeholders = implode(',', array_fill(0, count($contasFiltradas), '?'));

    // ============================
    // Construir cláusula WHERE dinâmica baseada em filtros
    // ============================
    $empresasPlaceholders = implode(',', array_fill(0, count($empresasSelecionadas), '?'));
    $whereRealizado = "WHERE vr.cdempresa IN ($empresasPlaceholders) AND vr.anoref IN (?, ?) AND vr.mesref <= ? AND vr.cdcontacontabil IN ($placeholders)";
    $whereOrcado = "WHERE vo.cdempresa IN ($empresasPlaceholders) AND vo.anoref = ? AND vo.cdcontacontabil IN ($placeholders)";

    // IMPORTANTE:
    // O filtro global de meses (multi) NÃO entra no WHERE da query consolidada,
    // para não conflitar com os períodos "Trimestre" e "Ano" nos cards.
    // Ele será aplicado apenas na agregação do período "mes" abaixo.
    
    // Se há filtro de centro de custo, adicionar à cláusula WHERE
    if ($cdcentrocusto > 0) {
        $whereRealizado .= " AND vr.cdcentrocusto = ?";
        $whereOrcado .= " AND vo.cdcentrocusto = ?";
    }
    
    // Se há filtro de trimestre específico, adicionar à cláusula WHERE
    if ($trimestre > 0 && $trimestre <= 4) {
        $mesTrimestres = [
            1 => [1, 2, 3],
            2 => [4, 5, 6],
            3 => [7, 8, 9],
            4 => [10, 11, 12]
        ];
        $mesTrimestre = $mesTrimestres[$trimestre];
        $mesTrimestrsPlaceholders = implode(',', array_fill(0, count($mesTrimestre), '?'));
        $whereRealizado .= " AND vr.mesref IN ($mesTrimestrsPlaceholders)";
        $whereOrcado .= " AND vo.mesref IN ($mesTrimestrsPlaceholders)";
    }
    
    // Se há filtro de país, adicionar à cláusula WHERE
    if ($cdpais > 0) {
        $whereRealizado .= " AND e.cdpais = ?";
        $whereOrcado .= " AND e.cdpais = ?";
    }

    // ============================
    // OTIMIZADO: Realizado + Orçado em uma única consulta UNION
    // Reduz de 2 consultas para 1
    // ============================
        $sqlConsolidado = "
                SELECT 'REAL' AS tipo, vr.cdcontacontabil, vr.anoref, vr.mesref, 
                             COALESCE(SUM(vr.valor_mes), 0) AS total
                    FROM back.v_realizado_conta_mes vr
                    JOIN back.empresa e ON e.cdempresa = vr.cdempresa
                    " . $whereRealizado . "
            GROUP BY vr.cdcontacontabil, vr.anoref, vr.mesref
      
                UNION ALL
        
                SELECT 'ORC' AS tipo, vo.cdcontacontabil, vo.anoref, vo.mesref,
                             COALESCE(SUM(vo.valor_mes), 0) AS total
                    FROM back.v_orcado_conta_mes vo
                    JOIN back.empresa e ON e.cdempresa = vo.cdempresa
                    " . $whereOrcado . "
            GROUP BY vo.cdcontacontabil, vo.anoref, vo.mesref
        ";

    // Montar parâmetros da query consolidada
    $paramsConsolidado = array_merge($empresasSelecionadas, [$anoref, $anorefAnterior, $mesLimiteConsulta]);
    $paramsConsolidado = array_merge($paramsConsolidado, $contasFiltradas);
    if ($cdcentrocusto > 0) {
        $paramsConsolidado[] = $cdcentrocusto;
    }
    if ($trimestre > 0 && $trimestre <= 4) {
        $mesTrimestres = [1 => [1, 2, 3], 2 => [4, 5, 6], 3 => [7, 8, 9], 4 => [10, 11, 12]];
        $mesTrimestre = $mesTrimestres[$trimestre];
        $paramsConsolidado = array_merge($paramsConsolidado, $mesTrimestre);
    }
    if ($cdpais > 0) {
        $paramsConsolidado[] = $cdpais;
    }
    $paramsConsolidado = array_merge($paramsConsolidado, $empresasSelecionadas);
    $paramsConsolidado = array_merge($paramsConsolidado, [$anoref]);
    $paramsConsolidado = array_merge($paramsConsolidado, $contasFiltradas);
    if ($cdcentrocusto > 0) {
        $paramsConsolidado[] = $cdcentrocusto;
    }
    if ($trimestre > 0 && $trimestre <= 4) {
        $mesTrimestres = [1 => [1, 2, 3], 2 => [4, 5, 6], 3 => [7, 8, 9], 4 => [10, 11, 12]];
        $mesTrimestre = $mesTrimestres[$trimestre];
        $paramsConsolidado = array_merge($paramsConsolidado, $mesTrimestre);
    }
    if ($cdpais > 0) {
        $paramsConsolidado[] = $cdpais;
    }
    
    $stmt = $pdo->prepare($sqlConsolidado);
    $stmt->execute($paramsConsolidado);
    
    // Dia corrente para referência de projeções/comparativos
    $diaCorrenteSistema = (int)date('j'); // dia de hoje (1-31)
    
    while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $tipo   = $r['tipo'];
        $cid    = (int)$r['cdcontacontabil'];
        $anoRow = (int)$r['anoref'];
        $mesRow = (int)$r['mesref'];
        $total  = (float)$r['total'];

        if ($tipo === 'REAL') {
            if (isset($valoresRealizadosMeses[$anoRow][$mesRow][$cid])) {
                $valoresRealizadosMeses[$anoRow][$mesRow][$cid] += $total;
            }

            if ($anoRow === $anoref) {
                // Ano/Trimestre: sempre YTD até o mês do sistema (não depende do filtro de meses)
                if ($mesRow <= $mesLimiteConsulta) {
                    $valoresRealizadosAno[$cid] += $total;
                }
                // Período "mês": soma apenas os meses selecionados globalmente
                if (in_array($mesRow, $mesesSel, true)) {
                    $valoresRealizadosMes[$cid] += $total;
                }

                // Mapas do mês do sistema (independente do filtro de meses)
                if ($mesRow === $mesSistema) {
                    $valoresRealizadosMesSistema[$cid] += $total;
                }
                // Acumula trimestre (baseado no trimestre selecionado/derivado)
                if ($mesRow >= $mesesInicioTrimestre && $mesRow <= $mesFinalTrimestre && $mesRow <= $mesLimiteConsulta) {
                    $valoresRealizadosTrimestre[$cid] += $total;
                }
            }
        } elseif ($tipo === 'ORC') {
            $valoresOrcadosAno[$cid] += $total;
            // Período "mês": soma apenas os meses selecionados globalmente
            if (in_array($mesRow, $mesesSel, true)) {
                $valoresOrcadosMes[$cid] += $total;
            }

            // Mapas do mês do sistema (independente do filtro de meses)
            if ($mesRow === $mesSistema) {
                $valoresOrcadosMesSistema[$cid] += $total;
            }
            // Acumula trimestre
            if ($mesRow >= $mesesInicioTrimestre && $mesRow <= $mesFinalTrimestre) {
                $valoresOrcadosTrimestre[$cid] += $total;
            }
            // Acumula por mês (para tendência)
            if ($mesRow >= 1 && $mesRow <= 12 && isset($valoresOrcadosMeses[$mesRow][$cid])) {
                $valoresOrcadosMeses[$mesRow][$cid] += $total;
            }
        }
    }

    if ($profiling) { $mark('query_consolidada_real_orc'); }
    // ============================
    // OTIMIZADO: Realizado Anterior - única consulta consolidada
    // Busca valor acumulado do dia similar do ano passado
    // ============================
    
    // Obtém realizado do período anterior em uma única consulta (mês e ano)
    $isAnoAtual = ($anoref === $anoSistema);
    $diaRefYtd = $isAnoAtual
        ? $diaCorrenteSistema
        : (int)date('t', mktime(0, 0, 0, $mesLimiteConsulta, 1, $anorefAnterior));
    $diaRefYtd = min($diaRefYtd, (int)date('t', mktime(0, 0, 0, $mesLimiteConsulta, 1, $anorefAnterior)));

    // Ano anterior (YTD) é sempre calculado com base no mês-limite (sistema)
    [, $valoresRealizadosAnoAnt] = obterRealizadoAnteriorMesEAnoConsolidado(
        $pdo,
        $empresasSelecionadas,
        $anoref,
        $mesLimiteConsulta,
        $diaRefYtd,
        $contasFiltradas
    );

    // Mês anterior (multi-mês): soma os meses selecionados do ano anterior.
    // Para o mês do sistema (quando selecionado e no ano corrente), usa o mesmo dia do mês;
    // para meses já encerrados ou anos anteriores, usa o último dia do mês.
    $valoresRealizadosMesAnt = array_fill_keys($contasFiltradas, 0.0);
    foreach ($mesesSel as $mSel) {
        $mSel = (int)$mSel;
        if ($mSel < 1 || $mSel > 12) continue;

        $diaRefMes = ($isAnoAtual && $mSel === $mesSistema)
            ? $diaCorrenteSistema
            : (int)date('t', mktime(0, 0, 0, $mSel, 1, $anorefAnterior));

        [$mapMesAnt, ] = obterRealizadoAnteriorMesEAnoConsolidado(
            $pdo,
            $empresasSelecionadas,
            $anoref,
            $mSel,
            $diaRefMes,
            $contasFiltradas
        );

        foreach ($mapMesAnt as $cid => $valor) {
            if (!isset($valoresRealizadosMesAnt[$cid])) continue;
            $valoresRealizadosMesAnt[$cid] += (float)$valor;
        }
    }
    
    // Obtém realizado anterior trimestral (acumulado do trimestre anterior até o dia corrente)
    // Trimestre anterior: só faz sentido quando o mês-limite (sistema) está dentro do trimestre analisado
    if ($mesLimiteConsulta < $mesesInicioTrimestre || $mesLimiteConsulta > $mesFinalTrimestre) {
        $valoresRealizadosTrimestreAnt = array_fill_keys($contasFiltradas, 0.0);
    } else {
        $mesAtualTrimRef = $mesLimiteConsulta;
        $diaRefTrimestre = $isAnoAtual
            ? $diaCorrenteSistema
            : (int)date('t', mktime(0, 0, 0, $mesAtualTrimRef, 1, $anorefAnterior));
        $diaRefTrimestre = min($diaRefTrimestre, (int)date('t', mktime(0, 0, 0, $mesAtualTrimRef, 1, $anorefAnterior)));

        $valoresRealizadosTrimestreAnt = obterRealizadoAnteriorTrimestreConsolidado(
            $pdo,
            $empresasSelecionadas,
            $anoref,
            $mesesInicioTrimestre,
            $mesFinalTrimestre,
            $mesAtualTrimRef,
            $diaRefTrimestre,
            $contasFiltradas,
            $cdcentrocusto
        );
    }

    if ($profiling) { $mark('realizado_anterior'); }

    // Datas para projeções
    // Projeções de ano/trimestre usam referência do mês do sistema (não do filtro de meses)
    $diasNoMesSistema = (int)date('t', mktime(0, 0, 0, $mesSistema, 1, $anoref));
    $diaAtualMesSistema = $isAnoAtual ? $diaCorrenteSistema : $diasNoMesSistema;

    $diasNoAno = (int)(date('L', strtotime($anoref . '-01-01')) ? 366 : 365);
    if ($isAnoAtual) {
        $dtDiaAno = new DateTimeImmutable(sprintf('%04d-%02d-%02d', $anoref, $mesSistema, $diaAtualMesSistema));
        $diaAno = ((int)$dtDiaAno->format('z')) + 1;
    } else {
        $diaAno = $diasNoAno;
    }

    // Montar estrutura final por variável/seção
    $saidaDash = [];
    foreach ($dashboards as $vid => $dash) {
        $outSecoes = [];
        $modeloDash = strtolower(trim((string)($dash['modelo'] ?? 'orcamento')));
        $isOrcamento = ($modeloDash === 'orcamento');
        foreach ($dash['secoes'] as $sec) {
            $formula = $sec['formula'];

            $orcAno     = calcularFormulaDashboardInterno($formula, $valoresOrcadosAno);
            $realAno    = calcularFormulaDashboardInterno($formula, $valoresRealizadosAno);
            $realAnoAnt = calcularFormulaDashboardInterno($formula, $valoresRealizadosAnoAnt);
            $orcMes     = calcularFormulaDashboardInterno($formula, $valoresOrcadosMes);
            $realMes    = calcularFormulaDashboardInterno($formula, $valoresRealizadosMes);
            // $valoresRealizadosMesAnt será calculado mais abaixo (multi-mês)
            $realMesAnt = calcularFormulaDashboardInterno($formula, $valoresRealizadosMesAnt);
            $orcTrim    = calcularFormulaDashboardInterno($formula, $valoresOrcadosTrimestre);
            $realTrim   = calcularFormulaDashboardInterno($formula, $valoresRealizadosTrimestre);
            $realTrimAnt = calcularFormulaDashboardInterno($formula, $valoresRealizadosTrimestreAnt);

            // Modelo ORÇAMENTO: o realizado pode vir negativo (débito). Normaliza para positivo.
            // A inversão de cores (consumo alto = crítico) é tratada na camada de apresentação.
            if ($isOrcamento) {
                $orcAno = abs($orcAno);
                $realAno = abs($realAno);
                $realAnoAnt = abs($realAnoAnt);
                $orcMes = abs($orcMes);
                $realMes = abs($realMes);
                $realMesAnt = abs($realMesAnt);
                $orcTrim = abs($orcTrim);
                $realTrim = abs($realTrim);
                $realTrimAnt = abs($realTrimAnt);
            }

            $atingAno = ($orcAno != 0.0) ? ($realAno / $orcAno) : 0.0;
            $restAno  = max(0.0, $orcAno - $realAno);
            $atingMes = ($orcMes != 0.0) ? ($realMes / $orcMes) : 0.0;
            $restMes  = max(0.0, $orcMes - $realMes);
            $atingTrim = ($orcTrim != 0.0) ? ($realTrim / $orcTrim) : 0.0;
            $restTrim  = max(0.0, $orcTrim - $realTrim);

            $varAnoVsAnt = ($realAnoAnt != 0.0) ? (($realAno - $realAnoAnt) / abs($realAnoAnt)) : null;
            $varMesVsAnt = ($realMesAnt != 0.0) ? (($realMes - $realMesAnt) / abs($realMesAnt)) : null;
            $varTrimVsAnt = ($realTrimAnt != 0.0) ? (($realTrim - $realTrimAnt) / abs($realTrimAnt)) : null;

            // Projeção mensal:
            // - Se o filtro global inclui o mês do sistema no ano corrente, projeta apenas o mês corrente
            //   e soma com os meses selecionados já encerrados.
            // - Caso contrário, proj = real (não faz sentido projetar meses passados/futuros ou ano histórico).
            // - Se $usarPesoDia = true, usa pesos por dia da semana na projeção.
            $incluiMesSistema = $isAnoAtual && in_array((int)$mesSistema, $mesesSel, true);
            if ($incluiMesSistema) {
                $diasNoMes = (int)date('t', mktime(0, 0, 0, $mesSistema, 1, $anoref));
                $diaAtual  = max(1, $diaCorrenteSistema);
                
                // Usa dias COMPLETOS (exclui o dia corrente que ainda não terminou)
                $diasCompletos = max(1, $diaAtual - 1);

                $realMesAtual = calcularFormulaDashboardInterno($formula, $valoresRealizadosMesSistema);

                if ($usarPesoDia) {
                    // Projeção ponderada: (realizado / somaPesosPassados) * somaPesosMes
                    $somaPesosPassados = somarPesosDias($anoref, $mesSistema, 1, $diasCompletos);
                    $somaPesosMes      = somarPesosDias($anoref, $mesSistema, 1, $diasNoMes);
                    $projMesAtualValor = ($somaPesosPassados > 0) ? ($realMesAtual / $somaPesosPassados) * $somaPesosMes : 0.0;
                } else {
                    // Projeção linear
                    $projMesAtualValor = ($realMesAtual / $diasCompletos) * $diasNoMes;
                }

                // Se há múltiplos meses selecionados (ex: Jan+Fev), soma meses fechados + projeção do corrente
                // Se há apenas o mês corrente, usa direto a projeção (não subtrai o realizado dele mesmo)
                $mesesFechadosSelecionados = array_filter($mesesSel, fn($m) => $m < $mesSistema);
                if (!empty($mesesFechadosSelecionados)) {
                    // Tem meses anteriores: soma realizado dos fechados + projeção do corrente
                    $projMesValor = ($realMes - $realMesAtual) + $projMesAtualValor;
                } else {
                    // Apenas mês corrente selecionado: usa direto a projeção
                    $projMesValor = $projMesAtualValor;
                }
                $projMesPerc  = ($orcMes != 0.0) ? ($projMesValor / $orcMes) : 0.0;
            } else {
                $projMesValor = $realMes;
                $projMesPerc  = ($orcMes != 0.0) ? ($realMes / $orcMes) : 0.0;
            }

            $projecaoAnoHabilitada = $isAnoAtual;
            if ($projecaoAnoHabilitada) {
                if ($usarPesoDia) {
                    // Projeção ponderada: (realizado / somaPesosPassados) * somaPesosAno
                    $somaPesosPassadosAno = somarPesosDiasAno($anoref, $diaAno);
                    $somaPesosAnoTotal    = somarPesosDiasAno($anoref, 0);
                    $projAnoValor = ($somaPesosPassadosAno > 0) ? ($realAno / $somaPesosPassadosAno) * $somaPesosAnoTotal : 0.0;
                } else {
                    // Projeção linear
                    $projAnoValor = ($diaAno > 0) ? ($realAno / $diaAno * $diasNoAno) : 0.0;
                }
                $projAnoPerc  = ($orcAno != 0.0) ? ($projAnoValor / $orcAno) : 0.0;
            } else {
                $projAnoValor = $realAno;
                $projAnoPerc  = ($orcAno != 0.0) ? ($realAno / $orcAno) : 0.0;
            }
            
            // Projeção trimestral: só projeta no trimestre corrente do ano corrente
            $trimestreSistema = (int)ceil($mesSistema / 3);
            $projecaoTrimHabilitada = ($isAnoAtual && $trimestreBase === $trimestreSistema);

            if ($projecaoTrimHabilitada) {
                if ($usarPesoDia) {
                    // Projeção ponderada por trimestre
                    $somaPesosTrimTotal = somarPesosDiasMeses($anoref, $mesesInicioTrimestre, $mesFinalTrimestre);
                    $somaPesosTrimAtual = somarPesosDiasMeses($anoref, $mesesInicioTrimestre, $mesFinalTrimestre, $mesSistema, $diaCorrenteSistema);
                    $projTrimValor = ($somaPesosTrimAtual > 0) ? ($realTrim / $somaPesosTrimAtual) * $somaPesosTrimTotal : 0.0;
                } else {
                    // Projeção linear
                    $diasTrimestreTotal = 0;
                    for ($m = $mesesInicioTrimestre; $m <= $mesFinalTrimestre; $m++) {
                        $diasTrimestreTotal += (int)date('t', mktime(0, 0, 0, $m, 1, $anoref));
                    }

                    $diasTrimestreAtual = 0;
                    for ($m = $mesesInicioTrimestre; $m <= $mesFinalTrimestre && $m <= $mesSistema; $m++) {
                        if ($m < $mesSistema) {
                            $diasTrimestreAtual += (int)date('t', mktime(0, 0, 0, $m, 1, $anoref));
                        } else {
                            $diasTrimestreAtual += $diaCorrenteSistema;
                        }
                    }

                    $projTrimValor = ($diasTrimestreAtual > 0) ? ($realTrim / $diasTrimestreAtual * $diasTrimestreTotal) : 0.0;
                }
                $projTrimPerc  = ($orcTrim != 0.0) ? ($projTrimValor / $orcTrim) : 0.0;
            } else {
                $projTrimValor = $realTrim;
                $projTrimPerc  = ($orcTrim != 0.0) ? ($realTrim / $orcTrim) : 0.0;
            }

            $outSecoes[] = [
                'cdsecao'  => (int)$sec['cdsecao'],
                'titulo'   => $sec['titulo'],
                'mascara'  => $sec['mascara'],
                'formula'  => $formula,
                'ano'      => [
                    'orcado'           => $orcAno,
                    'realizado'        => $realAno,
                    'atingidoPerc'     => $atingAno,
                    'restanteValor'    => $restAno,
                    'restantePerc'     => max(0.0, 1.0 - $atingAno),
                    'realizadoAnterior'=> $realAnoAnt,
                    'varAnteriorPerc'  => $varAnoVsAnt,
                    'projValor'        => $projAnoValor,
                    'projPerc'         => $projAnoPerc,
                ],
                'mes'      => [
                    'orcado'           => $orcMes,
                    'realizado'        => $realMes,
                    'atingidoPerc'     => $atingMes,
                    'restanteValor'    => $restMes,
                    'restantePerc'     => max(0.0, 1.0 - $atingMes),
                    'realizadoAnterior'=> $realMesAnt,
                    'varAnteriorPerc'  => $varMesVsAnt,
                    'projValor'        => $projMesValor,
                    'projPerc'         => $projMesPerc,
                ],
                'trimestre' => [
                    'orcado'           => $orcTrim,
                    'realizado'        => $realTrim,
                    'atingidoPerc'     => $atingTrim,
                    'restanteValor'    => $restTrim,
                    'restantePerc'     => max(0.0, 1.0 - $atingTrim),
                    'realizadoAnterior'=> $realTrimAnt,
                    'varAnteriorPerc'  => $varTrimVsAnt,
                    'projValor'        => $projTrimValor,
                    'projPerc'         => $projTrimPerc,
                ],
            ];
        }

        $saidaDash[] = [
            'cdvariavel'      => $vid,
            'nome'            => $dash['nome'],
            'modelo'          => $dash['modelo'],
            'grafico'         => $dash['grafico'] ?? false,
            'fl_origem'       => $dash['fl_origem'] ?? false,
            'cdintegracao'    => $dash['cdintegracao'] ?? null,
            'fl_agregar_calc' => $dash['fl_agregar_calc'] ?? false,
            'secoes'          => $outSecoes,
        ];
    }

    if ($profiling) { $mark('montagem_variaveis'); }

    // ============================
    // Série YTD (Year-to-Date) para o primeiro dashboard
    // Compara realizado acumulado mês a mês: ano atual x ano anterior
    // Até o mês corrente (corrida ao longo do ano)
    // ============================
    $serieYtd = null;
    if (!empty($dashboards)) {
        $firstId   = array_key_first($dashboards);
        $dashMain  = $dashboards[$firstId];
        $labelsMesBase = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'];

        $ultimoMes = $mesLimiteConsulta > 0 && $mesLimiteConsulta <= 12 ? $mesLimiteConsulta : 12;
        $labelsMes = array_slice($labelsMesBase, 0, $ultimoMes);

        $acumAtual    = [];
        $acumAnterior = [];
        $somaAtual    = 0.0;
        $somaAnterior = 0.0;

        for ($m = 1; $m <= $ultimoMes; $m++) {
            $mapAtual    = $valoresRealizadosMeses[$anoref][$m]       ?? [];
            $mapAnterior = $valoresRealizadosMeses[$anorefAnterior][$m] ?? [];

            $valorMesAtual    = 0.0;
            $valorMesAnterior = 0.0;
            foreach ($dashMain['secoes'] as $secMain) {
                $f = $secMain['formula'];
                if ($f === null || $f === '') {
                    continue;
                }
                $valorMesAtual    += calcularFormulaDashboardInterno($f, $mapAtual);
                $valorMesAnterior += calcularFormulaDashboardInterno($f, $mapAnterior);
            }

            $somaAtual    += $valorMesAtual;
            $somaAnterior += $valorMesAnterior;
            $acumAtual[]    = $somaAtual;
            $acumAnterior[] = $somaAnterior;
        }

        $serieYtd = [
            'nome'         => $dashMain['nome'],
            'labels'       => $labelsMes,
            'anoAtual'     => $acumAtual,
            'anoAnterior'  => $acumAnterior,
        ];
    }

    if ($profiling) { $mark('serie_ytd'); }

    // Se há múltiplas empresas selecionadas, indicar consolidação
    $nomeEmpresaExibicao = $dadosIniciais['dsempresa'] ?? '';
    if (count($empresasSelecionadas) > 1) {
        $nomeEmpresaExibicao = "Consolidação (" . count($empresasSelecionadas) . " empresas)";
    }

    $saida = [
        'ok'       => true,
        'empresa'  => $cdempresaExibicao,
        'dsempresa'=> $nomeEmpresaExibicao,
        'anoref'   => $anoref,
        'mesAtual' => $mesRef,
        'moeda'    => $moeda,
        'dashboards'=> $saidaDash,
        'serieYtd' => $serieYtd,
        'valoresRealizadosMensais' => $valoresRealizadosMeses[$anoref] ?? [],
        'valoresOrcadosMensais' => $valoresOrcadosMeses,
    ];

    if ($profiling) {
        $profileSteps[] = [
            'label' => 'total',
            'ms' => array_sum(array_column($profileSteps, 'ms')),
        ];
        $saida['profile'] = $profileSteps;
    }

    return $saida;
}


/**
 * Calcula projeção simples do mês baseado em dias úteis
 * Considera apenas feriados FUTUROS que vão impactar no projetado
 * Fórmula: realizado + (realizado / dias_passados) * dias_úteis_restantes
 */
/**
 * Obtém o parâmetro FERIADO_DIA_UTIL para saber se feriado é considerado dia útil
 * @param PDO $pdo Conexão com banco de dados
 * @return bool True se feriado é dia útil, False caso contrário
 */
function obterFeriadoDiaUtil(PDO $pdo): bool {
    try {
        $stmt = $pdo->prepare("
            SELECT valor
              FROM back.parametro
             WHERE dspadrao = 'FERIADO_DIA_UTIL'
             LIMIT 1
        ");
        $stmt->execute();
        $resultado = $stmt->fetchColumn();
        return strtolower(trim((string)$resultado)) === 'sim';
    } catch (Throwable $e) {
        // Padrão: feriado NÃO é dia útil
        return false;
    }
}

function calcularProjecaoMes(
    float $realizado,
    float $meta,
    int $diaAtual,
    int $totalDiasMes,
    int $diasRestantes,
    int $feriadosRestantes = 0,
    bool $feriadoDiaUtil = false
): array {
    if ($diaAtual <= 0) {
        return ['proj_valor' => 0, 'proj_perc' => 0.0];
    }
    
    // Taxa diária = realizado até hoje / dias corridos passados
    // Feriados passados já se concretizaram no realizado, não precisam ser descontados
    $diasPassados = max(1, $diaAtual);
    $taxaDiaria = $realizado / $diasPassados;
    
    // Dias úteis restantes = dias restantes - feriados FUTUROS
    // Se feriado NÃO é dia útil: reduz os dias de trabalho (diminui dias úteis)
    // Se feriado É dia útil: não reduz tanto os dias de trabalho
    $diasUteisRestantes = $diasRestantes;
    if (!$feriadoDiaUtil) {
        // Feriado não é dia útil: subtrai dos dias restantes
        $diasUteisRestantes = max(0, $diasRestantes - $feriadosRestantes);
    }
    // Se feriado É dia útil, mantém a contagem total de dias restantes
    
    // Projeção = realizado + (taxa_diária * dias_úteis_restantes)
    $projValor = $realizado + ($taxaDiaria * $diasUteisRestantes);
    $projPerc = $meta > 0 ? ($projValor / $meta) : 0.0;
    
    return [
        'proj_valor' => $projValor,
        'proj_perc' => $projPerc,
    ];
}

/**
 * Calcula projeção simples do ano baseado em dias úteis
 * Considera apenas feriados FUTUROS que vão impactar no projetado
 */
function calcularProjecaoAno(
    float $realizado,
    float $meta,
    int $diaAnoAtual,
    int $diasRestantesAno,
    int $feriadosRestantesAno = 0,
    bool $feriadoDiaUtil = false
): array {
    if ($diaAnoAtual <= 0) {
        return ['proj_valor' => 0, 'proj_perc' => 0.0];
    }
    
    // Taxa diária = realizado até hoje / dias corridos passados do ano
    // Feriados passados já estão no realizado
    $diasPassados = max(1, $diaAnoAtual);
    $taxaDiaria = $realizado / $diasPassados;
    
    // Dias úteis restantes = dias restantes - feriados FUTUROS
    // Se feriado NÃO é dia útil: reduz os dias de trabalho (diminui dias úteis)
    // Se feriado É dia útil: não reduz tanto os dias de trabalho
    $diasUteisRestantes = $diasRestantesAno;
    if (!$feriadoDiaUtil) {
        // Feriado não é dia útil: subtrai dos dias restantes
        $diasUteisRestantes = max(0, $diasRestantesAno - $feriadosRestantesAno);
    }
    // Se feriado É dia útil, mantém a contagem total de dias restantes
    
    // Projeção = realizado + (taxa_diária * dias_úteis_restantes)
    $projValor = $realizado + ($taxaDiaria * $diasUteisRestantes);
    $projPerc = $meta > 0 ? ($projValor / $meta) : 0.0;
    
    return [
        'proj_valor' => $projValor,
        'proj_perc' => $projPerc,
    ];
}

/**
 * Obtém os feriados do mês (sem projeção), filtrando por dia corrente
 * @param PDO $pdo Conexão com banco de dados
 * @param int $mesCorrente Mês corrente (1-12)
 * @param int $anoCorrente Ano corrente (YYYY)
 * @param int $diaCorrente Dia corrente (1-31)
 * @return array Lista de feriados (data/desc/pais/sort)
 */
function obterFeriadosMesBase(PDO $pdo, int $mesCorrente, int $anoCorrente, int $diaCorrente): array {
    $sqlF = "
        SELECT f.cdferiado, f.dsferiado, f.flanual, f.mes, f.dia, f.dtferiado,
               COALESCE(p.abreviacao, p.dspais) AS dspais
          FROM back.calendario_feriado f
          LEFT JOIN back.pais p ON f.cdpais = p.cdpais
         WHERE f.flativo = 0
           AND (
                 (f.flanual = 1 AND f.mes = :mes)
              OR (f.flanual = 0 AND EXTRACT(MONTH FROM f.dtferiado) = :mes AND EXTRACT(YEAR FROM f.dtferiado) = :ano)
           )
         ORDER BY f.dia ASC, f.dtferiado ASC
    ";

    $feriados = [];
    try {
        $stmtF = $pdo->prepare($sqlF);
        $stmtF->execute([':mes' => $mesCorrente, ':ano' => $anoCorrente]);

        while ($r = $stmtF->fetch(PDO::FETCH_ASSOC)) {
            $ds     = (string)($r['dsferiado'] ?? 'Feriado');
            $dspais = (string)($r['dspais'] ?? 'Indefinido');
            $anual  = (int)($r['flanual'] ?? 1);

            if ($anual === 1) {
                $m = (int)($r['mes'] ?? 0);
                $d = (int)($r['dia'] ?? 0);
                if ($m === $mesCorrente && $d >= $diaCorrente) {
                    $dataObj = DateTime::createFromFormat('Y-n-j', $anoCorrente . '-' . $m . '-' . $d);
                    if ($dataObj) {
                        $feriados[] = [
                            'data' => $dataObj->format('d/m'),
                            'desc' => $ds,
                            'pais' => $dspais,
                            'sort' => (int)$dataObj->format('Ymd'),
                            'mes'  => $m,
                            'dia'  => $d,
                        ];
                    }
                }
            } else {
                $dt = $r['dtferiado'] ?? null;
                if ($dt) {
                    try {
                        $tz = new DateTimeZone('America/Sao_Paulo');
                        $dataObj = new DateTime($dt, $tz);

                        $dtMes = (int)$dataObj->format('n');
                        $dtAno = (int)$dataObj->format('Y');
                        $dtDia = (int)$dataObj->format('j');

                        if ($dtMes === $mesCorrente && $dtAno === $anoCorrente && $dtDia >= $diaCorrente) {
                            $feriados[] = [
                                'data' => $dataObj->format('d/m'),
                                'desc' => $ds,
                                'pais' => $dspais,
                                'sort' => (int)$dataObj->format('Ymd'),
                                'mes'  => $dtMes,
                                'dia'  => $dtDia,
                            ];
                        }
                    } catch (Exception $e) {
                        // Ignora feriado com data inválida
                    }
                }
            }
        }
    } catch (Throwable $e) {
        // Erro capturado, retorna vazio
    }

    usort($feriados, function ($a, $b) { return ($a['sort'] <=> $b['sort']); });
    return $feriados;
}

/**
 * Aplica projeção de feriados usando contas filtradas
 * @param PDO $pdo Conexão com banco de dados
 * @param int $cdempresa ID da empresa
 * @param array $feriadosBase Lista de feriados base
 * @param array $contasFiltradas Contas filtradas para projeção
 * @param float $percentualProjecao Percentual de projeção
 * @return array Lista de feriados com projeção
 */
function calcularProjecaoFeriados(PDO $pdo, int $cdempresa, array $feriadosBase, array $contasFiltradas, float $percentualProjecao): array {
    if (empty($feriadosBase)) {
        return [];
    }

    $saida = [];
    $mapPorMes = [];
    
    // Pré-calcula média diária por mês para fallback
    $mediasPorMes = [];
    
    foreach ($feriadosBase as $f) {
        $mes = (int)($f['mes'] ?? 0);
        $dia = (int)($f['dia'] ?? 0);

        if ($mes <= 0 || $dia <= 0) {
            $parts = explode('/', (string)($f['data'] ?? ''));
            if (count($parts) === 2) {
                $dia = (int)$parts[0];
                $mes = (int)$parts[1];
            }
        }

        if ($mes <= 0 || $dia <= 0) {
            continue;
        }

        if (!isset($mapPorMes[$mes])) {
            $mapPorMes[$mes] = obterValoresDiasAnoAnteriorMes($pdo, $cdempresa, $mes, $contasFiltradas);
        }
        
        if (!isset($mediasPorMes[$mes])) {
            // Calcula média diária daquele mês do ano anterior
            $valoresDia = $mapPorMes[$mes];
            if (!empty($valoresDia)) {
                $total = array_sum($valoresDia);
                $diasComDados = count($valoresDia);
                $mediasPorMes[$mes] = $diasComDados > 0 ? $total / $diasComDados : 0;
            } else {
                $mediasPorMes[$mes] = 0;
            }
        }
        
        $valorDiaAnt = $mapPorMes[$mes][$dia] ?? $mediasPorMes[$mes];
        $projecao = $valorDiaAnt * (1 + $percentualProjecao);

        $saida[] = [
            'data' => $f['data'],
            'desc' => $f['desc'],
            'pais' => $f['pais'],
            'projecao' => $projecao,
            'sort' => $f['sort'],
        ];
    }

    return $saida;
}

/**
 * Obtém os feriados restantes do mês corrente para um empresa/país com projeção de venda
 * @param PDO $pdo Conexão com banco de dados
 * @param int $cdempresa ID da empresa
 * @param int $mesCorrente Mês corrente (1-12)
 * @param int $anoCorrente Ano corrente (YYYY)
 * @param int $diaCorrente Dia corrente (1-31)
 * @return array ['qtd' => int, 'lista' => [['data' => 'dd/mm', 'desc' => string, 'pais' => string, 'projecao' => float], ...]]
 */
function obterFeriadosRestantesMes(PDO $pdo, int $cdempresa, int $mesCorrente, int $anoCorrente, int $diaCorrente, ?int $cdvariavel = null): array {
    // Extrai as contas de forma específica: 
    // - Se cdvariavel foi passado, extrai apenas as contas daquela variável
    // - Se não, extrai todas as contas do dashboard (compatibilidade com código anterior)
    $contasUsadas = [];
    
    if ($cdvariavel !== null) {
        // Extrai apenas contas desta variável específica
        $sqlDash = "
            SELECT s.formula
              FROM back.variavel v
              JOIN back.variavel_secao s ON s.cdvariavel = v.cdvariavel
             WHERE v.cdvariavel = :cdvar
                             AND v.tipo = 'Dashboard'
               AND v.flativo = 0
               AND s.flativo = 0
        ";
        $stmtDash = $pdo->prepare($sqlDash);
        $stmtDash->execute([':cdvar' => $cdvariavel]);
    } else {
        // Extrai TODAS as contas do dashboard (comportamento antigo)
        $sqlDash = "
            SELECT s.formula
              FROM back.variavel v
              JOIN back.variavel_secao s ON s.cdvariavel = v.cdvariavel
             WHERE v.tipo = 'Dashboard'
               AND v.flativo = 0
               AND s.flativo = 0
        ";
        $stmtDash = $pdo->query($sqlDash);
    }
    
    while ($rDash = $stmtDash->fetch(PDO::FETCH_ASSOC)) {
        if (!empty($rDash['formula'])) {
            preg_match_all('/\d+/', $rDash['formula'], $m);
            foreach ($m[0] as $cd) {
                $contasUsadas[(int)$cd] = true;
            }
        }
    }
    
    $contasFiltradas = array_keys($contasUsadas);
    if (empty($contasFiltradas)) {
        $contasFiltradas = [1]; // fallback: usa conta 1
    }

    $feriadosBase = obterFeriadosMesBase($pdo, $mesCorrente, $anoCorrente, $diaCorrente);
    $percentualProjecao = obterPercentualProjecaoFeriado($pdo);
    $feriadosRestantes = calcularProjecaoFeriados($pdo, $cdempresa, $feriadosBase, $contasFiltradas, $percentualProjecao);

    return [
        'qtd' => count($feriadosRestantes),
        'lista' => $feriadosRestantes,
    ];
}

/**
 * Busca a última sincronização bem-sucedida para uma empresa
 * @param PDO $pdo Conexão com banco de dados
 * @param int $cdempresa ID da empresa
 * @return string|null Data/hora da última sincronização (formato: 'YYYY-MM-DD HH:MM:SS') ou null
 */
function obterUltimaSincronizacao(PDO $pdo, int $cdempresa): ?string {
    try {
        $stmt = $pdo->prepare("
            SELECT MAX(dtexec_fim) as ultima_sinc 
            FROM back.integracao_log
            WHERE cdempresa = :emp 
            AND status = 'sucesso'
            LIMIT 1
        ");
        $stmt->execute([':emp' => $cdempresa]);
        $resultado = $stmt->fetch(\PDO::FETCH_ASSOC);
        
        if ($resultado && $resultado['ultima_sinc']) {
            return $resultado['ultima_sinc'];
        }
    } catch (\Exception $e) {
        // Se falhar, retorna null (não quebra o código)
    }
    
    return null;
}

/**
 * Busca o percentual de projeção de feriado do parâmetro
 * @param PDO $pdo Conexão com banco de dados
 * @return float Percentual (0.15 para 15%, por ex)
 */
function obterPercentualProjecaoFeriado(PDO $pdo): float {
    try {
        $sql = "SELECT valor FROM back.parametro WHERE dspadrao = 'PROJECAO_FERIADO' LIMIT 1";
        $stmt = $pdo->prepare($sql);
        $stmt->execute();
        $resultado = $stmt->fetch(PDO::FETCH_ASSOC);
        
        if ($resultado && is_numeric($resultado['valor'])) {
            $percentual = (float)$resultado['valor'] / 100;
            return $percentual;
        }
    } catch (Throwable $e) {
        // Erro capturado, retorna 0
    }
    
    return 0.0;
}

/**
 * Busca o valor realizado do mesmo dia do ano anterior
 * @param PDO $pdo Conexão com banco de dados
 * @param int $cdempresa ID da empresa
 * @param int $mes Mês (1-12)
 * @param int $dia Dia (1-31)
 * @param array $contasFiltradas Array com IDs das contas contábeis para filtrar
 * @return float Valor realizado do período anterior para esse dia
 */
function obterValorDiaAnoAnterior(PDO $pdo, int $cdempresa, int $mes, int $dia, array $contasFiltradas = []): float {
    try {
        $anoCorrente = (int)date('Y');
        $anoAnterior = $anoCorrente - 1;
        
        if (empty($contasFiltradas)) {
            $contasFiltradas = [1]; // fallback
        }
        
        $placeholders = implode(',', array_fill(0, count($contasFiltradas), '?'));
        
        // Query para buscar realizado desse dia específico do ano anterior usando a view
        $sql = "
            SELECT COALESCE(SUM(valor_dia), 0.0) AS valor_total
              FROM back.v_realizado_conta_mes_por_dia
             WHERE cdempresa = ?
               AND anoref = ?
               AND mesref = ?
               AND diaref = ?
               AND cdcontacontabil IN ($placeholders)
        ";
        
        $params = array_merge([$cdempresa, $anoAnterior, $mes, $dia], $contasFiltradas);
        
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        $resultado = $stmt->fetch(PDO::FETCH_ASSOC);
        
        if ($resultado && is_numeric($resultado['valor_total'])) {
            $valor = (float)$resultado['valor_total'];
            return $valor;
        }
        return 0.0;
    } catch (Throwable $e) {
        return 0.0;
    }
}

/**
 * Obtém valores realizados do ano anterior por dia para um mês específico
 * @param PDO $pdo Conexão com banco de dados
 * @param int $cdempresa ID da empresa
 * @param int $mes Mês (1-12)
 * @param array $contasFiltradas Array com IDs das contas contábeis
 * @return array Mapa [dia => valor_total]
 */
function obterValoresDiasAnoAnteriorMes(PDO $pdo, int $cdempresa, int $mes, array $contasFiltradas): array {
    if (empty($contasFiltradas) || $mes <= 0) {
        return [];
    }

    $anoCorrente = (int)date('Y');
    $anoAnterior = $anoCorrente - 1;
    $sql = "
        SELECT diaref, COALESCE(SUM(valor_dia), 0.0) AS valor_total
          FROM back.v_realizado_conta_mes_por_dia
         WHERE cdempresa = ?
           AND anoref = ?
           AND mesref = ?
                     AND cdcontacontabil = ANY(?::int[])
      GROUP BY diaref
    ";

    $contasArray = formatPgIntArray($contasFiltradas);
    $params = [$cdempresa, $anoAnterior, $mes, $contasArray];

    try {
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        $map = [];
        while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $dia = (int)$r['diaref'];
            $map[$dia] = (float)$r['valor_total'];
        }
        return $map;
    } catch (Throwable $e) {
        return [];
    }
}
/**
 * Retorna feriados do mês com projeções calculadas ESPECIFICAMENTE para uma variável
 * Usa apenas as contas que aquela variável referencia
 * 
 * @param PDO $pdo Conexão com banco
 * @param int $cdempresa ID da empresa
 * @param int $mesCorrente Mês atual (1-12)
 * @param int $anoCorrente Ano atual
 * @param int $diaCorrente Dia atual
 * @param int $cdvariavel ID da variável
 * @return array Lista de feriados com projeções específicas da variável
 */
function obterFeriadosRestantesPorVariavel(PDO $pdo, int $cdempresa, int $mesCorrente, int $anoCorrente, int $diaCorrente, int $cdvariavel): array {
    // Reutiliza a função principal mas passa o cdvariavel para extrair apenas suas contas
    return obterFeriadosRestantesMes($pdo, $cdempresa, $mesCorrente, $anoCorrente, $diaCorrente, $cdvariavel);
}

/**
 * Obtém valores realizados do mês/ano atual por conta/dia
 * @param PDO $pdo Conexão com banco de dados
 * @param int $cdempresa ID da empresa
 * @param int $mes Mês (1-12)
 * @param int $ano Ano (ex: 2026)
 * @param array $contasFiltradas Array com IDs das contas contábeis
 * @return array Mapa [cdcontacontabil][dia] => valor_total
 */
function obterValoresContasDiasMesAtual(PDO $pdo, int $cdempresa, int $mes, int $ano, array $contasFiltradas): array {
    if (empty($contasFiltradas) || $mes <= 0 || $ano <= 0) {
        return [];
    }

    $sql = "
        SELECT cdcontacontabil, diaref, COALESCE(SUM(valor_dia), 0.0) AS valor_total
          FROM back.v_realizado_conta_mes_por_dia
         WHERE cdempresa = ?
           AND anoref = ?
           AND mesref = ?
           AND cdcontacontabil = ANY(?::int[])
      GROUP BY cdcontacontabil, diaref
      ORDER BY diaref DESC
    ";

    $contasArray = formatPgIntArray($contasFiltradas);
    $params = [$cdempresa, $ano, $mes, $contasArray];

    try {
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        $map = [];
        while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $cid = (int)$r['cdcontacontabil'];
            $dia = (int)$r['diaref'];
            if (!isset($map[$cid])) {
                $map[$cid] = [];
            }
            $map[$cid][$dia] = (float)$r['valor_total'];
        }
        return $map;
    } catch (Throwable $e) {
        return [];
    }
}

/**
 * Obtém valores realizados do ano anterior por conta/dia para um mês específico
 * @param PDO $pdo Conexão com banco de dados
 * @param int $cdempresa ID da empresa
 * @param int $mes Mês (1-12)
 * @param array $contasFiltradas Array com IDs das contas contábeis
 * @return array Mapa [cdcontacontabil][dia] => valor_total
 */
function obterValoresContasDiasAnoAnteriorMes(PDO $pdo, int $cdempresa, int $mes, array $contasFiltradas): array {
    if (empty($contasFiltradas) || $mes <= 0) {
        return [];
    }

    $anoCorrente = (int)date('Y');
    $anoAnterior = $anoCorrente - 1;
    $sql = "
        SELECT cdcontacontabil, diaref, COALESCE(SUM(valor_dia), 0.0) AS valor_total
          FROM back.v_realizado_conta_mes_por_dia
         WHERE cdempresa = ?
           AND anoref = ?
           AND mesref = ?
                     AND cdcontacontabil = ANY(?::int[])
      GROUP BY cdcontacontabil, diaref
    ";

    $contasArray = formatPgIntArray($contasFiltradas);
    $params = [$cdempresa, $anoAnterior, $mes, $contasArray];

    try {
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        $map = [];
        while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $cid = (int)$r['cdcontacontabil'];
            $dia = (int)$r['diaref'];
            if (!isset($map[$cid])) {
                $map[$cid] = [];
            }
            $map[$cid][$dia] = (float)$r['valor_total'];
        }
        return $map;
    } catch (Throwable $e) {
        return [];
    }
}

/**
 * Aplica projeção de feriados usando mapa por conta/dia (evita consultas)
 * @param array $feriadosBase Lista de feriados base
 * @param array $mapContaDia Mapa [cdconta][dia] => valor_total
 * @param array $contasFiltradas Contas filtradas para projeção
 * @param float $percentualProjecao Percentual de projeção
 * @return array Lista de feriados com projeção
 */
function calcularProjecaoFeriadosComMapa(array $feriadosBase, array $mapContaDia, array $contasFiltradas, float $percentualProjecao): array {
    if (empty($feriadosBase) || empty($mapContaDia)) {
        return [];
    }

    $saida = [];
    
    // Calcula média diária por conta para usar como fallback
    $mediasDiariasAno = [];
    foreach ($mapContaDia as $cid => $diasMap) {
        if (!empty($diasMap)) {
            $total = array_sum($diasMap);
            $dias = count($diasMap);
            $mediasDiariasAno[$cid] = $dias > 0 ? $total / $dias : 0;
        }
    }
    
    foreach ($feriadosBase as $f) {
        $mes = (int)($f['mes'] ?? 0);
        $dia = (int)($f['dia'] ?? 0);

        if ($mes <= 0 || $dia <= 0) {
            $parts = explode('/', (string)($f['data'] ?? ''));
            if (count($parts) === 2) {
                $dia = (int)$parts[0];
                $mes = (int)$parts[1];
            }
        }

        if ($mes <= 0 || $dia <= 0) {
            continue;
        }

        $valorDiaAnt = 0.0;
        foreach ($contasFiltradas as $cid) {
            $cid = (int)$cid;
            // Tenta usar valor específico do dia, senão usa média diária do mês anterior
            if (isset($mapContaDia[$cid][$dia])) {
                $valorDiaAnt += (float)$mapContaDia[$cid][$dia];
            } elseif (isset($mediasDiariasAno[$cid])) {
                $valorDiaAnt += (float)$mediasDiariasAno[$cid];
            }
        }

        $projecao = $valorDiaAnt * (1 + $percentualProjecao);

        $saida[] = [
            'data' => $f['data'],
            'desc' => $f['desc'],
            'pais' => $f['pais'],
            'projecao' => $projecao,
            'sort' => $f['sort'],
        ];
    }

    return $saida;
}

/**
 * Obtém realizado do mesmo dia do ano anterior (mês e ano) em uma única consulta
 * @param PDO $pdo Conexão com banco de dados
 * @param int $cdempresa ID da empresa
 * @param int $anoRef Ano de referência atual
 * @param int $mesRef Mês de referência atual (1-12)
 * @param int $diaRef Dia atual
 * @param array $contasFiltradas Array com IDs das contas
 * @return array [valoresMes, valoresAno]
 */
function obterRealizadoAnteriorMesEAno(PDO $pdo, int $cdempresa, int $anoRef, int $mesRef, int $diaRef, array $contasFiltradas): array {
    if (empty($contasFiltradas)) {
        return [[], []];
    }

    $anoAnterior = $anoRef - 1;
    $valoresMes = array_fill_keys($contasFiltradas, 0.0);
    $valoresAno = array_fill_keys($contasFiltradas, 0.0);

    $sql = "
        SELECT cdcontacontabil,
               COALESCE(valor_acumulado, 0.0) AS valor_acumulado,
               COALESCE(valor_acumulado_ano, 0.0) AS valor_acumulado_ano
          FROM back.v_realizado_conta_mes_por_dia
         WHERE cdempresa       = ?
           AND anoref          = ?
           AND mesref          = ?
           AND diaref          = ?
            AND cdcontacontabil = ANY(?::int[])
    ";

    try {
        $contasArray = formatPgIntArray($contasFiltradas);
        $params = [$cdempresa, $anoAnterior, $mesRef, $diaRef, $contasArray];
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);

        while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $cid = (int)$r['cdcontacontabil'];
            $valoresMes[$cid] = (float)$r['valor_acumulado'];
            $valoresAno[$cid] = (float)$r['valor_acumulado_ano'];
        }
    } catch (Throwable $e) {
        // Erro na consulta: retorna zeros
    }

    return [$valoresMes, $valoresAno];
}

/**
 * Obtém realizado do trimestre anterior (mesmo trimestre do ano anterior)
 * Acumula do início do trimestre até o dia corrente do trimestre anterior
 * 
 * @param PDO $pdo Conexão com banco
 * @param int $cdempresa ID empresa
 * @param int $anoRef Ano de referência
 * @param int $mesesInicioTrimestre Mês inicial do trimestre (1, 4, 7 ou 10)
 * @param int $mesFinalTrimestre Mês final do trimestre (3, 6, 9 ou 12)
 * @param int $mesAtual Mês atual
 * @param int $diaCorrente Dia corrente
 * @param array $contasFiltradas Array de IDs de contas
 * @return array Map [cdconta] => valor_acumulado_trimestre
 */
function obterRealizadoAnteriorTrimestre(PDO $pdo, int $cdempresa, int $anoRef, int $mesesInicioTrimestre, int $mesFinalTrimestre, int $mesAtual, int $diaCorrente, array $contasFiltradas, int $cdcentrocusto = 0): array {
    if (empty($contasFiltradas)) {
        return array_fill_keys($contasFiltradas, 0.0);
    }

    $anoAnterior = $anoRef - 1;
    $valoresTrim = array_fill_keys($contasFiltradas, 0.0);

    // Acumula todos os meses anteriores ao mês atual dentro do trimestre
    for ($m = $mesesInicioTrimestre; $m < $mesAtual; $m++) {
        $sql = "
            SELECT cdcontacontabil, COALESCE(SUM(valor_mes), 0.0) AS total
              FROM back.v_realizado_conta_mes
             WHERE cdempresa       = ?
               AND anoref          = ?
               AND mesref          = ?
               AND cdcontacontabil = ANY(?::int[])";
        
        if ($cdcentrocusto > 0) {
            $sql .= " AND cdcentrocusto = ?";
        }
        
        $sql .= "
          GROUP BY cdcontacontabil
        ";

        try {
            $contasArray = formatPgIntArray($contasFiltradas);
            $params = [$cdempresa, $anoAnterior, $m, $contasArray];
            if ($cdcentrocusto > 0) {
                $params[] = $cdcentrocusto;
            }
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);

            while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $cid = (int)$r['cdcontacontabil'];
                $valoresTrim[$cid] += (float)$r['total'];
            }
        } catch (Throwable $e) {
            // Erro na consulta: continua com próximo mês
        }
    }

    // Para o mês atual do trimestre anterior, acumula apenas até o dia corrente
    if ($mesAtual >= $mesesInicioTrimestre && $mesAtual <= $mesFinalTrimestre) {
        $sql = "
            SELECT cdcontacontabil, COALESCE(valor_acumulado, 0.0) AS valor_acumulado
              FROM back.v_realizado_conta_mes_por_dia
             WHERE cdempresa       = ?
               AND anoref          = ?
               AND mesref          = ?
               AND diaref          = ?
               AND cdcontacontabil = ANY(?::int[])";
        
        if ($cdcentrocusto > 0) {
            $sql .= " AND cdcentrocusto = ?";
        }

        try {
            $contasArray = formatPgIntArray($contasFiltradas);
            $params = [$cdempresa, $anoAnterior, $mesAtual, $diaCorrente, $contasArray];
            if ($cdcentrocusto > 0) {
                $params[] = $cdcentrocusto;
            }
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);

            while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $cid = (int)$r['cdcontacontabil'];
                $valoresTrim[$cid] += (float)$r['valor_acumulado'];
            }
        } catch (Throwable $e) {
            // Erro na consulta: retorna zeros para este mês
        }
    }

    return $valoresTrim;
}

/**
 * Obtém realizado anterior consolidado (múltiplas empresas)
 * Busca valor acumulado do dia similar do ano passado de todas as empresas selecionadas
 * 
 * @param PDO $pdo Conexão com banco
 * @param array $empresasSelecionadas Array de IDs de empresas
 * @param int $anoRef Ano de referência
 * @param int $mesRef Mês de referência
 * @param int $diaRef Dia de referência
 * @param array $contasFiltradas Array de IDs de contas
 * @return array [$valoresMes, $valoresAno]
 */
function obterRealizadoAnteriorMesEAnoConsolidado(PDO $pdo, array $empresasSelecionadas, int $anoRef, int $mesRef, int $diaRef, array $contasFiltradas): array {
    if (empty($contasFiltradas) || empty($empresasSelecionadas)) {
        return [array_fill_keys($contasFiltradas, 0.0), array_fill_keys($contasFiltradas, 0.0)];
    }

    $anoAnterior = $anoRef - 1;
    $valoresMes = array_fill_keys($contasFiltradas, 0.0);
    $valoresAno = array_fill_keys($contasFiltradas, 0.0);

    $empresasPlaceholders = implode(',', array_fill(0, count($empresasSelecionadas), '?'));
    $contasPlaceholders = implode(',', array_fill(0, count($contasFiltradas), '?'));

    $sql = "
        SELECT cdcontacontabil,
               COALESCE(SUM(valor_acumulado), 0.0) AS valor_acumulado,
               COALESCE(SUM(valor_acumulado_ano), 0.0) AS valor_acumulado_ano
          FROM back.v_realizado_conta_mes_por_dia
         WHERE cdempresa IN ($empresasPlaceholders)
           AND anoref          = ?
           AND mesref          = ?
           AND diaref          = ?
           AND cdcontacontabil IN ($contasPlaceholders)
         GROUP BY cdcontacontabil
    ";

    try {
        $params = array_merge($empresasSelecionadas, [$anoAnterior, $mesRef, $diaRef], $contasFiltradas);
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);

        while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $cid = (int)$r['cdcontacontabil'];
            $valoresMes[$cid] = (float)$r['valor_acumulado'];
            $valoresAno[$cid] = (float)$r['valor_acumulado_ano'];
        }
    } catch (Throwable $e) {
        error_log('[obterRealizadoAnteriorMesEAnoConsolidado] Erro: ' . $e->getMessage(), 0);
    }

    return [$valoresMes, $valoresAno];
}

/**
 * Obtém realizado anterior do trimestre consolidado (múltiplas empresas)
 * Acumula do início do trimestre até o dia corrente do trimestre anterior
 * 
 * @param PDO $pdo Conexão com banco
 * @param array $empresasSelecionadas Array de IDs de empresas
 * @param int $anoRef Ano de referência
 * @param int $mesesInicioTrimestre Mês inicial do trimestre (1, 4, 7 ou 10)
 * @param int $mesFinalTrimestre Mês final do trimestre (3, 6, 9 ou 12)
 * @param int $mesAtual Mês atual
 * @param int $diaCorrente Dia corrente
 * @param array $contasFiltradas Array de IDs de contas
 * @param int $cdcentrocusto ID do centro de custo (opcional)
 * @return array Map [cdconta] => valor_acumulado_trimestre
 */
function obterRealizadoAnteriorTrimestreConsolidado(PDO $pdo, array $empresasSelecionadas, int $anoRef, int $mesesInicioTrimestre, int $mesFinalTrimestre, int $mesAtual, int $diaCorrente, array $contasFiltradas, int $cdcentrocusto = 0): array {
    if (empty($contasFiltradas) || empty($empresasSelecionadas)) {
        return array_fill_keys($contasFiltradas, 0.0);
    }

    $anoAnterior = $anoRef - 1;
    $valoresTrim = array_fill_keys($contasFiltradas, 0.0);

    $empresasPlaceholders = implode(',', array_fill(0, count($empresasSelecionadas), '?'));

    // Acumula todos os meses anteriores ao mês atual dentro do trimestre
    for ($m = $mesesInicioTrimestre; $m < $mesAtual; $m++) {
        $sql = "
            SELECT cdcontacontabil, COALESCE(SUM(valor_mes), 0.0) AS total
              FROM back.v_realizado_conta_mes
             WHERE cdempresa IN ($empresasPlaceholders)
               AND anoref          = ?
               AND mesref          = ?
               AND cdcontacontabil IN (";
        
        $sql .= implode(',', array_fill(0, count($contasFiltradas), '?'));
        $sql .= ")";
        
        if ($cdcentrocusto > 0) {
            $sql .= " AND cdcentrocusto = ?";
        }
        
        $sql .= "
          GROUP BY cdcontacontabil
        ";

        try {
            $params = array_merge($empresasSelecionadas, [$anoAnterior, $m], $contasFiltradas);
            if ($cdcentrocusto > 0) {
                $params[] = $cdcentrocusto;
            }
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);

            while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $cid = (int)$r['cdcontacontabil'];
                $valoresTrim[$cid] += (float)$r['total'];
            }
        } catch (Throwable $e) {
            error_log('[obterRealizadoAnteriorTrimestreConsolidado] Erro no mês ' . $m . ': ' . $e->getMessage(), 0);
        }
    }

    // Para o mês atual do trimestre anterior, acumula apenas até o dia corrente
    if ($mesAtual >= $mesesInicioTrimestre && $mesAtual <= $mesFinalTrimestre) {
        $sql = "
            SELECT cdcontacontabil, COALESCE(SUM(valor_acumulado), 0.0) AS valor_acumulado
              FROM back.v_realizado_conta_mes_por_dia
             WHERE cdempresa IN ($empresasPlaceholders)
               AND anoref          = ?
               AND mesref          = ?
               AND diaref          = ?
               AND cdcontacontabil IN (";
        
        $sql .= implode(',', array_fill(0, count($contasFiltradas), '?'));
        $sql .= ")";
        
        if ($cdcentrocusto > 0) {
            $sql .= " AND cdcentrocusto = ?";
        }

        $sql .= "
             GROUP BY cdcontacontabil
        ";

        try {
            $params = array_merge($empresasSelecionadas, [$anoAnterior, $mesAtual, $diaCorrente], $contasFiltradas);
            if ($cdcentrocusto > 0) {
                $params[] = $cdcentrocusto;
            }
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);

            while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $cid = (int)$r['cdcontacontabil'];
                $valoresTrim[$cid] += (float)$r['valor_acumulado'];
            }
        } catch (Throwable $e) {
            error_log('[obterRealizadoAnteriorTrimestreConsolidado] Erro no mês atual: ' . $e->getMessage(), 0);
        }
    }

    return $valoresTrim;
}

/**
 * Obtém valores realizados dos últimos N dias por conta, cruzando meses.
 * Retorna mapa [conta]['YYYY-MM-DD'] => valor_dia
 *
 * @param PDO   $pdo
 * @param int   $cdempresa
 * @param int   $numDias   Quantidade de dias para trás (padrão 7)
 * @param array $contasFiltradas IDs das contas contábeis
 * @return array  [cdcontacontabil]['YYYY-MM-DD'] => valor_total
 */
function obterValoresContasUltimosDias(PDO $pdo, int $cdempresa, int $numDias, array $contasFiltradas): array {
    if (empty($contasFiltradas) || $numDias <= 0) {
        return [];
    }

    // Calcula a data inicial (N dias atrás a partir de hoje)
    $hoje = new DateTime();
    $dataInicio = (clone $hoje)->modify("-" . ($numDias - 1) . " days");

    // Pode envolver 2 meses: mês atual e mês anterior
    $mesesEnvolver = [];
    $dt = clone $dataInicio;
    while ($dt <= $hoje) {
        $key = $dt->format('Y-m');
        if (!isset($mesesEnvolver[$key])) {
            $mesesEnvolver[$key] = [
                'ano' => (int)$dt->format('Y'),
                'mes' => (int)$dt->format('n'),
            ];
        }
        $dt->modify('+1 day');
    }

    $contasArray = formatPgIntArray($contasFiltradas);
    $map = [];

    foreach ($mesesEnvolver as $periodo) {
        $sql = "
            SELECT cdcontacontabil, diaref, COALESCE(SUM(valor_dia), 0.0) AS valor_total
              FROM back.v_realizado_conta_mes_por_dia
             WHERE cdempresa = ?
               AND anoref = ?
               AND mesref = ?
               AND cdcontacontabil = ANY(?::int[])
          GROUP BY cdcontacontabil, diaref
        ";
        $params = [$cdempresa, $periodo['ano'], $periodo['mes'], $contasArray];

        try {
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $cid = (int)$r['cdcontacontabil'];
                $dia = (int)$r['diaref'];
                // Monta a data completa YYYY-MM-DD
                $dataStr = sprintf('%04d-%02d-%02d', $periodo['ano'], $periodo['mes'], $dia);
                // Somente incluir se estiver dentro do range
                if ($dataStr >= $dataInicio->format('Y-m-d') && $dataStr <= $hoje->format('Y-m-d')) {
                    if (!isset($map[$cid])) {
                        $map[$cid] = [];
                    }
                    $map[$cid][$dataStr] = ($map[$cid][$dataStr] ?? 0.0) + (float)$r['valor_total'];
                }
            }
        } catch (Throwable $e) {
            error_log('[obterValoresContasUltimosDias] Erro: ' . $e->getMessage(), 0);
        }
    }

    return $map;
}
