<?php
/**
 * Questionnaire structure revisions: bump, snapshot manifests, retire questions.
 */

function qdb_active_questions_clause(string $alias = 'question'): string {
    return "COALESCE($alias.retiredAt, 0) = 0";
}

function qdb_active_options_clause(string $alias = 'answer_option'): string {
    return "COALESCE($alias.retiredAt, 0) = 0";
}

function qdb_questionnaire_structure_revision(PDO $pdo, string $qnID): int {
    $stmt = $pdo->prepare(
        'SELECT COALESCE(structureRevision, 1) FROM questionnaire WHERE questionnaireID = :id'
    );
    $stmt->execute([':id' => $qnID]);
    $rev = $stmt->fetchColumn();
    return $rev !== false ? max(1, (int)$rev) : 1;
}

/**
 * @return array<string, mixed>
 */
function qdb_build_structure_manifest(PDO $pdo, string $qnID, bool $includeRetired = false): array {
    $where = 'questionnaireID = :qn';
    if (!$includeRetired) {
        $where .= ' AND ' . qdb_active_questions_clause('question');
    }
    $qStmt = $pdo->prepare(
        "SELECT questionID, type, isRequired, configJson, defaultText, retiredAt
         FROM question WHERE $where ORDER BY orderIndex"
    );
    $qStmt->execute([':qn' => $qnID]);
    $questions = [];
    foreach ($qStmt->fetchAll(PDO::FETCH_ASSOC) as $qRow) {
        $fullId = $qRow['questionID'];
        $shortId = qdb_question_local_id($fullId, $qnID);
        $config = json_decode($qRow['configJson'] ?? '{}', true) ?: [];
        $qKey = qdb_question_key($config, $qRow['defaultText']);

        $optWhere = 'questionID = :qid';
        if (!$includeRetired) {
            $optWhere .= ' AND ' . qdb_active_options_clause('answer_option');
        }
        $aoStmt = $pdo->prepare(
            "SELECT defaultText, points FROM answer_option WHERE $optWhere ORDER BY orderIndex"
        );
        $aoStmt->execute([':qid' => $fullId]);
        $options = [];
        foreach ($aoStmt->fetchAll(PDO::FETCH_ASSOC) as $ao) {
            $options[] = [
                'key'    => $ao['defaultText'],
                'points' => (int)$ao['points'],
            ];
        }

        $entry = [
            'questionID' => $fullId,
            'shortId'    => $shortId,
            'questionKey'=> $qKey,
            'type'       => $qRow['type'] ?? '',
            'isRequired' => (int)($qRow['isRequired'] ?? 0) === 1,
            'options'    => $options,
        ];
        if ($includeRetired && (int)($qRow['retiredAt'] ?? 0) > 0) {
            $entry['retired'] = true;
        }
        if (($qRow['type'] ?? '') === 'glass_scale_question' && !empty($config['symptoms'])) {
            $entry['symptoms'] = array_values(array_map('strval', (array)$config['symptoms']));
        }
        $questions[] = $entry;
    }

    return [
        'questionnaireID'   => $qnID,
        'structureRevision' => qdb_questionnaire_structure_revision($pdo, $qnID),
        'questions'         => $questions,
    ];
}

/**
 * @return array<string, mixed>|null
 */
function qdb_load_structure_manifest(PDO $pdo, string $qnID, int $revision): ?array {
    $stmt = $pdo->prepare(
        'SELECT manifestJson FROM questionnaire_structure_snapshot
         WHERE questionnaireID = :qn AND structureRevision = :rev'
    );
    $stmt->execute([':qn' => $qnID, ':rev' => $revision]);
    $json = $stmt->fetchColumn();
    if ($json === false || $json === '') {
        return null;
    }
    $decoded = json_decode((string)$json, true);
    return is_array($decoded) ? $decoded : null;
}

function qdb_save_structure_snapshot(PDO $pdo, string $qnID, int $revision, array $manifest): void {
    $json = json_encode($manifest, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    if ($json === false) {
        $json = '{}';
    }
    $pdo->prepare(
        'INSERT INTO questionnaire_structure_snapshot (questionnaireID, structureRevision, createdAt, manifestJson)
         VALUES (:qn, :rev, :ts, :mj) '
        . qdb_upsert_update(
            ['questionnaireID', 'structureRevision'],
            [
                'manifestJson' => 'excluded.manifestJson',
                'createdAt' => 'excluded.createdAt',
            ]
        )
    )->execute([
        ':qn'  => $qnID,
        ':rev' => $revision,
        ':ts'  => time(),
        ':mj'  => $json,
    ]);
}

/**
 * Backfill revision-1 snapshots for questionnaires missing any snapshot row.
 */
function qdb_backfill_structure_snapshots(PDO $pdo): bool {
    $ids = $pdo->query('SELECT questionnaireID FROM questionnaire')->fetchAll(PDO::FETCH_COLUMN);
    $changed = false;
    foreach ($ids as $qnID) {
        $qnID = (string)$qnID;
        $chk = $pdo->prepare(
            'SELECT 1 FROM questionnaire_structure_snapshot
             WHERE questionnaireID = :qn LIMIT 1'
        );
        $chk->execute([':qn' => $qnID]);
        if ($chk->fetchColumn()) {
            continue;
        }
        $rev = qdb_questionnaire_structure_revision($pdo, $qnID);
        $manifest = qdb_build_structure_manifest($pdo, $qnID, true);
        $manifest['structureRevision'] = $rev;
        qdb_save_structure_snapshot($pdo, $qnID, $rev, $manifest);
        $changed = true;
    }
    return $changed;
}

function qdb_bump_structure_revision(PDO $pdo, string $qnID, string $reason = ''): int {
    $current = qdb_questionnaire_structure_revision($pdo, $qnID);
    $manifest = qdb_build_structure_manifest($pdo, $qnID, false);
    $manifest['structureRevision'] = $current;
    qdb_save_structure_snapshot($pdo, $qnID, $current, $manifest);

    $newRev = $current + 1;
    $pdo->prepare(
        'UPDATE questionnaire SET structureRevision = :rev, structureChangedAt = :ts WHERE questionnaireID = :qn'
    )->execute([':rev' => $newRev, ':ts' => time(), ':qn' => $qnID]);

    return $newRev;
}

function qdb_questionnaire_id_for_question(PDO $pdo, string $questionID): ?string {
    $stmt = $pdo->prepare('SELECT questionnaireID FROM question WHERE questionID = :qid');
    $stmt->execute([':qid' => $questionID]);
    $id = $stmt->fetchColumn();
    return $id !== false ? (string)$id : null;
}

function qdb_option_has_client_data(PDO $pdo, string $answerOptionID): bool {
    $stmt = $pdo->prepare('SELECT 1 FROM client_answer WHERE answerOptionID = :id LIMIT 1');
    $stmt->execute([':id' => $answerOptionID]);
    if ($stmt->fetchColumn()) {
        return true;
    }
    if (!qdb_table_exists($pdo, 'client_answer_submission')) {
        return false;
    }
    $stmt = $pdo->prepare(
        'SELECT 1 FROM client_answer_submission WHERE answerOptionID = :id LIMIT 1'
    );
    $stmt->execute([':id' => $answerOptionID]);
    return (bool)$stmt->fetchColumn();
}

function qdb_question_has_client_data(PDO $pdo, string $questionID): bool {
    $stmt = $pdo->prepare('SELECT 1 FROM client_answer WHERE questionID = :qid LIMIT 1');
    $stmt->execute([':qid' => $questionID]);
    if ($stmt->fetchColumn()) {
        return true;
    }
    if (!qdb_table_exists($pdo, 'client_answer_submission')) {
        return false;
    }
    $stmt = $pdo->prepare(
        'SELECT 1 FROM client_answer_submission WHERE questionID = :qid LIMIT 1'
    );
    $stmt->execute([':qid' => $questionID]);
    return (bool)$stmt->fetchColumn();
}

function qdb_retire_question(PDO $pdo, string $questionID, int $revision): void {
    $pdo->prepare(
        'UPDATE question SET retiredAt = :ts, retiredInRevision = :rev
         WHERE questionID = :qid AND COALESCE(retiredAt, 0) = 0'
    )->execute([':ts' => time(), ':rev' => $revision, ':qid' => $questionID]);
}

/**
 * Build submit maps from a structure manifest (current or legacy).
 *
 * @return array{
 *   shortIdMap: array<string, string>,
 *   shortIdToType: array<string, string>,
 *   optionMap: array<string, array<string, array{answerOptionID: string, points: int}>>,
 *   symptomParentMap: array<string, string>,
 *   freeTextMaxLen: array<string, int>,
 *   manifestQuestionIds: list<string>
 * }
 */
function qdb_submit_maps_from_manifest(PDO $pdo, string $qnID, array $manifest): array {
    $shortIdMap = [];
    $shortIdToType = [];
    $optionMap = [];
    $symptomParentMap = [];
    $freeTextMaxLen = [];
    $manifestQuestionIds = [];

    foreach ($manifest['questions'] ?? [] as $qEntry) {
        if (!is_array($qEntry)) {
            continue;
        }
        $fullId = (string)($qEntry['questionID'] ?? '');
        $shortId = (string)($qEntry['shortId'] ?? '');
        if ($fullId === '' || $shortId === '') {
            continue;
        }
        $type = (string)($qEntry['type'] ?? '');
        $shortIdMap[$shortId] = $fullId;
        $shortIdToType[$shortId] = $type;
        $manifestQuestionIds[] = $fullId;

        if ($type === 'free_text') {
            $cfgStmt = $pdo->prepare('SELECT configJson FROM question WHERE questionID = :qid');
            $cfgStmt->execute([':qid' => $fullId]);
            $cfgJson = $cfgStmt->fetchColumn();
            $cfg = json_decode($cfgJson ?: '{}', true) ?: [];
            $freeTextMaxLen[$fullId] = max(1, min((int)($cfg['maxLength'] ?? 500), 10000));
        }

        $optionMap[$fullId] = [];
        foreach ($qEntry['options'] ?? [] as $opt) {
            if (!is_array($opt)) {
                continue;
            }
            $key = (string)($opt['key'] ?? '');
            if ($key === '') {
                continue;
            }
            $aoStmt = $pdo->prepare(
                'SELECT answerOptionID, points FROM answer_option
                 WHERE questionID = :qid AND defaultText = :k LIMIT 1'
            );
            $aoStmt->execute([':qid' => $fullId, ':k' => $key]);
            $aoRow = $aoStmt->fetch(PDO::FETCH_ASSOC);
            $optionMap[$fullId][$key] = [
                'answerOptionID' => $aoRow ? (string)$aoRow['answerOptionID'] : '',
                'points'         => $aoRow ? (int)$aoRow['points'] : (int)($opt['points'] ?? 0),
            ];
        }

        if ($type === 'glass_scale_question') {
            foreach ($qEntry['symptoms'] ?? [] as $symptomKey) {
                $sk = trim((string)$symptomKey);
                if ($sk !== '') {
                    $symptomParentMap[$sk] = $fullId;
                }
            }
        }
    }

    if ($symptomParentMap === []) {
        $symptomParentMap = qdb_glass_symptom_parent_map($pdo, $qnID);
    }

    return [
        'shortIdMap'          => $shortIdMap,
        'shortIdToType'       => $shortIdToType,
        'optionMap'           => $optionMap,
        'symptomParentMap'    => $symptomParentMap,
        'freeTextMaxLen'      => $freeTextMaxLen,
        'manifestQuestionIds' => $manifestQuestionIds,
    ];
}

/**
 * Build submit maps from active DB questions (current revision).
 *
 * @return array<string, mixed>
 */
function qdb_submit_maps_from_active_questions(PDO $pdo, string $qnID): array {
    $manifest = qdb_build_structure_manifest($pdo, $qnID, false);
    $manifest['structureRevision'] = qdb_questionnaire_structure_revision($pdo, $qnID);
    return qdb_submit_maps_from_manifest($pdo, $qnID, $manifest);
}

/**
 * @return list<array{field: string, header: string, questionID: string, type: string}>
 */
function qdb_result_columns_from_manifest(array $manifest): array {
    $cols = [];
    foreach ($manifest['questions'] ?? [] as $qEntry) {
        if (!is_array($qEntry)) {
            continue;
        }
        $fullId = (string)($qEntry['questionID'] ?? '');
        $shortId = (string)($qEntry['shortId'] ?? '');
        $qKey = (string)($qEntry['questionKey'] ?? '');
        $type = (string)($qEntry['type'] ?? '');
        if ($fullId === '') {
            continue;
        }
        $header = $qKey !== '' ? $qKey : ($shortId !== '' ? $shortId : $fullId);
        $cols[] = [
            'field'      => $header,
            'header'     => $header,
            'questionID' => $fullId,
            'type'       => $type,
        ];
        if ($type === 'glass_scale_question') {
            foreach ($qEntry['symptoms'] ?? [] as $symptomKey) {
                $sk = trim((string)$symptomKey);
                if ($sk === '') {
                    continue;
                }
                $cols[] = [
                    'field'      => $sk,
                    'header'     => $sk,
                    'questionID' => $fullId,
                    'type'       => 'glass_symptom',
                    'symptomKey' => $sk,
                ];
            }
        }
    }
    return $cols;
}

function qdb_compute_questionnaire_score_from_manifest(
    PDO $pdo,
    string $clientCode,
    array $manifest
): int {
    require_once __DIR__ . '/scoring.php';
    $qnID = (string)($manifest['questionnaireID'] ?? '');
    if ($qnID === '') {
        return 0;
    }

    $questionIDs = [];
    foreach ($manifest['questions'] ?? [] as $qEntry) {
        if (is_array($qEntry) && !empty($qEntry['questionID'])) {
            $questionIDs[] = (string)$qEntry['questionID'];
        }
    }
    if ($questionIDs === []) {
        return 0;
    }

    $ph = implode(',', array_fill(0, count($questionIDs), '?'));
    $aStmt = $pdo->prepare(
        "SELECT ca.questionID, ca.answerOptionID, ca.freeTextValue, ca.numericValue,
                ao.defaultText AS optionKey
         FROM client_answer ca
         LEFT JOIN answer_option ao ON ao.answerOptionID = ca.answerOptionID
         WHERE ca.clientCode = ? AND ca.questionID IN ($ph)"
    );
    $aStmt->execute(array_merge([$clientCode], $questionIDs));
    $answersByQuestion = [];
    foreach ($aStmt->fetchAll(PDO::FETCH_ASSOC) as $a) {
        $answersByQuestion[$a['questionID']] = $a;
    }

    $total = 0;
    foreach ($manifest['questions'] ?? [] as $qEntry) {
        if (!is_array($qEntry)) {
            continue;
        }
        $fullId = (string)($qEntry['questionID'] ?? '');
        $type = (string)($qEntry['type'] ?? '');
        $optionPoints = [];
        foreach ($qEntry['options'] ?? [] as $opt) {
            if (is_array($opt) && isset($opt['key'])) {
                $optionPoints[(string)$opt['key']] = (int)($opt['points'] ?? 0);
            }
        }
        $qRow = [
            'questionID' => $fullId,
            'type'       => $type,
            'configJson' => json_encode(
                ['symptoms' => $qEntry['symptoms'] ?? []],
                JSON_UNESCAPED_UNICODE
            ),
        ];
        $total += qdb_score_points_for_question(
            $pdo,
            $qRow,
            $answersByQuestion[$fullId] ?? null,
            $optionPoints
        );
    }
    return $total;
}
