<?php

namespace App\Core;

/**
 * Cache simples baseado em arquivo JSON com TTL.
 * Usado para queries pesadas do Firebird que não mudam a cada segundo.
 */
class Cache
{
    private static string $dir = '';

    public static function init(string $dir): void
    {
        self::$dir = rtrim($dir, '/\\');
        if (!is_dir(self::$dir)) {
            @mkdir(self::$dir, 0777, true);
        }
    }

    private static function file(string $key): string
    {
        if (!self::$dir) {
            self::$dir = dirname(__DIR__, 2) . '/storage/cache';
            if (!is_dir(self::$dir)) @mkdir(self::$dir, 0777, true);
        }
        // MULTI-LOJA: isola o cache por loja ativa (duas lojas com o mesmo cod_empresa
        // não podem colidir). Em single-loja escopo='' => chave inalterada (cache atual preservado).
        $escopo = \App\Core\Lojas::escopoCache();
        if ($escopo !== '') $key = $escopo . '|' . $key;
        return self::$dir . '/' . md5($key) . '.json';
    }

    public static function get(string $key): mixed
    {
        $file = self::file($key);
        if (!file_exists($file)) return null;
        $data = @json_decode(file_get_contents($file), true);
        if (!$data || ($data['expires'] ?? 0) < time()) {
            @unlink($file);
            return null;
        }
        return $data['value'];
    }

    public static function set(string $key, mixed $value, int $ttlSegundos = 300): void
    {
        $file = self::file($key);
        @file_put_contents(
            $file,
            json_encode(['expires' => time() + $ttlSegundos, 'value' => $value]),
            LOCK_EX
        );
    }

    /** Bypass de cache pra request atual — APENAS ?nocache=1 explícito (v1.4.7).
     *  ANTES (v1.4.1) o ?_=<ts> também acionava bypass, mas isso ferrou perf:
     *  o auto-refresh da Gestão de Vendas (v1.4.2) injetava ?_= a cada 30s,
     *  martelando o banco sem aproveitar o cache. Agora ?_= serve só pra quebrar
     *  cache do NAVEGADOR (browser); o cache PHP só é pulado com ?nocache=1. */
    private static ?bool $bypass = null;
    private static function bypassAtivo(): bool
    {
        if (self::$bypass !== null) return self::$bypass;
        self::$bypass = !empty($_GET['nocache']);
        return self::$bypass;
    }

    /**
     * Retorna valor do cache ou executa $fn, armazena e retorna.
     * Quando a request vem com ?nocache=1, ignora o cache e busca direto.
     */
    public static function lembrar(string $key, int $ttlSegundos, callable $fn): mixed
    {
        if (!self::bypassAtivo()) {
            $cached = self::get($key);
            if ($cached !== null) return $cached;
        }
        $value = $fn();
        self::set($key, $value, $ttlSegundos);
        return $value;
    }

    public static function limpar(string $key): void
    {
        @unlink(self::file($key));
    }

    public static function limparTudo(): void
    {
        if (!self::$dir) return;
        foreach (glob(self::$dir . '/*.json') ?: [] as $f) {
            @unlink($f);
        }
    }

    /**
     * v1.4.8.2: Housekeeping — apaga arquivos de cache JÁ EXPIRADOS que ficaram
     * "órfãos" (chaves nunca mais acessadas). Chamado 1x por boot da app via
     * Application::limparCacheExpirado(). Não toca em caches válidos.
     *
     * Antes: chaves antigas (períodos passados, lojas trocadas) ficavam ocupando
     * disco pra sempre porque o get() só limpa quando alguém consulta a chave.
     */
    public static function limparExpirados(): int
    {
        if (!self::$dir) return 0;
        $apagados = 0;
        $agora = time();
        foreach (glob(self::$dir . '/*.json') ?: [] as $f) {
            $raw = @file_get_contents($f);
            if ($raw === false) continue;
            $data = @json_decode($raw, true);
            // Arquivo corrompido OU já expirado → apaga
            if (!is_array($data) || (int)($data['expires'] ?? 0) < $agora) {
                if (@unlink($f)) $apagados++;
            }
        }
        return $apagados;
    }
}
