from __future__ import annotations

from collections.abc import Iterable
from dataclasses import dataclass
from html import escape
from typing import Any

from app.llm.schemas import (
    ClinicalStructuredModel,
    DocumentoGenericoStructured,
    DocumentoQuirurgicoStructured,
    FacturaStructured,
    HistoriaClinicaStructured,
    LaboratorioStructured,
    PrefacturaStructured,
    PrescripcionStructured,
    RadiologiaStructured,
    get_clinical_document_model,
)


@dataclass(frozen=True)
class _HtmlCell:
    content: str


def _text(value: Any, default: str = "No especificado") -> str:
    raw = str(value or "").strip()
    return escape(raw or default)


def _list_items(values: list[str]) -> str:
    if not values:
        return "<ol><li>No especificado</li></ol>"
    return "<ol>" + "".join(f"<li>{_text(item)}</li>" for item in values) + "</ol>"


def _paragraph(label: str, value: Any) -> str:
    rendered = _text(value).replace("\n", "<br>")
    return f"<p><b>{escape(label)}</b></p><p>{rendered}</p>"


def _redacted_paragraph(label: str) -> str:
    return (
        f'<p><b>{escape(label)}</b></p><p class="clinical-redacted-value">Dato censurado por protección</p>'
    )


def _is_missing_identity_value(value: Any) -> bool:
    return str(value or "").strip().casefold() in {"", "no especificado", "desconocido"}


def _date_paragraph(label: str, value: Any) -> str:
    return f"<p><b>{escape(label)}</b></p><p>{_render_cell_content(_date_cell(value))}</p>"


def _class_attr(value: str | None) -> str:
    return f' class="{escape(value)}"' if value else ""


def _render_cell_content(value: Any) -> str:
    if isinstance(value, _HtmlCell):
        return value.content
    return _text(value)


def _date_cell(value: Any) -> _HtmlCell | None:
    raw = str(value or "").strip()
    if not raw:
        return None
    escaped = escape(raw)
    return _HtmlCell(f'<time data-local-date="true" datetime="{escaped}">{escaped}</time>')


def _currency(value: Any) -> str | None:
    if value is None:
        return None
    try:
        numeric = float(value)
    except (TypeError, ValueError):
        return None
    decimals = 2 if numeric != int(numeric) else 0
    rendered = f"${numeric:,.{decimals}f}"
    return rendered.replace(",", "_").replace(".", ",").replace("_", ".")


def _render_surgical_cost_calculation(model: FacturaStructured) -> str:
    summary = model.resumen_tarifario_soat
    procedures = [
        item for item in model.servicios_procedimientos.procedimientos_quirurgicos if item.valoracion_soat
    ]
    if summary is None or not procedures:
        return ""
    cards = []
    for item in procedures:
        valuation = item.valoracion_soat
        associated = item.costo_quirurgico_asociado
        if valuation is None:
            continue
        associated_total = _currency(associated.valor_final) if associated else None
        status = associated.estado.value if associated else "sin asociación"
        component_rows = []
        if associated:
            component_rows = [
                [
                    component.componente.title(),
                    "; ".join(component.conceptos_lineas),
                    _currency(component.valor_cobrado),
                    ", ".join(str(index) for index in component.indices_lineas_canonicas),
                    ", ".join(component.codigos_facturacion),
                    component.metodo,
                    component.formula,
                    component.redondeo,
                ]
                for component in associated.componentes
            ]
        procedure_summary = _table(
            [
                "CUPS",
                "Código SOAT",
                "Concepto SOAT oficial",
                "Grupo",
                "Valor directo",
                "Costo asociado",
                "Referencia SOAT",
                "Estado",
            ],
            [
                [
                    item.codigo_cups or item.codigo_referencia,
                    item.codigo_soat or item.codigo_facturacion,
                    item.concepto_soat,
                    item.grupo_quirurgico_soat,
                    _currency(associated.valor_directo) if associated else item.total,
                    associated_total,
                    _currency(valuation.valor_liquidado),
                    status,
                ]
            ],
            wrapper_class="ui-table-wrap--dense-wide",
        )
        details = "".join(
            [
                '<details class="surgical-cost-calculation__details">',
                f"<summary>Ver desglose y evidencia de {_text(item.codigo_cups or item.descripcion)}</summary>",
                _table(
                    [
                        "Componente",
                        "Concepto facturado",
                        "Cobrado",
                        "Líneas",
                        "Códigos",
                        "Método",
                        "Fórmula",
                        "Redondeo",
                    ],
                    component_rows,
                    wrapper_class="ui-table-wrap--dense-wide",
                    empty_message="No hay componentes cobrados asociados.",
                ),
                _paragraph(
                    "Fórmula total", associated.formula if associated else "Sin asociación disponible"
                ),
                _paragraph(
                    "Saldo sin asignar", _currency(associated.saldo_no_asignado) if associated else None
                ),
                _paragraph(
                    "Evidencia y saldos",
                    "; ".join(associated.evidencia)
                    if associated and associated.evidencia
                    else "Sin saldos pendientes registrados.",
                ),
                "</details>",
            ]
        )
        cards.append(
            f'<article class="surgical-cost-calculation__item">{procedure_summary}{details}</article>'
        )
    return "".join(
        [
            '<section class="surgical-cost-calculation" data-surgical-cost-calculation="true" aria-labelledby="surgical-cost-title">',
            '<h3 id="surgical-cost-title">Cálculo de procedimientos quirúrgicos</h3>',
            '<p class="surgical-cost-calculation__notice"><b>Costo cobrado asociado:</b> se obtiene de las líneas facturadas. '
            "La referencia SOAT se muestra por separado y no modifica el total declarado.</p>",
            "".join(cards),
            _table(
                ["Resumen", "Valor"],
                [
                    [
                        "Total quirúrgico asociado",
                        _currency(
                            sum(
                                (
                                    item.costo_quirurgico_asociado.valor_final
                                    for item in procedures
                                    if item.costo_quirurgico_asociado
                                ),
                                0,
                            )
                        ),
                    ],
                    ["Total referencia SOAT", _currency(summary.total_liquidado)],
                    ["Pendientes de revisión", summary.pendientes],
                    ["Versión de catálogo", summary.version_catalogo],
                ],
                wrapper_class="ui-table-wrap--text-heavy",
            ),
            "</section>",
        ]
    )


def _format_historia_medication(item: Any) -> str:
    parts = [item.nombre]
    posologia = item.posologia or " ".join(
        part for part in [item.via, item.frecuencia, item.duracion] if part
    )
    if posologia:
        parts.append(posologia)
    if item.dosis:
        parts.append(item.dosis)
    if item.cantidad:
        parts.append(item.cantidad)
    medicamento = " - ".join(parts)
    return f"{item.codigo} - {medicamento}" if item.codigo else medicamento


def _render_historia_recommendations(model: HistoriaClinicaStructured) -> str:
    if not model.recomendaciones_medicas:
        return ""

    category_labels = {
        "incapacidad": "Incapacidad",
        "tratamiento_cuidado": "Tratamientos y cuidados",
        "restriccion": "Restricciones",
        "seguimiento_rehabilitacion": "Seguimiento y rehabilitación",
        "signo_alarma": "Signos de alarma",
        "otra": "Otras indicaciones",
    }
    grouped: dict[str, list[str]] = {category: [] for category in category_labels}
    for item in model.recomendaciones_medicas:
        main_text = str(item.indicacion or item.evidencia or "").strip()
        if not main_text:
            continue
        details = []
        if item.periodo_duracion:
            details.append(f"Período/duración: {item.periodo_duracion}")
        if item.fecha:
            details.append(f"Fecha: {item.fecha}")
        if item.pagina:
            details.append(f"Página: {item.pagina}")
        if item.evidencia and item.evidencia.casefold() != main_text.casefold():
            details.append(f"Evidencia: {item.evidencia}")
        grouped[item.categoria].append(f"{main_text} ({'; '.join(details)})" if details else main_text)

    formulated = [
        _format_historia_medication(item)
        for item in model.medicamentos
        if str(item.tipo_uso or "").strip().casefold() == "formulado"
    ]
    existing_treatments = {value.casefold() for value in grouped["tratamiento_cuidado"]}
    grouped["tratamiento_cuidado"].extend(
        item for item in formulated if item.casefold() not in existing_treatments
    )

    rendered_categories = [
        f"<p><b>{escape(label)}</b></p>{_list_items(items)}"
        for category, label in category_labels.items()
        if (items := grouped[category])
    ]
    if not rendered_categories:
        return ""
    return "<p><b>Recomendaciones médicas</b></p>" + "".join(rendered_categories)


def _table(
    headers: list[str],
    rows: list[list[Any]],
    *,
    wrapper_class: str = "",
    table_class: str = "",
    column_classes: list[str] | None = None,
    empty_message: str | None = None,
) -> str:
    if not rows:
        if empty_message:
            return (
                f'<div class="ui-table-wrap{(" " + wrapper_class) if wrapper_class else ""}">'
                f'<p class="ui-table-empty">{escape(empty_message)}</p></div>'
            )
        rows = [["No especificado" for _ in headers]]
    column_classes = column_classes or [""] * len(headers)
    thead = (
        "<tr>"
        + "".join(
            f"<th{_class_attr(column_classes[index] if index < len(column_classes) else '')}>{escape(header)}</th>"
            for index, header in enumerate(headers)
        )
        + "</tr>"
    )
    body_rows = []
    for row in rows:
        cells = []
        for index, cell in enumerate(row):
            cell_class = column_classes[index] if index < len(column_classes) else ""
            cells.append(f"<td{_class_attr(cell_class)}>{_render_cell_content(cell)}</td>")
        body_rows.append("<tr>" + "".join(cells) + "</tr>")
    return (
        f'<div class="ui-table-wrap{(" " + wrapper_class) if wrapper_class else ""}">'
        f'<table border="1"{_class_attr(table_class)}><thead>'
        + thead
        + "</thead><tbody>"
        + "".join(body_rows)
        + "</tbody></table></div>"
    )


def render_historia_html(
    model: HistoriaClinicaStructured,
    *,
    redacted_identity_fields: Iterable[str] = (),
) -> str:
    redacted_fields = set(redacted_identity_fields)
    diagnosticos = [
        f"{item.codigo} - {item.descripcion}" if item.codigo else item.descripcion
        for item in model.diagnosticos
    ]
    procedimientos = [
        f"{item.codigo} {item.descripcion}" if item.codigo else item.descripcion
        for item in model.procedimientos
    ]
    procedimientos_antecedentes = [
        f"{item.codigo} {item.descripcion}" if item.codigo else item.descripcion
        for item in model.procedimientos_antecedentes
    ]
    medicamentos = [
        _format_historia_medication(item)
        for item in model.medicamentos
        if str(item.tipo_uso or "").strip().casefold() == "administrado"
    ]
    return "".join(
        [
            _redacted_paragraph("Nombre del paciente")
            if "patient_name" in redacted_fields and _is_missing_identity_value(model.patient_name)
            else _paragraph("Nombre del paciente", model.patient_name),
            _paragraph("Prestador de servicio", model.prestador_servicio),
            _paragraph("Número de caso", model.numero_caso),
            _redacted_paragraph("Datos de identificación del paciente")
            if "patient_id" in redacted_fields and _is_missing_identity_value(model.identificacion_paciente)
            else _paragraph("Datos de identificación del paciente", model.identificacion_paciente),
            _paragraph("Sexo", model.sexo),
            _paragraph("Edad", model.edad),
            _date_paragraph("Fecha de nacimiento", model.fecha_nacimiento),
            _date_paragraph("Fecha de ingreso", model.fecha_ingreso),
            _paragraph("Motivo de consulta", model.motivo_consulta),
            _paragraph("Resumen", model.resumen_clinico),
            _render_historia_recommendations(model),
            f"<p><b>Antecedentes</b></p>{_list_items(model.antecedentes)}",
            f"<p><b>Diagnósticos</b></p>{_list_items(diagnosticos)}",
            f"<p><b>Procedimientos</b></p>{_list_items(procedimientos)}",
            f"<p><b>Procedimientos antecedentes</b></p>{_list_items(procedimientos_antecedentes)}",
            f"<p><b>Medicamentos administrados</b></p>{_list_items(medicamentos)}",
        ]
    )


def render_laboratorio_html(model: LaboratorioStructured) -> str:
    rows = [[item.parametro, item.resultado, item.rango_normal, item.estado] for item in model.resultados]
    return "".join(
        [
            _paragraph("Nombre del paciente", model.patient_name),
            _date_paragraph("Fecha del examen", model.fecha_examen),
            _paragraph("Tipo de examen", model.tipo_examen),
            "<p><b>Resultados principales</b></p>",
            _table(["Parámetro", "Resultado", "Rango normal", "Estado"], rows),
            f"<p><b>Valores alterados</b></p>{_list_items(model.valores_alterados)}",
            _paragraph("Interpretación", model.interpretacion),
            _paragraph("Observaciones", model.observaciones),
        ]
    )


def render_radiologia_html(model: RadiologiaStructured) -> str:
    return "".join(
        [
            _paragraph("Nombre del paciente", model.patient_name),
            _date_paragraph("Fecha del estudio", model.fecha_estudio),
            _paragraph("Tipo de estudio", model.tipo_estudio),
            _paragraph("Región anatómica", model.region_anatomica),
            _paragraph("Técnica", model.tecnica),
            _paragraph("Hallazgos", model.hallazgos),
            _paragraph("Conclusión", model.conclusion),
        ]
    )


def render_prescripcion_html(model: PrescripcionStructured) -> str:
    medicamentos = []
    for item in model.medicamentos:
        parts = [item.nombre]
        if item.dosis:
            parts.append(item.dosis)
        if item.via:
            parts.append(item.via)
        if item.frecuencia:
            parts.append(item.frecuencia)
        if item.cantidad:
            parts.append(item.cantidad)
        medicamentos.append(" - ".join(parts))
    return "".join(
        [
            _paragraph("Nombre del paciente", model.patient_name),
            _date_paragraph("Fecha de prescripción", model.fecha),
            _paragraph("Diagnóstico contexto", model.diagnostico_contexto),
            f"<p><b>Medicamentos formulados</b></p>{_list_items(medicamentos)}",
            _paragraph("Observaciones", model.observaciones),
        ]
    )


def render_quirurgico_html(model: DocumentoQuirurgicoStructured) -> str:
    diagnosticos = [
        f"{item.codigo} - {item.descripcion}" if item.codigo else item.descripcion
        for item in model.diagnosticos
    ]
    procedimientos = [
        f"{item.codigo} {item.descripcion}" if item.codigo else item.descripcion
        for item in model.procedimientos
    ]
    return "".join(
        [
            _paragraph("Nombre del paciente", model.patient_name),
            _paragraph("Resumen", model.resumen_clinico),
            _date_paragraph("1. FECHA DEL PROCEDIMIENTO", model.fecha_procedimiento),
            f"<p><b>3. DIAGNÓSTICOS</b></p>{_list_items(diagnosticos)}",
            f"<p><b>4. PROCEDIMIENTOS REALIZADOS</b></p>{_list_items(procedimientos)}",
            _paragraph("5. HALLAZGOS QUIRÚRGICOS", model.hallazgos),
            _paragraph(
                "6. DESCRIPCIÓN DEL PROCEDIMIENTO",
                model.descripcion_procedimiento or model.procedimiento_principal,
            ),
            _paragraph("Complicaciones", model.complicaciones),
            _paragraph("Conclusión", model.conclusion),
        ]
    )


def render_factura_html(model: FacturaStructured) -> str:
    servicios = model.servicios_procedimientos
    resumen = model.resumen_financiero
    lineas_canonicas = [
        [
            _date_cell(item.fecha_servicio),
            item.codigo_facturacion,
            item.codigo_referencia,
            item.descripcion,
            item.categoria,
            item.cantidad,
            item.valor_unitario,
            item.total,
        ]
        for item in model.lineas_canonicas
    ]
    compact_value_classes = ["ui-table-cell--wrap", "ui-table-cell--wrap"]
    descriptive_column_classes = [
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--wrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
    ]
    canonical_column_classes = [
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--wrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
    ]
    return "".join(
        [
            _paragraph("Nombre del paciente", model.patient_name),
            "<p><b>1. INFORMACIÓN DEL PROVEEDOR</b></p>",
            _table(
                ["Campo", "Valor"],
                [
                    ["Nombre de la institución médica", model.proveedor.nombre_institucion],
                    ["NIT", model.proveedor.nit],
                    ["Dirección", model.proveedor.direccion],
                    ["Ciudad y departamento", model.proveedor.ciudad_departamento],
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=compact_value_classes,
            ),
            "<p><b>2. INFORMACIÓN DE LA FACTURA</b></p>",
            _table(
                ["Campo", "Valor"],
                [
                    ["Número de factura", model.datos_factura.numero_factura],
                    ["Prefijo", model.datos_factura.prefijo],
                    ["Fecha de emisión", _date_cell(model.datos_factura.fecha_emision)],
                    ["Número de caso", model.datos_factura.numero_caso],
                    ["Fecha de vencimiento", _date_cell(model.datos_factura.fecha_vencimiento)],
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=compact_value_classes,
            ),
            "<p><b>3. INFORMACIÓN DEL PAGADOR</b></p>",
            _table(
                ["Campo", "Valor"],
                [
                    ["Aseguradora/EPS", model.pagador.aseguradora_eps],
                    ["NIT del pagador", model.pagador.nit_pagador],
                    ["Tipo de convenio", model.pagador.tipo_convenio],
                    ["Número de autorización", model.pagador.numero_autorizacion],
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=compact_value_classes,
            ),
            "<p><b>4. INFORMACIÓN DEL PACIENTE</b></p>",
            _table(
                ["Campo", "Valor"],
                [
                    ["Nombre completo", model.paciente.nombre_completo or model.patient_name],
                    ["Número de identificación (CC)", model.paciente.numero_identificacion],
                    ["Fecha de ingreso", _date_cell(model.paciente.fecha_ingreso)],
                    ["Fecha de egreso", _date_cell(model.paciente.fecha_egreso)],
                    ["Estancia hospitalaria", model.paciente.estancia_hospitalaria],
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=compact_value_classes,
            ),
            "<p><b>5. SERVICIOS Y PROCEDIMIENTOS MÉDICOS</b></p>",
            "<p><b>Líneas facturadas canónicas</b></p>",
            _table(
                [
                    "Fecha",
                    "Código facturación",
                    "Código referencia",
                    "Descripción",
                    "Categoría",
                    "Cantidad",
                    "Valor unitario",
                    "Total",
                ],
                lineas_canonicas,
                wrapper_class="ui-table-wrap--dense-wide",
                table_class="ui-table ui-table--invoice-canonical",
                column_classes=canonical_column_classes,
                empty_message="No se pudieron estructurar líneas facturadas.",
            ),
            "<p><b>Procedimientos quirúrgicos</b></p>",
            _table(
                ["Concepto", "Código CUPS", "Descripción", "Cantidad", "Valor unitario", "Total"],
                [
                    [
                        item.concepto,
                        item.codigo_cups,
                        item.descripcion,
                        item.cantidad,
                        item.valor_unitario,
                        item.total,
                    ]
                    for item in servicios.procedimientos_quirurgicos
                ],
                wrapper_class="ui-table-wrap--dense-wide",
                column_classes=descriptive_column_classes + ["ui-table-cell--nowrap"],
                empty_message="Sin procedimientos quirúrgicos detectados.",
            ),
            _render_surgical_cost_calculation(model),
            "<p><b>Procedimientos no quirúrgicos</b></p>",
            _table(
                ["Código CUPS", "Nombre del ítem", "Cantidad", "Valor unitario", "Total"],
                [
                    [
                        item.codigo_cups,
                        item.descripcion or item.concepto,
                        item.cantidad,
                        item.valor_unitario,
                        item.total,
                    ]
                    for item in servicios.procedimientos_no_quirurgicos
                ],
                wrapper_class="ui-table-wrap--dense-wide",
                empty_message="Sin procedimientos no quirúrgicos detectados.",
            ),
            "<p><b>Exámenes de laboratorio</b></p>",
            _table(
                ["Prueba", "Descripción", "Cantidad", "Valor unitario", "Total"],
                [
                    [item.prueba, item.descripcion, item.cantidad, item.valor_unitario, item.total]
                    for item in servicios.examenes_laboratorio
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=[
                    "ui-table-cell--nowrap",
                    "ui-table-cell--wrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                ],
                empty_message="Sin exámenes de laboratorio detectados.",
            ),
            "<p><b>Imagenología</b></p>",
            _table(
                ["Estudio", "Descripción", "Cantidad", "Valor unitario", "Total"],
                [
                    [item.estudio, item.descripcion, item.cantidad, item.valor_unitario, item.total]
                    for item in servicios.imagenologia
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=[
                    "ui-table-cell--nowrap",
                    "ui-table-cell--wrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                ],
                empty_message="Sin estudios de imagen detectados.",
            ),
            "<p><b>Hospitalización</b></p>",
            _table(
                ["Habitación", "Días", "Tarifa", "Total"],
                [[item.habitacion, item.dias, item.tarifa, item.total] for item in servicios.hospitalizacion],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=[
                    "ui-table-cell--wrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                ],
                empty_message="Sin cargos de hospitalización detectados.",
            ),
            "<p><b>Honorarios médicos</b></p>",
            _table(
                ["Rol", "Profesional", "Cantidad", "Valor unitario", "Total"],
                [
                    [item.rol, item.profesional, item.cantidad, item.valor_unitario, item.total]
                    for item in servicios.honorarios_medicos
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=[
                    "ui-table-cell--nowrap",
                    "ui-table-cell--wrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                ],
                empty_message="Sin honorarios médicos detectados.",
            ),
            "<p><b>Medicamentos</b></p>",
            _table(
                ["Medicamento", "Dosis", "Cantidad", "Valor unitario", "Total"],
                [
                    [item.medicamento, item.dosis, item.cantidad, item.valor_unitario, item.total]
                    for item in servicios.medicamentos
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=[
                    "ui-table-cell--wrap",
                    "ui-table-cell--wrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                ],
                empty_message="Sin medicamentos detectados.",
            ),
            "<p><b>Otros servicios</b></p>",
            _table(
                [
                    "Código facturación",
                    "Código referencia",
                    "Descripción",
                    "Cantidad",
                    "Valor unitario",
                    "Total",
                ],
                [
                    [
                        item.codigo_facturacion or item.concepto,
                        item.codigo_referencia or item.codigo_cups,
                        item.descripcion,
                        item.cantidad,
                        item.valor_unitario,
                        item.total,
                    ]
                    for item in servicios.otros_servicios
                ],
                wrapper_class="ui-table-wrap--dense-wide",
                column_classes=descriptive_column_classes + ["ui-table-cell--nowrap"],
                empty_message="Sin otros servicios detectados.",
            ),
            "<p><b>6. ANÁLISIS FINANCIERO</b></p>",
            _table(
                ["Concepto", "Valor"],
                [
                    ["Total de servicios", resumen.total_servicios],
                    ["Descuentos", resumen.descuentos],
                    ["Copagos", resumen.copagos],
                    ["Valor total de la factura", resumen.valor_total_factura],
                    ["Valor en letras", resumen.valor_en_letras],
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=compact_value_classes,
            ),
            _paragraph("7. OBSERVACIONES IMPORTANTES", resumen.observaciones_importantes),
        ]
    )


def render_prefactura_html(model: PrefacturaStructured) -> str:
    rows = [[item.codigo, item.descripcion, item.cantidad, item.valor] for item in model.servicios]
    return "".join(
        [
            _paragraph("Nombre del paciente", model.paciente_nombre or model.patient_name),
            _paragraph("Número de caso", model.numero_caso),
            _paragraph("Prestador", model.prestador),
            _paragraph("Identificación del paciente", model.paciente_identificacion),
            "<p><b>Servicios incluidos en prefactura</b></p>",
            _table(["Código", "Descripción", "Cantidad", "Valor"], rows),
            _paragraph("Valor estimado", model.valor_estimado),
            _paragraph("Observaciones", model.observaciones),
        ]
    )


def render_generico_html(model: DocumentoGenericoStructured) -> str:
    referencias = [
        f"{item.tipo}: {item.nombre} - {item.concepto}" if item.concepto else f"{item.tipo}: {item.nombre}"
        for item in model.referencias_diagnosticas
    ]
    return "".join(
        [
            _paragraph("Nombre del paciente", model.patient_name),
            _paragraph("Título", model.titulo or model.tipo_inferido),
            _paragraph("Resumen", model.resumen),
            f"<p><b>Referencias diagnósticas</b></p>{_list_items(referencias)}",
            _paragraph("Observaciones", model.observaciones),
        ]
    )


def render_clinical_document_html(
    model: ClinicalStructuredModel,
    *,
    redacted_identity_fields: Iterable[str] = (),
) -> str:
    if isinstance(model, HistoriaClinicaStructured):
        return render_historia_html(model, redacted_identity_fields=redacted_identity_fields)
    if isinstance(model, LaboratorioStructured):
        return render_laboratorio_html(model)
    if isinstance(model, RadiologiaStructured):
        return render_radiologia_html(model)
    if isinstance(model, PrescripcionStructured):
        return render_prescripcion_html(model)
    if isinstance(model, DocumentoQuirurgicoStructured):
        return render_quirurgico_html(model)
    if isinstance(model, FacturaStructured):
        return render_factura_html(model)
    if isinstance(model, PrefacturaStructured):
        return render_prefactura_html(model)
    if isinstance(model, DocumentoGenericoStructured):
        return render_generico_html(model)
    raise TypeError(f"Modelo clínico no soportado: {type(model)!r}")


def render_clinical_document_html_from_payload(document_type: str, payload: dict[str, Any]) -> str:
    model_cls = get_clinical_document_model(document_type)
    model = model_cls.model_validate(payload)
    return render_clinical_document_html(model)
