<?php
/*
 * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * See the LICENSE file in the project root for the full license text.
 */

declare(strict_types=1);

namespace App\Http;

use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFunction;

/**
 * Twig 3 wrapper that keeps the historical View::render($name, $data, $layout)
 * signature so controllers don't change. Templates live under views/ as
 * *.twig and use {% extends "layout.twig" %} for inheritance — the $layout
 * parameter is now vestigial (only `null` is honoured, used by the bare
 * /sprints/{id}/present route to skip layout inheritance — but Twig's
 * extends handles that just by picking the right base template).
 */
final class View
{
    private readonly Environment $twig;

    public function __construct(string $viewsDir, ?string $cacheDir = null)
    {
        $loader = new FilesystemLoader($viewsDir);
        $this->twig = new Environment($loader, [
            'cache'            => $cacheDir ?? false,
            'auto_reload'      => (getenv('APP_ENV') ?: 'production') !== 'production',
            'strict_variables' => false,
            'autoescape'       => 'html',
        ]);

        // fmt_days(x) — same shape used by the previous PHP templates and the
        // CapacityCalculator: 0 → "0", whole numbers → integer, halves → x.5.
        $this->twig->addFunction(new TwigFunction(
            'fmt_days',
            static function (mixed $x): string {
                $n = (float) $x;
                if (abs($n - round($n)) < 1e-9) {
                    return (string) (int) round($n);
                }
                return number_format($n, 1);
            }
        ));

        // fmt_rtb(x) — two decimals, e.g. 0.05.
        $this->twig->addFunction(new TwigFunction(
            'fmt_rtb',
            static fn(mixed $x): string => number_format((float) $x, 2, '.', '')
        ));

        // pretty_json(raw) — used by audit/index.twig for the diff <pre> blocks.
        $this->twig->addFunction(new TwigFunction(
            'pretty_json',
            static function (?string $raw): string {
                if ($raw === null || $raw === '') {
                    return '';
                }
                try {
                    $v = json_decode($raw, true, 64, JSON_THROW_ON_ERROR);
                } catch (\JsonException) {
                    return $raw;
                }
                return (string) (json_encode(
                    $v,
                    JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
                ) ?: $raw);
            }
        ));

        // query_string(filters, drop, extra) — rebuilds a query string while
        // dropping one key and merging extras. Used by /audit pagination.
        $this->twig->addFunction(new TwigFunction(
            'query_string',
            /**
             * @param array<string,scalar|null> $filters
             * @param array<string,scalar|null> $extra
             */
            static function (array $filters, string $drop, array $extra = []): string {
                $params = array_filter(
                    array_merge($filters, $extra),
                    static fn($v) => $v !== '' && $v !== null
                );
                unset($params[$drop]);
                return $params === [] ? '' : '?' . http_build_query($params);
            }
        ));
    }

    /**
     * Render a view and return the HTML.
     *
     * @param string              $name view name; "home" → views/home.twig
     * @param array<string,mixed> $data
     * @param string|null         $layout retained for back-compat; ignored
     *                                    by Twig (templates use {% extends %})
     */
    public function render(string $name, array $data = [], ?string $layout = 'layout'): string
    {
        unset($layout);
        return $this->twig->render($name . '.twig', $data);
    }

    /** @param array<string,mixed> $data */
    public function renderRaw(string $name, array $data): string
    {
        return $this->twig->render($name . '.twig', $data);
    }

    /** Direct access to the Twig env (rarely needed; mostly for tests). */
    public function twig(): Environment
    {
        return $this->twig;
    }
}

/** Escape for HTML. Kept so legacy callers still resolve; Twig auto-escapes. */
function e(mixed $v): string
{
    if ($v === null) {
        return '';
    }
    return htmlspecialchars((string) $v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
