<?php

/**
 * TfeController
 *
 * Handles all data-fetching and view-variable assembly for the public TFE
 * detail page (public/tfe.php).
 *
 * Responsibilities:
 *  - Validate the `id` GET parameter and load the thesis record
 *  - Enforce publication visibility (redirect to index on 404)
 *  - Resolve the OG image (cover file → first image file)
 *  - Build the complete OG / Twitter Card tag array
 *  - Assemble the meta description from the synopsis
 *  - Collect WebVTT caption file paths for video pairing
 *  - Return a flat array of view variables ready for template extraction
 *
 * The class has NO output side-effects; all template rendering stays in
 * public/tfe.php so the view layer remains easy to inspect and modify.
 */
class TfeController
{
    private const BASE_URL     = 'https://xamxam.erg.be';
    private const META_MAX_LEN = 160;

    private Database $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    // ── Factory ───────────────────────────────────────────────────────────────

    /**
     * Convenience factory: loads the Database singleton and returns a ready
     * controller instance.
     */
    public static function create(): self
    {
        require_once APP_ROOT . '/src/Database.php';
        require_once APP_ROOT . '/src/ErrorHandler.php';

        return new self(Database::getInstance());
    }

    // ── Main entry point ─────────────────────────────────────────────────────

    /**
     * Process the current request.
     *
     * On success returns an array of view variables.
     * On failure (missing id, thesis not found) sends a redirect and exits.
     *
     * @return array<string, mixed>
     */
    public function handle(): array
    {
        $thesisId = isset($_GET['id']) ? (int) $_GET['id'] : 0;

        if ($thesisId <= 0) {
            $this->redirectHome();
        }

        try {
            $data = $this->db->getThesisById($thesisId);
        } catch (Exception $e) {
            ErrorHandler::log('tfe_view', $e, ['thesis_id' => $thesisId]);
            $this->redirectHome();
        }

        if (empty($data)) {
            $this->redirectHome();
        }

        // Access type (1 = open, 2 = restricted, 3 = forbidden)
        $accessTypeId      = $this->db->getThesisAccessTypeId($thesisId) ?? 1;
        $isInterdit        = ($accessTypeId === 3);

        // Check if restricted files feature is enabled and user has access
        $restrictedEnabled = $this->db->isRestrictedFilesEnabled();
        $hasRestrictedAccess = false;

        if ($restrictedEnabled && $accessTypeId === 2) {
            // Check for cookie-based access
            $cookieToken = $_COOKIE['tfe_access_' . $thesisId] ?? null;
            if ($cookieToken) {
                $hasRestrictedAccess = $this->db->hasValidCookieAccess($cookieToken, $thesisId);
            }
        }

        // If access is restricted and user doesn't have valid access, hide files
        $shouldHideFiles = ($restrictedEnabled && $accessTypeId === 2 && !$hasRestrictedAccess);

        // Caption (WebVTT) files — N-th VTT is paired with the N-th <video>
        $captionFiles = $this->collectCaptionPaths($data['files'] ?? []);

        // Jury members with interne/externe split
        $jury = $this->db->getThesisJury($thesisId);
        $juryByRole = $this->splitJuryByRole($jury);

        // Page meta
        $metaDescription = $this->buildMetaDescription($data['synopsis'] ?? '');
        $ogTags          = $this->buildOgTags($data, $thesisId, $metaDescription);
        $pageTitle       = $data['title']
            . (!empty($data['authors']) ? ' – ' . $data['authors'] : '')
            . ' – XAMXAM';

        return [
            // Core data
            'thesisId'          => $thesisId,
            'data'              => $data,
            'accessTypeId'      => $accessTypeId,
            'isInterdit'        => $isInterdit,
            'captionFiles'      => $captionFiles,
            'juryPresidents'      => $juryByRole['presidents'],
            'promoteursInternes'  => $juryByRole['internes'],
            'promoteursExternes'  => $juryByRole['externes'],
            'promoteursUlb'       => $juryByRole['ulb'],
            'juryLecteursInternes' => $juryByRole['lecteurs_internes'],
            'juryLecteursExternes' => $juryByRole['lecteurs_externes'],

            // Restricted files access
            'restrictedEnabled'  => $restrictedEnabled,
            'hasRestrictedAccess' => $hasRestrictedAccess,
            'shouldHideFiles'    => $shouldHideFiles,

            // Page meta
            'pageTitle'       => $pageTitle,
            'metaDescription' => $metaDescription,
            'ogTags'          => $ogTags,

            // Layout
            'currentNav'  => '',
            'extraCss'    => ['/assets/css/tfe.css'],
            'bodyClass'   => 'tfe-body',
        ];
    }

    // ── Private helpers ───────────────────────────────────────────────────────

    /**
     * Build a ~160-character meta description from the thesis synopsis.
     */
    protected function buildMetaDescription(string $synopsis): string
    {
        $plain = strip_tags($synopsis);

        if (empty($plain)) {
            return 'Mémoire de fin d\'études – XAMXAM, répertoire des TFE de l\'erg.';
        }

        return strlen($plain) > self::META_MAX_LEN
            ? substr($plain, 0, self::META_MAX_LEN - 3) . '…'
            : $plain;
    }

    /**
     * Resolve the OG image URL: cover file → first image file → empty string.
     *
     * @param  array<int, array<string, mixed>> $files
     */
    protected function resolveOgImage(array $files): string
    {
        // Prefer the dedicated cover
        foreach ($files as $file) {
            if (($file['file_type'] ?? '') === 'cover') {
                return self::BASE_URL . '/media.php?path=' . rawurlencode($file['file_path']);
            }
        }

        // Fall back to first image file
        foreach ($files as $file) {
            $ext = strtolower(pathinfo($file['file_path'] ?? '', PATHINFO_EXTENSION));
            if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'], true)) {
                return self::BASE_URL . '/media.php?path=' . rawurlencode($file['file_path']);
            }
        }

        return '';
    }

    /**
     * Build the complete $ogTags array consumed by templates/head.php.
     *
     * @param  array<string, mixed> $data
     * @return array<string, string>
     */
    protected function buildOgTags(array $data, int $thesisId, string $metaDescription): array
    {
        $ogImage  = $this->resolveOgImage($data['files'] ?? []);
        $title    = $data['title'] . (!empty($data['authors']) ? ' – ' . $data['authors'] : '');
        $imageAlt = $data['title'] . (!empty($data['authors']) ? ' par ' . $data['authors'] : '');

        return [
            'type'                   => 'article',
            'title'                  => $title,
            'description'            => $metaDescription,
            'url'                    => self::BASE_URL . '/tfe?id=' . $thesisId,
            'image'                  => $ogImage,
            'image_alt'              => $imageAlt,
            'site_name'              => 'XAMXAM – ERG',
            'article_author'         => $data['authors'] ?? '',
            'article_published_time' => !empty($data['year']) ? $data['year'] . '-01-01' : '',
        ];
    }

    /**
     * Split jury members by role and internal/external/ULB flags.
     *
     * @param  array<int, array<string, mixed>> $jury
     * @return array{presidents: list<string>, internes: list<string>, externes: list<string>, ulb: list<string>, lecteurs_internes: list<string>, lecteurs_externes: list<string>}
     */
    protected function splitJuryByRole(array $jury): array
    {
        $result = ['presidents' => [], 'internes' => [], 'externes' => [], 'ulb' => [], 'lecteurs_internes' => [], 'lecteurs_externes' => []];

        foreach ($jury as $member) {
            $name = $member['name'] ?? '';
            if ($name === '') {
                continue;
            }

            switch ($member['role']) {
                case 'president':
                    $result['presidents'][] = $name;
                    break;
                case 'promoteur':
                    if ((int)($member['is_ulb'] ?? 0) === 1) {
                        $result['ulb'][] = $name;
                    } elseif ((int)$member['is_external'] === 1) {
                        $result['externes'][] = $name;
                    } else {
                        $result['internes'][] = $name;
                    }
                    break;
                case 'lecteur':
                    if ((int)$member['is_external'] === 1) {
                        $result['lecteurs_externes'][] = $name;
                    } else {
                        $result['lecteurs_internes'][] = $name;
                    }
                    break;
            }
        }

        return $result;
    }

    /**
     * Return an ordered list of VTT caption file paths from the files array.
     * The N-th entry corresponds to the N-th <video> element in document order.
     *
     * @param  array<int, array<string, mixed>> $files
     * @return list<string>
     */
    protected function collectCaptionPaths(array $files): array
    {
        $captions = [];

        foreach ($files as $file) {
            $mime = $file['mime_type'] ?? '';
            $ext  = strtolower(pathinfo($file['file_path'] ?? '', PATHINFO_EXTENSION));

            if ($mime === 'text/vtt' || $ext === 'vtt') {
                $captions[] = $file['file_path'];
            }
        }

        return $captions;
    }

    // ── Response helpers ──────────────────────────────────────────────────────

    /**
     * Redirect to the home page and terminate.  Never returns.
     */
    private function redirectHome(): never
    {
        header('Location: /');
        exit;
    }
}
