package com.tomhempel.rbarometer.ui

import android.graphics.Typeface
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.tomhempel.rbarometer.MainActivity
import com.tomhempel.rbarometer.MyApp
import com.tomhempel.rbarometer.R
import com.tomhempel.rbarometer.data.AnswerRepository
import com.tomhempel.rbarometer.data.CoachScoringReviewRepository
import com.tomhempel.rbarometer.data.CompletedQuestionnaireRepository
import com.tomhempel.rbarometer.data.PendingUploadRepository
import com.tomhempel.rbarometer.data.Question
import com.tomhempel.rbarometer.network.DatabaseUploader
import com.tomhempel.rbarometer.network.QuestionnaireCache
import com.tomhempel.rbarometer.questionnaire.AppLanguages
import com.tomhempel.rbarometer.questionnaire.QuestionnaireTranslationsLoader
import com.tomhempel.rbarometer.ui.localization.UiStrings
import com.tomhempel.rbarometer.utils.ScreenLoading
import com.tomhempel.rbarometer.utils.ViewUtils
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.gson.JsonParser
import kotlinx.coroutines.*

/**
 * Upload overview: pending questionnaires across clients, localized UI, language selector.
 */
class UploadOverviewHandler(
    private val activity: MainActivity,
    private val token: String,
    private val languageIDProvider: () -> String,
    private val onLanguageChanged: (String) -> Unit = {},
    private val onClose: (uploaded: Boolean) -> Unit,
) {
    private val uiScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
    private val tag = "UploadOverviewHandler"

    private var uploadLanguageId: String = languageIDProvider()
    private var langSpinner: Spinner? = null
    private var detailLangSpinner: Spinner? = null
    private var suppressLangCallback = false
    private var overviewLoadJob: Job? = null
    private var detailLoadJob: Job? = null
    private var overviewActive = false
    private var uploadedDuringSession = false

    private val nameMap: Map<String, String> by lazy {
        QuestionnaireCache.getQuestionnaireList(activity).associate { it.id to it.name }
    }

    data class PendingRow(
        val clientCode: String,
        val questionnaireId: String,
        val questionnaireName: String,
        val answeredCount: Int,
    )

    data class PendingScoringRow(
        val clientCode: String,
        val profileID: String,
        val profileName: String,
        val coachBand: String,
        val weightedTotal: Double,
    )

    sealed class ListItem {
        data class ClientHeader(val clientCode: String) : ListItem()
        data class Questionnaire(val row: PendingRow) : ListItem()
        data class ScoringReview(val row: PendingScoringRow) : ListItem()
    }

    fun show() {
        openOverviewScreen()
    }

    private fun closeOverview() {
        overviewActive = false
        overviewLoadJob?.cancel()
        overviewLoadJob = null
        detailLoadJob?.cancel()
        detailLoadJob = null
        activity.setBackPressInterceptor(null)
        onClose(uploadedDuringSession)
    }

    private fun openOverviewScreen() {
        overviewLoadJob?.cancel()
        detailLoadJob?.cancel()
        overviewActive = true
        activity.setContentView(R.layout.upload_overview_screen)
        activity.setBackPressInterceptor {
            closeOverview()
            true
        }
        uploadLanguageId = languageIDProvider()

        val titleTv = requireView<TextView>(R.id.titleUploadOverview)
        val subtitleTv = requireView<TextView>(R.id.subtitleUploadOverview)
        val summaryTv = requireView<TextView>(R.id.summaryUploadOverview)
        val listLoading = requireView<View>(R.id.uploadListLoading)
        val progressDeterminate = requireView<ProgressBar>(R.id.progressBarUploadDeterminate)
        val emptyView = requireView<TextView>(R.id.emptyViewUpload)
        val recycler = requireView<RecyclerView>(R.id.uploadListRecycler)
        val backButton = requireView<Button>(R.id.backButtonUpload)
        val uploadButton = requireView<MaterialButton>(R.id.confirmUploadButton)
        langSpinner = requireView(R.id.uploadLangSpinner)

        bindChrome(titleTv, subtitleTv, backButton, uploadButton)
        setupLanguageSpinner(langSpinner) {
            reloadOverviewList(
                recycler, summaryTv, listLoading, emptyView, uploadButton,
                titleTv, subtitleTv, backButton,
            )
        }

        backButton.setOnClickListener { closeOverview() }

        setListLoading(listLoading, true, recycler, summaryTv, emptyView)
        progressDeterminate.visibility = View.GONE
        summaryTv.visibility = View.GONE

        val adapter = UploadListAdapter(
            onRowClick = { row -> openDetailScreen(row.clientCode, row.questionnaireId) },
            clientHeaderText = { code -> "${td("client", "Client")}: $code" },
            summaryForCount = { count -> buildSummaryString(count) },
            scoringSummaryFor = { row -> buildScoringSummaryString(row) },
            scoringTitleFor = { row -> scoringReviewTitle(row) },
        )
        recycler.layoutManager = LinearLayoutManager(activity)
        recycler.adapter = adapter

        overviewLoadJob = uiScope.launch {
            val items = loadPendingListItems()
            if (!overviewActive) return@launch
            setListLoading(listLoading, false, recycler, summaryTv, emptyView)
            if (items.isEmpty()) {
                emptyView.text = td("no_questionnaires", "Nothing pending upload.")
                emptyView.visibility = View.VISIBLE
                uploadButton.isEnabled = false
                uploadButton.alpha = 0.5f
                adapter.submit(emptyList())
                return@launch
            }
            val qCount = items.count { it is ListItem.Questionnaire }
            val sCount = items.count { it is ListItem.ScoringReview }
            val cCount = items.filterIsInstance<ListItem.ClientHeader>().size
            summaryTv.text = formatSummary(qCount, sCount, cCount)
            summaryTv.visibility = View.VISIBLE
            uploadButton.isEnabled = true
            uploadButton.alpha = 1f
            adapter.submit(items)

            uploadButton.setOnClickListener {
                uploadButton.isEnabled = false
                uploadButton.alpha = 0.5f
                backButton.isEnabled = false
                progressDeterminate.visibility = View.VISIBLE
                progressDeterminate.isIndeterminate = false
                progressDeterminate.progress = 0
                progressDeterminate.max = (qCount + sCount).coerceAtLeast(1)

                DatabaseUploader.uploadDatabaseWithToken(
                    activity,
                    token,
                    uploadLanguageId,
                    onProgress = { done, total ->
                        if (!overviewActive) return@uploadDatabaseWithToken
                        progressDeterminate.max = total.coerceAtLeast(1)
                        progressDeterminate.progress = done.coerceAtMost(progressDeterminate.max)
                    },
                ) { result ->
                    if (!overviewActive) return@uploadDatabaseWithToken
                    progressDeterminate.progress = progressDeterminate.max
                    progressDeterminate.visibility = View.GONE
                    backButton.isEnabled = true
                    uploadButton.isEnabled = true
                    uploadButton.alpha = 1f
                    showUploadResultDialog(result)
                }
            }
        }
    }

    private fun reloadOverviewList(
        recycler: RecyclerView,
        summaryTv: TextView,
        listLoading: View,
        emptyView: TextView,
        uploadButton: MaterialButton,
        titleTv: TextView,
        subtitleTv: TextView,
        backButton: Button,
    ) {
        if (!overviewActive) return
        val adapter = recycler.adapter as? UploadListAdapter ?: return
        overviewLoadJob?.cancel()
        setListLoading(listLoading, true, recycler, summaryTv, emptyView)
        overviewLoadJob = uiScope.launch {
            val items = loadPendingListItems()
            if (!overviewActive) return@launch
            setListLoading(listLoading, false, recycler, summaryTv, emptyView)
            bindChrome(titleTv, subtitleTv, backButton, uploadButton)
            adapter.submit(items)
            if (items.isEmpty()) {
                summaryTv.visibility = View.GONE
                emptyView.text = td("no_questionnaires", "Nothing pending upload.")
                emptyView.visibility = View.VISIBLE
                uploadButton.isEnabled = false
                uploadButton.alpha = 0.5f
            } else {
                val qCount = items.count { it is ListItem.Questionnaire }
                val sCount = items.count { it is ListItem.ScoringReview }
                val cCount = items.filterIsInstance<ListItem.ClientHeader>().size
                summaryTv.text = formatSummary(qCount, sCount, cCount)
                summaryTv.visibility = View.VISIBLE
                emptyView.visibility = View.GONE
                uploadButton.isEnabled = true
                uploadButton.alpha = 1f
            }
        }
    }

    private suspend fun loadPendingListItems(): List<ListItem> = withContext(Dispatchers.IO) {
        val pending = PendingUploadRepository.getUploadReadyQuestionnaires(activity)
        val pendingScoring = PendingUploadRepository.getUploadReadyScoringReviews(activity)
        if (pending.isEmpty() && pendingScoring.isEmpty()) return@withContext emptyList()

        val qRows = pending.map { entry ->
            val answers = AnswerRepository
                .getAnswersForClientAndQuestionnaire(entry.clientCode, entry.questionnaireId)
            val answeredCount = filterDuplicateAliasAnswerIds(
                entry.questionnaireId,
                answers.map { it.questionId },
            )
                .mapNotNull { questionId -> answers.find { it.questionId == questionId } }
                .count { it.answerValue.isNotBlank() }
            val displayName = UiStrings.questionnaireDisplayName(
                uploadLanguageId,
                entry.questionnaireId,
                nameMap[entry.questionnaireId],
            )
            PendingRow(entry.clientCode, entry.questionnaireId, displayName, answeredCount)
        }

        val sRows = pendingScoring.map { review ->
            PendingScoringRow(
                clientCode = review.clientCode,
                profileID = review.profileID,
                profileName = review.profileName.ifBlank { review.profileID },
                coachBand = review.coachBand,
                weightedTotal = review.weightedTotal,
            )
        }

        val clientCodes = (qRows.map { it.clientCode } + sRows.map { it.clientCode }).distinct()
            .sortedBy { it.lowercase() }

        val items = mutableListOf<ListItem>()
        for (code in clientCodes) {
            items.add(ListItem.ClientHeader(code))
            qRows.filter { it.clientCode == code }
                .sortedWith(
                    compareBy(
                        { UiStrings.extractQuestionnaireNumber(it.questionnaireId) ?: Int.MAX_VALUE },
                        { it.questionnaireId },
                    ),
                )
                .forEach { items.add(ListItem.Questionnaire(it)) }
            sRows.filter { it.clientCode == code }
                .sortedBy { it.profileName.lowercase() }
                .forEach { items.add(ListItem.ScoringReview(it)) }
        }
        items
    }

    private fun formatSummary(questionnaireCount: Int, scoringCount: Int, clientCount: Int): String {
        val template = td(
            "upload_summary",
            "{qn} questionnaires · {scores} score reviews · {clients} clients",
        )
        return template
            .replace("{qn}", questionnaireCount.toString())
            .replace("{scores}", scoringCount.toString())
            .replace("{clients}", clientCount.toString())
    }

    private fun scoringReviewTitle(row: PendingScoringRow): String {
        val template = td("upload_item_scoring_review", "Score review: {profile}")
        return template.replace("{profile}", row.profileName)
    }

    private fun buildScoringSummaryString(row: PendingScoringRow): String {
        val bandLabel = when (row.coachBand) {
            "green" -> td("category_green", "Green")
            "yellow" -> td("category_yellow", "Yellow")
            "red" -> td("category_red", "Red")
            else -> row.coachBand
        }
        val template = td("upload_scoring_review_summary", "Coach: {band} · total {total}")
        return template
            .replace("{band}", bandLabel)
            .replace("{total}", row.weightedTotal.toString())
    }

    private fun openDetailScreen(clientCode: String, questionnaireId: String) {
        overviewActive = false
        overviewLoadJob?.cancel()
        overviewLoadJob = null
        activity.setContentView(R.layout.questionnaire_detail_screen)
        activity.setBackPressInterceptor {
            openOverviewScreen()
            true
        }

        val title = requireView<TextView>(R.id.titleQuestionnaireDetail)
        val recycler = requireView<RecyclerView>(R.id.qaListRecycler)
        val listLoading = requireView<View>(R.id.qaListLoading)
        val emptyView = requireView<TextView>(R.id.emptyViewQA)
        val backButton = requireView<Button>(R.id.backButtonQA)
        detailLangSpinner = requireView(R.id.detailLangSpinner)

        val displayName = UiStrings.questionnaireDisplayName(
            uploadLanguageId,
            questionnaireId,
            nameMap[questionnaireId],
        )
        title.text = "${td("client", "Client")}: $clientCode – $displayName"
        backButton.text = td("previous", "Back")
        emptyView.text = td("no_questions_available", "No questions available.")
        backButton.setOnClickListener { openOverviewScreen() }

        setupLanguageSpinner(detailLangSpinner) {
            val dn = UiStrings.questionnaireDisplayName(
                uploadLanguageId,
                questionnaireId,
                nameMap[questionnaireId],
            )
            title.text = "${td("client", "Client")}: $clientCode – $dn"
            backButton.text = td("previous", "Back")
            reloadDetailList(clientCode, questionnaireId, recycler, listLoading, emptyView)
        }

        val qaAdapter = QaListAdapter()
        recycler.layoutManager = LinearLayoutManager(activity)
        recycler.adapter = qaAdapter

        emptyView.visibility = View.GONE
        reloadDetailList(clientCode, questionnaireId, recycler, listLoading, emptyView)
    }

    private fun reloadDetailList(
        clientCode: String,
        questionnaireId: String,
        recycler: RecyclerView,
        listLoading: View,
        emptyView: TextView,
    ) {
        val adapter = recycler.adapter as? QaListAdapter ?: return
        detailLoadJob?.cancel()
        setListLoading(listLoading, true, recycler, emptyView)
        detailLoadJob = uiScope.launch {
            val cards = withContext(Dispatchers.IO) {
                QuestionnaireTranslationsLoader.applyFor(activity, questionnaireId)
                val questions = MyApp.database.questionDao().getQuestionsForQuestionnaire(questionnaireId)
                val answers = AnswerRepository
                    .getAnswersForClientAndQuestionnaire(clientCode, questionnaireId)
                val filteredAnswerIds = filterDuplicateAliasAnswerIds(
                    questionnaireId,
                    answers.map { it.questionId },
                ).toSet()
                val answerMap = answers
                    .filter { it.questionId in filteredAnswerIds }
                    .associate { it.questionId to it.answerValue }
                questions
                    .filter { it.questionId in filteredAnswerIds }
                    .mapIndexed { idx, q ->
                    QaCard(
                        index = idx + 1,
                        questionText = UiStrings.localizeQuestionLabel(
                            uploadLanguageId,
                            q.questionId,
                            q.question,
                        ),
                        answerText = localizeAnswerForRow(q, answerMap[q.questionId]),
                    )
                }
            }
            if (!isActive) return@launch
            setListLoading(listLoading, false, recycler, emptyView)
            if (cards.isEmpty()) {
                emptyView.visibility = View.VISIBLE
                adapter.submit(emptyList())
            } else {
                emptyView.visibility = View.GONE
                adapter.submit(cards)
            }
        }
    }

    private fun localizeAnswerForRow(q: Question, raw: String?): String {
        val value = raw?.takeIf { it.isNotBlank() } ?: "—"
        val baseId = q.questionId.substringAfterLast('-', q.questionId)
        return UiStrings.localizeAnswerValue(uploadLanguageId, baseId, value)
    }

    private fun filterDuplicateAliasAnswerIds(
        questionnaireId: String,
        questionIds: List<String>,
    ): List<String> {
        val canonicalByAlias = canonicalAnswerRoomIds(questionnaireId)
        if (canonicalByAlias.isEmpty()) return questionIds

        return questionIds.filter { questionId ->
            canonicalByAlias[questionId] == null
        }
    }

    private fun canonicalAnswerRoomIds(questionnaireId: String): Map<String, String> {
        val json = QuestionnaireCache.loadQuestionnaireJson(activity, questionnaireId) ?: return emptyMap()
        val root = runCatching { JsonParser.parseString(json).asJsonObject }.getOrNull() ?: return emptyMap()
        val questions = root.getAsJsonArray("questions") ?: return emptyMap()
        val result = mutableMapOf<String, String>()

        questions.forEach { element ->
            val obj = element.asJsonObject
            val shortId = obj.get("id")?.asString?.takeIf { it.isNotBlank() } ?: return@forEach
            val key = obj.get("question")?.asString?.takeIf { it.isNotBlank() } ?: return@forEach
            if (key == shortId) return@forEach

            result["$questionnaireId-$shortId"] = "$questionnaireId-$key"
        }

        return result
    }

    private fun bindChrome(
        titleTv: TextView,
        subtitleTv: TextView,
        backButton: Button,
        uploadButton: MaterialButton,
    ) {
        titleTv.text = td("start_upload", "Upload Overview")
        subtitleTv.text = td("upload_pending_subtitle", "Pending upload")
        backButton.text = td("cancel", td("previous", "Back"))
        uploadButton.text = td("upload", "Upload")
    }

    private fun setupLanguageSpinner(spinner: Spinner?, onChanged: () -> Unit) {
        spinner ?: return
        fun bindAdapter() {
            val labels = AppLanguages.spinnerLabels(uploadLanguageId)
            val selected = labels.getOrNull(AppLanguages.indexOf(uploadLanguageId))
            ViewUtils.setupLanguagePickerSpinner(activity, spinner, labels, selected)
        }
        bindAdapter()
        spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
                if (suppressLangCallback) return
                val newLang = AppLanguages.ids[position]
                if (newLang == uploadLanguageId) return
                uploadLanguageId = newLang
                AppLanguageStore.set(activity, uploadLanguageId)
                onLanguageChanged(uploadLanguageId)
                suppressLangCallback = true
                bindAdapter()
                spinner.setSelection(AppLanguages.indexOf(uploadLanguageId), false)
                suppressLangCallback = false
                onChanged()
            }
            override fun onNothingSelected(parent: AdapterView<*>?) {}
        }
        suppressLangCallback = true
        spinner.setSelection(AppLanguages.indexOf(uploadLanguageId), false)
        suppressLangCallback = false
    }

    private fun showUploadResultDialog(result: DatabaseUploader.UploadResult) {
        if (result.needsReLogin) {
            activity.requestLogin()
            return
        }
        if (!result.hasErrors) {
            if (result.nothingUploaded) {
                val msgKey = if (result.skipped > 0) "upload_prepare_failed" else "upload_nothing_pending"
                MaterialAlertDialogBuilder(activity)
                    .setTitle(td("upload_failed_title", td("error", "Upload")))
                    .setMessage(
                        td(
                            msgKey,
                            "No questionnaires were uploaded. Complete a questionnaire first, then try again.",
                        ),
                    )
                    .setPositiveButton(td("ok", "OK")) { d, _ ->
                        d.dismiss()
                        openOverviewScreen()
                    }
                    .show()
                return
            }

            val msg = buildUploadSuccessMessage(result) +
                "\n\n" +
                td("upload_success_sync_next", "upload_success_sync_next")
            MaterialAlertDialogBuilder(activity)
                .setTitle(td("upload_success_title", "upload_success_title"))
                .setMessage(msg)
                .setPositiveButton(td("ok", "OK")) { d, _ ->
                    d.dismiss()
                    uploadedDuringSession = true
                    closeOverview()
                }
                .show()
            return
        }

        val sb = StringBuilder()
        if (result.submitted > 0) {
            sb.appendLine("✓ ${result.submitted} ${td("done", "submitted")}")
            sb.appendLine()
        }
        sb.appendLine("✗ ${result.errors.size} ${td("error", "failed")}:")
        result.errors.forEach { err ->
            val name = when {
                err.questionnaireId.isNotBlank() -> err.questionnaireId
                err.profileName.isNotBlank() -> err.profileName
                err.profileID.isNotBlank() -> err.profileID
                else -> err.clientCode
            }
            sb.appendLine()
            sb.append("• $name")
            if (err.clientCode.isNotBlank() && name != err.clientCode) {
                sb.append(" (${err.clientCode})")
            }
            sb.appendLine()
            sb.append("  ")
            sb.appendLine(friendlyErrorMessage(err))
        }

        MaterialAlertDialogBuilder(activity)
            .setTitle(
                if (result.allFailed) "✗ ${td("error", "Upload failed")}"
                else "⚠ ${td("error", "Partial upload")}",
            )
            .setMessage(sb.toString().trimEnd())
            .setPositiveButton(td("ok", "OK")) { d, _ ->
                d.dismiss()
                openOverviewScreen()
            }
            .setNegativeButton(td("cancel", "Back")) { d, _ ->
                d.dismiss()
                closeOverview()
            }
            .show()
    }

    private fun friendlyErrorMessage(err: DatabaseUploader.UploadError): String {
        return when (err.serverCode) {
            "VALIDATION_FAILED" -> err.message.ifBlank {
                td("error_invalid_data", "Some answers are missing or invalid.")
            }
            "NOT_FOUND" -> td("error_not_found", "Client or questionnaire not found on server.")
            "MISSING_FIELDS", "INVALID_BODY", "INVALID_FIELD" ->
                td("error_invalid_data", "The data could not be accepted by the server.")
            else -> when {
                err.message.contains("401") || err.message.contains("403") ->
                    td("login_required", "Session expired. Please log in again.")
                err.message.contains("UnknownHost") || err.message.contains("connect") ->
                    td("offline", "No internet connection.")
                else -> err.message
            }
        }
    }

    private fun td(key: String, fallback: String): String =
        UiStrings.tDisplay(uploadLanguageId, key, fallback)

    private fun setListLoading(overlay: View, loading: Boolean, vararg content: View) {
        ScreenLoading.setVisible(overlay, loading, *content)
    }

    private fun buildUploadSuccessMessage(result: DatabaseUploader.UploadResult): String {
        val parts = mutableListOf<String>()
        if (result.submittedQuestionnaires > 0) {
            val t = td("upload_success_questionnaires", "{count} questionnaire(s)")
            parts += t.replace("{count}", result.submittedQuestionnaires.toString())
        }
        if (result.submittedScoringReviews > 0) {
            val t = td("upload_success_scoring_reviews", "{count} score review(s)")
            parts += t.replace("{count}", result.submittedScoringReviews.toString())
        }
        val base = if (parts.isEmpty()) {
            td("upload_success_message", "Upload complete.")
        } else {
            parts.joinToString(" · ")
        }
        return if (result.revisionNotices.isEmpty()) {
            base
        } else {
            base + "\n\n" + result.revisionNotices.joinToString("\n")
        }
    }

    private class UploadListAdapter(
        private val onRowClick: (PendingRow) -> Unit,
        private val clientHeaderText: (String) -> String,
        private val summaryForCount: (Int) -> String,
        private val scoringSummaryFor: (PendingScoringRow) -> String,
        private val scoringTitleFor: (PendingScoringRow) -> String,
    ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

        private var items: List<ListItem> = emptyList()

        fun submit(list: List<ListItem>) {
            items = list
            notifyDataSetChanged()
        }

        override fun getItemViewType(position: Int): Int = when (items[position]) {
            is ListItem.ClientHeader -> 0
            is ListItem.Questionnaire -> 1
            is ListItem.ScoringReview -> 2
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
            val inflater = LayoutInflater.from(parent.context)
            return when (viewType) {
                0 -> UploadHeaderHolder(inflater.inflate(R.layout.item_upload_client_header, parent, false))
                else -> UploadCardHolder(inflater.inflate(R.layout.item_upload_questionnaire, parent, false))
            }
        }

        override fun getItemCount(): Int = items.size

        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            when (val item = items[position]) {
                is ListItem.ClientHeader -> {
                    val h = holder as UploadHeaderHolder
                    h.text.text = clientHeaderText(item.clientCode)
                }
                is ListItem.Questionnaire -> {
                    val h = holder as UploadCardHolder
                    val row = item.row
                    h.title.text = row.questionnaireName
                    h.summary.text = summaryForCount(row.answeredCount)
                    h.chevron.visibility = View.VISIBLE
                    h.itemView.isClickable = true
                    h.itemView.setOnClickListener { onRowClick(row) }
                }
                is ListItem.ScoringReview -> {
                    val h = holder as UploadCardHolder
                    val row = item.row
                    h.title.text = scoringTitleFor(row)
                    h.summary.text = scoringSummaryFor(row)
                    h.chevron.visibility = View.GONE
                    h.itemView.isClickable = false
                    h.itemView.setOnClickListener(null)
                }
            }
        }

    }

    data class QaCard(val index: Int, val questionText: String, val answerText: String)

    private class QaListAdapter : RecyclerView.Adapter<QaAnswerHolder>() {
        private var cards: List<QaCard> = emptyList()

        fun submit(list: List<QaCard>) {
            cards = list
            notifyDataSetChanged()
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QaAnswerHolder {
            val v = LayoutInflater.from(parent.context)
                .inflate(R.layout.item_qa_answer_card, parent, false)
            return QaAnswerHolder(v)
        }

        override fun getItemCount(): Int = cards.size

        override fun onBindViewHolder(holder: QaAnswerHolder, position: Int) {
            val card = cards[position]
            ViewUtils.styleQuestionTitle(holder.question)
            ViewUtils.styleQuestionValue(holder.answer)
            holder.question.text = "${card.index}. ${card.questionText}"
            holder.answer.text = card.answerText
        }
    }

    private class UploadHeaderHolder(view: View) : RecyclerView.ViewHolder(view) {
        val text: TextView = view.findViewById(R.id.clientHeaderText)
    }

    private class UploadCardHolder(view: View) : RecyclerView.ViewHolder(view) {
        val title: TextView = view.findViewById(R.id.uploadCardTitle)
        val summary: TextView = view.findViewById(R.id.uploadCardSummary)
        val chevron: View = view.findViewById(R.id.uploadCardChevron)
    }

    private class QaAnswerHolder(view: View) : RecyclerView.ViewHolder(view) {
        val question: TextView = view.findViewById(R.id.qaQuestionLabel)
        val answer: TextView = view.findViewById(R.id.qaAnswerValue)
    }

    private fun buildSummaryString(answeredCount: Int): String {
        val doneLabel = td("done", "Completed")
        val questionsLabel = td("questions_filled", "answers")
        return "✓ $doneLabel · $answeredCount $questionsLabel"
    }

    @Suppress("UNCHECKED_CAST")
    private fun <T : View> requireView(id: Int): T {
        val v = activity.findViewById<T>(id)
        if (v == null) {
            val msg = "Missing view: $id"
            Log.e(tag, msg)
            throw IllegalStateException(msg)
        }
        return v
    }
}
