<?php
/**
 * ThesisEditController
 *
 * Centralises all data-fetching and mutation logic for the admin thesis-edit
 * workflow (admin/edit.php + admin/actions/edit.php).
 *
 * Responsibilities:
 *  - Loading thesis data and lookup tables for the edit form view
 *  - Validating and persisting POST submissions (thesis metadata, authors,
 *    jury, languages, formats, tags, banner)
 *  - WCAG 3.3.1: mapping validation exceptions to autofocus field hints
 *
 * The class has NO output side-effects; all redirects, flash writes, and
 * template rendering stay in the thin dispatcher files so the view layer
 * remains easy to inspect and modify.
 */
class ThesisEditController
{
    private Database $db;

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

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

    /**
     * Convenience factory — instantiates Database and returns a ready
     * controller.  Accepts an optional existing Database instance so callers
     * that already hold one (e.g. during testing) can avoid a second
     * connection.
     */
    public static function create(?Database $db = null): self
    {
        require_once APP_ROOT . '/src/Database.php';

        return new self($db ?? Database::getInstance());
    }

    // ── Read / view data ─────────────────────────────────────────────────────

    /**
     * Load all data required to render the edit form.
     *
     * Returns a flat array of view variables:
     *   - 'thesis'              – thesis row (from getThesis)
     *   - 'currentLanguages'   – int[]
     *   - 'currentFormats'     – int[]
     *   - 'jury'               – jury rows
     *   - 'orientations'       – lookup rows
     *   - 'apPrograms'         – lookup rows
     *   - 'finalityTypes'      – lookup rows
     *   - 'languages'          – lookup rows
     *   - 'formatTypes'        – lookup rows
     *   - 'licenseTypes'       – lookup rows
     *   - 'accessTypes'        – lookup rows
     *   - 'currentLicenseId'   – int|null
     *   - 'currentAccessTypeId'– int|null
     *   - 'currentContextNote' – string
     *   - 'pageTitle'          – string
     *
     * @throws Exception if the thesis is not found or a DB error occurs.
     */
    public function load(int $thesisId): array
    {
        if ($thesisId <= 0) {
            throw new InvalidArgumentException("ID invalide");
        }

        $thesis = $this->db->getThesis($thesisId);
        if (!$thesis) {
            throw new RuntimeException("TFE non trouvé");
        }

        $currentLanguages = $this->db->getThesisLanguageIds($thesisId);
        $currentFormats   = $this->db->getThesisFormatIds($thesisId);
        $jury             = $this->db->getThesisJury($thesisId);

        $orientations  = $this->db->getAllOrientations();
        $apPrograms    = $this->db->getAllAPPrograms();
        $finalityTypes = $this->db->getAllFinalityTypes();
        $languages     = $this->db->getAllLanguages();
        $formatTypes   = $this->db->getAllFormatTypes();
        $licenseTypes  = $this->db->getAllLicenseTypes();
        $accessTypes   = $this->db->getAccessTypes();

        $rawRow              = $this->db->getThesisRawFields($thesisId);
        $currentLicenseId    = $rawRow['license_id']    ?? null;
        $currentAccessTypeId = $rawRow['access_type_id'] ?? null;
        $currentContextNote  = $rawRow['context_note']   ?? '';

        // Author contact info (from view)
        $currentAuthorEmail       = $thesis['author_email']        ?? '';
        $currentAuthorShowContact = (bool)($thesis['author_show_contact'] ?? false);

        return [
            'thesis'                    => $thesis,
            'currentLanguages'          => $currentLanguages,
            'currentFormats'            => $currentFormats,
            'jury'                      => $jury,
            'orientations'              => $orientations,
            'apPrograms'                => $apPrograms,
            'finalityTypes'             => $finalityTypes,
            'languages'                 => $languages,
            'formatTypes'               => $formatTypes,
            'licenseTypes'              => $licenseTypes,
            'accessTypes'               => $accessTypes,
            'currentLicenseId'          => $currentLicenseId,
            'currentAccessTypeId'       => $currentAccessTypeId,
            'currentContextNote'        => $currentContextNote,
            'currentAuthorEmail'        => $currentAuthorEmail,
            'currentAuthorShowContact'  => $currentAuthorShowContact,
            'pageTitle'                 => 'Éditer TFE - ' . htmlspecialchars($thesis['title']),
        ];
    }

    // ── Write / action ────────────────────────────────────────────────────────

    /**
     * Validate and persist a thesis-edit POST submission.
     *
     * Runs the full update inside a transaction:
     *   1. Thesis metadata (title, subtitle, year, orientation, ap, finality,
     *      synopsis, context_note, file_size_info, baiu_link, license_id,
     *      access_type_id, is_published)
     *   2. Authors (setThesisAuthors)
     *   3. Jury   (setThesisJury)
     *   4. Languages (setThesisLanguages)
     *   5. Formats   (setThesisFormats)
     *   6. Tags      (setThesisTags)
     * Then handles banner upload/removal outside the transaction.
     *
     * @param int   $thesisId  Validated thesis ID (> 0).
     * @param array $post      Sanitised $_POST array.
     * @param array $files     $_FILES array (expects 'banner' key).
     *
     * @throws Exception on validation or DB error (caller must rollback if
     *                   the transaction is still open, but this method rolls
     *                   back internally before re-throwing).
     */
    public function save(int $thesisId, array $post, array $files): void
    {
        if ($thesisId <= 0) {
            throw new InvalidArgumentException("ID de TFE invalide.");
        }

        $this->db->beginTransaction();

        try {
            // ── 1. Thesis metadata ────────────────────────────────────────────
            $this->db->updateThesis($thesisId, [
                'title'          => trim($post['titre']          ?? ''),
                'subtitle'       => trim($post['subtitle']       ?? ''),
                'year'           => intval($post['année']        ?? 0),
                'orientation_id' => intval($post['orientation']  ?? 0),
                'ap_program_id'  => intval($post['ap']           ?? 0),
                'finality_id'    => intval($post['finality']     ?? 0),
                'synopsis'       => trim($post['synopsis']       ?? ''),
                'context_note'   => trim($post['context_note']   ?? ''),
                'file_size_info' => trim($post['duration_info']  ?? ''),
                'baiu_link'      => trim($post['lien']           ?? ''),
                'license_id'     => filter_var($post['license_id']    ?? '', FILTER_VALIDATE_INT) ?: null,
                'access_type_id' => filter_var($post['access_type_id'] ?? '', FILTER_VALIDATE_INT) ?: null,
                'is_published'   => isset($post['is_published']),
            ]);

            // ── 2. Authors ────────────────────────────────────────────────────
            $authorsRaw    = trim($post['auteurice'] ?? '');
            $showContact   = !empty($post['contact_public']);
            $authorEntries = [];
            if ($authorsRaw !== '') {
                foreach (array_map('trim', explode(',', $authorsRaw)) as $i => $name) {
                    if ($name !== '') {
                        $authorEntries[] = [
                            'name'         => $name,
                            'email'        => $i === 0 ? ($post['mail'] ?? null) : null,
                            'show_contact' => $i === 0 ? $showContact : false,
                        ];
                    }
                }
            }
            $this->db->setThesisAuthors($thesisId, $authorEntries);

            // ── 3. Jury ───────────────────────────────────────────────────────
            $juryMembers = $this->collectJuryMembers($post);
            $this->db->setThesisJury($thesisId, $juryMembers);

            // ── 4. Languages ──────────────────────────────────────────────────
            $this->db->setThesisLanguages(
                $thesisId,
                isset($post['languages']) && is_array($post['languages'])
                    ? $post['languages']
                    : []
            );

            // ── 5. Formats ────────────────────────────────────────────────────
            $this->db->setThesisFormats(
                $thesisId,
                isset($post['formats']) && is_array($post['formats'])
                    ? $post['formats']
                    : []
            );

            // ── 6. Tags ───────────────────────────────────────────────────────
            $keywordsRaw = trim($post['tag'] ?? '');
            $keywords    = $keywordsRaw !== ''
                ? array_map('trim', explode(',', $keywordsRaw))
                : [];
            $this->db->setThesisTags($thesisId, $keywords);

            $this->db->commit();

        } catch (Exception $e) {
            $this->db->rollback();
            throw $e;
        }

        // ── Banner (outside transaction — filesystem op) ──────────────────────
        if (isset($post['remove_banner'])) {
            $currentBannerPath = $this->db->getThesisBannerPath($thesisId);
            if ($currentBannerPath && defined('STORAGE_ROOT')) {
                $absPath = STORAGE_ROOT . '/' . $currentBannerPath;
                if (file_exists($absPath)) {
                    unlink($absPath);
                }
            }
            $this->db->setBannerPath($thesisId, null);
        } else {
            $this->db->handleBannerUpload($thesisId, $files['banner'] ?? null);
        }
    }

    // ── WCAG 3.3.1 helper ─────────────────────────────────────────────────────

    /**
     * Map a validation exception message to the name of the field that should
     * receive autofocus when the form is re-rendered.
     *
     * Returns null when no field mapping is found.
     */
    public static function autofocusFieldForError(string $message): ?string
    {
        if (str_contains($message, 'titre')    || str_contains($message, 'Titre'))    return 'titre';
        if (str_contains($message, 'année')    || str_contains($message, 'Année'))    return 'année';
        if (str_contains($message, 'synopsis') || str_contains($message, 'Synopsis')) return 'synopsis';
        if (str_contains($message, 'auteur')   || str_contains($message, 'Auteur'))   return 'auteurice';
        return null;
    }

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

    /**
     * Build the jury-members array from POST data.
     *
     * @param  array $post  Raw $_POST.
     * @return array<int, array{name: string, role: string, is_external: int}>
     */
    private function collectJuryMembers(array $post): array
    {
        $members = [];

        if (!empty(trim($post['jury_president'] ?? ''))) {
            $members[] = [
                'name'        => trim($post['jury_president']),
                'role'        => 'president',
                'is_external' => 0,
            ];
        }

        if (!empty(trim($post['jury_promoteur'] ?? ''))) {
            $members[] = [
                'name'        => trim($post['jury_promoteur']),
                'role'        => 'promoteur',
                'is_external' => isset($post['jury_promoteur_ext']) ? 1 : 0,
            ];
        }

        foreach ($post['jury_lecteurs'] ?? [] as $i => $name) {
            $name = trim($name);
            if ($name !== '') {
                $members[] = [
                    'name'        => $name,
                    'role'        => 'lecteur',
                    'is_external' => isset($post['jury_lecteurs_ext'][$i]) ? 1 : 0,
                ];
            }
        }

        return $members;
    }
}
