# #region DatasetReviewDependencies [C:2] [TYPE Module] [SEMANTICS dataset, review, dependency, di, serialize]
# @BRIEF Dependency injection, serialization helpers, and feature-flag guards for dataset review endpoints.
# @LAYER API

from __future__ import annotations

from datetime import UTC, datetime
from typing import Any

from fastapi import Depends, Header, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session

from src.core.database import get_db
from src.core.logger import belief_scope, logger
from src.dependencies import (
    get_config_manager,
    get_task_manager,
)
from src.models.dataset_review import (
    AnswerKind,
    ArtifactFormat,
    CandidateStatus,
    DatasetReviewSession,
    FieldProvenance,
    SessionStatus,
)
from src.schemas.dataset_review import (
    ClarificationQuestionDto,
    CompiledPreviewDto,
    DatasetRunContextDto,
    ExecutionMappingDto,
    SemanticFieldEntryDto,
    SessionDetail,
    SessionSummary,
    ValidationFindingDto,
)
from src.services.dataset_review.clarification_engine import (
    ClarificationEngine,
)
from src.services.dataset_review.orchestrator import (
    DatasetReviewOrchestrator,
)
from src.services.dataset_review.repositories.session_repository import (
    DatasetReviewSessionRepository,
    DatasetReviewSessionVersionConflictError,
)


# #region StartSessionRequest [C:1] [TYPE Class]
# @BRIEF Request DTO for starting one dataset review session.
class StartSessionRequest(BaseModel):
    source_kind: str = Field(..., pattern="^(superset_link|dataset_selection)$")
    source_input: str = Field(..., min_length=1)
    environment_id: str = Field(..., min_length=1)


# #endregion StartSessionRequest


# #region UpdateSessionRequest [C:1] [TYPE Class]
# @BRIEF Request DTO for lifecycle state updates on an existing session.
class UpdateSessionRequest(BaseModel):
    status: SessionStatus
    note: str | None = None


# #endregion UpdateSessionRequest


# #region SessionCollectionResponse [C:1] [TYPE Class]
# @BRIEF Paginated session collection response.
class SessionCollectionResponse(BaseModel):
    items: list[SessionSummary]
    total: int
    page: int
    page_size: int
    has_next: bool


# #endregion SessionCollectionResponse


# #region ExportArtifactResponse [C:1] [TYPE Class]
# @BRIEF Inline export response for documentation or validation outputs.
class ExportArtifactResponse(BaseModel):
    artifact_id: str
    session_id: str
    artifact_type: str
    format: str
    storage_ref: str
    created_by_user_id: str
    created_at: str | None = None
    content: dict[str, Any]


# #endregion ExportArtifactResponse


# #region FieldSemanticUpdateRequest [C:1] [TYPE Class]
# @BRIEF Request DTO for field-level semantic candidate acceptance or manual override.
class FieldSemanticUpdateRequest(BaseModel):
    candidate_id: str | None = None
    verbose_name: str | None = None
    description: str | None = None
    display_format: str | None = None
    lock_field: bool = False
    resolution_note: str | None = None


# #endregion FieldSemanticUpdateRequest


# #region FeedbackRequest [C:1] [TYPE Class]
# @BRIEF Request DTO for thumbs up/down feedback.
class FeedbackRequest(BaseModel):
    feedback: str = Field(..., pattern="^(up|down)$")


# #endregion FeedbackRequest


# #region ClarificationAnswerRequest [C:1] [TYPE Class]
# @BRIEF Request DTO for submitting one clarification answer.
class ClarificationAnswerRequest(BaseModel):
    question_id: str = Field(..., min_length=1)
    answer_kind: AnswerKind
    answer_value: str | None = None


# #endregion ClarificationAnswerRequest


# #region ClarificationSessionSummaryResponse [C:1] [TYPE Class]
# @BRIEF Summary DTO for current clarification session state.
class ClarificationSessionSummaryResponse(BaseModel):
    clarification_session_id: str
    session_id: str
    status: str
    current_question_id: str | None = None
    resolved_count: int
    remaining_count: int
    summary_delta: str | None = None


# #endregion ClarificationSessionSummaryResponse


# #region ClarificationStateResponse [C:1] [TYPE Class]
# @BRIEF Response DTO for current clarification state and active question payload.
class ClarificationStateResponse(BaseModel):
    clarification_session: ClarificationSessionSummaryResponse | None = None
    current_question: ClarificationQuestionDto | None = None


# #endregion ClarificationStateResponse


# #region ClarificationAnswerResultResponse [C:1] [TYPE Class]
# @BRIEF Response DTO for one clarification answer mutation result.
class ClarificationAnswerResultResponse(BaseModel):
    clarification_state: ClarificationStateResponse
    session: SessionSummary
    changed_findings: list[ValidationFindingDto]


# #endregion ClarificationAnswerResultResponse


# #region FeedbackResponse [C:1] [TYPE Class]
# @BRIEF Minimal response DTO for persisted AI feedback actions.
class FeedbackResponse(BaseModel):
    target_id: str
    feedback: str


# #endregion FeedbackResponse


# #region ApproveMappingRequest [C:1] [TYPE Class]
# @BRIEF Optional request DTO for explicit mapping approval audit notes.
class ApproveMappingRequest(BaseModel):
    approval_note: str | None = None


# #endregion ApproveMappingRequest


# #region BatchApproveSemanticItemRequest [C:1] [TYPE Class]
# @BRIEF Request DTO for one batch semantic-approval item.
class BatchApproveSemanticItemRequest(BaseModel):
    field_id: str = Field(..., min_length=1)
    candidate_id: str = Field(..., min_length=1)
    lock_field: bool = False


# #endregion BatchApproveSemanticItemRequest


# #region BatchApproveSemanticRequest [C:1] [TYPE Class]
# @BRIEF Request DTO for explicit batch semantic approvals.
class BatchApproveSemanticRequest(BaseModel):
    items: list[BatchApproveSemanticItemRequest] = Field(..., min_length=1)


# #endregion BatchApproveSemanticRequest


# #region BatchApproveMappingRequest [C:1] [TYPE Class]
# @BRIEF Request DTO for explicit batch mapping approvals.
class BatchApproveMappingRequest(BaseModel):
    mapping_ids: list[str] = Field(..., min_length=1)
    approval_note: str | None = None


# #endregion BatchApproveMappingRequest


# #region PreviewEnqueueResultResponse [C:1] [TYPE Class]
# @BRIEF Async preview trigger response exposing only enqueue state.
class PreviewEnqueueResultResponse(BaseModel):
    session_id: str
    session_version: int | None = None
    preview_status: str
    task_id: str | None = None


# #endregion PreviewEnqueueResultResponse


# #region MappingCollectionResponse [C:1] [TYPE Class]
# @BRIEF Wrapper for execution mapping list responses.
class MappingCollectionResponse(BaseModel):
    items: list[ExecutionMappingDto]


# #endregion MappingCollectionResponse


# #region UpdateExecutionMappingRequest [C:1] [TYPE Class]
# @BRIEF Request DTO for one manual execution-mapping override update.
class UpdateExecutionMappingRequest(BaseModel):
    effective_value: Any | None = None
    mapping_method: str | None = Field(default=None, pattern="^(manual_override|direct_match|heuristic_match|semantic_match)$")
    transformation_note: str | None = None


# #endregion UpdateExecutionMappingRequest


# #region LaunchDatasetResponse [C:1] [TYPE Class]
# @BRIEF Launch result exposing audited run context and SQL Lab redirect target.
class LaunchDatasetResponse(BaseModel):
    run_context: DatasetRunContextDto
    redirect_url: str


# #endregion LaunchDatasetResponse


# --- Dependency Injection ---

# #region _require_auto_review_flag [C:2] [TYPE Function]
# @BRIEF Guard US1 dataset review endpoints behind the configured feature flag.
def _require_auto_review_flag(config_manager=Depends(get_config_manager)) -> bool:
    with belief_scope("dataset_review.require_auto_review_flag"):
        settings = config_manager.get_config().settings
        if not settings.features.dataset_review:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset review feature is disabled")
        if not settings.ff_dataset_auto_review:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset auto review feature is disabled")
        return True


# #endregion _require_auto_review_flag


# #region _require_clarification_flag [C:2] [TYPE Function]
# @BRIEF Guard clarification-specific US2 endpoints behind the configured feature flag.
def _require_clarification_flag(config_manager=Depends(get_config_manager)) -> bool:
    with belief_scope("dataset_review.require_clarification_flag"):
        settings = config_manager.get_config().settings
        if not settings.features.dataset_review:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset review feature is disabled")
        if not settings.ff_dataset_clarification:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset clarification feature is disabled")
        return True


# #endregion _require_clarification_flag


# #region _require_execution_flag [C:2] [TYPE Function]
# @BRIEF Guard US3 execution endpoints behind the configured feature flag.
def _require_execution_flag(config_manager=Depends(get_config_manager)) -> bool:
    with belief_scope("dataset_review.require_execution_flag"):
        settings = config_manager.get_config().settings
        if not settings.features.dataset_review:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset review feature is disabled")
        if not settings.ff_dataset_execution:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dataset execution feature is disabled")
        return True


# #endregion _require_execution_flag


# #region _get_repository [C:1] [TYPE Function]
# @BRIEF Build repository dependency.
def _get_repository(db: Session = Depends(get_db)) -> DatasetReviewSessionRepository:
    return DatasetReviewSessionRepository(db)


# #endregion _get_repository


# #region _get_orchestrator [C:1] [TYPE Function]
# @BRIEF Build orchestrator dependency.
def _get_orchestrator(
    repository: DatasetReviewSessionRepository = Depends(_get_repository),
    config_manager=Depends(get_config_manager),
    task_manager=Depends(get_task_manager),
) -> DatasetReviewOrchestrator:
    return DatasetReviewOrchestrator(repository=repository, config_manager=config_manager, task_manager=task_manager)


# #endregion _get_orchestrator


# #region _get_clarification_engine [C:1] [TYPE Function]
# @BRIEF Build clarification engine dependency.
def _get_clarification_engine(
    repository: DatasetReviewSessionRepository = Depends(_get_repository),
) -> ClarificationEngine:
    return ClarificationEngine(repository=repository)


# #endregion _get_clarification_engine


# --- Serialization Helpers ---

# #region _serialize_session_summary [C:1] [TYPE Function]
# @BRIEF Map session aggregate into stable API summary DTO.
def _serialize_session_summary(session: DatasetReviewSession) -> SessionSummary:
    summary = SessionSummary.model_validate(session, from_attributes=True)
    summary.session_version = summary.version
    return summary


# #endregion _serialize_session_summary


# #region _serialize_session_detail [C:1] [TYPE Function]
# @BRIEF Map session aggregate into stable API detail DTO.
def _serialize_session_detail(session: DatasetReviewSession) -> SessionDetail:
    detail = SessionDetail.model_validate(session, from_attributes=True)
    detail.session_version = detail.version
    return detail


# #endregion _serialize_session_detail


# #region _require_session_version_header [C:1] [TYPE Function]
# @BRIEF Read the optimistic-lock session version header.
def _require_session_version_header(
    session_version: int = Header(..., alias="X-Session-Version", ge=0),
) -> int:
    return session_version


# #endregion _require_session_version_header


# #region _build_session_version_conflict_http_exception [C:1] [TYPE Function]
# @BRIEF Normalize optimistic-lock conflict errors into HTTP 409 responses.
def _build_session_version_conflict_http_exception(exc: DatasetReviewSessionVersionConflictError) -> HTTPException:
    return HTTPException(
        status_code=status.HTTP_409_CONFLICT,
        detail={"error_code": "session_version_conflict", "message": str(exc), "session_id": exc.session_id, "expected_version": exc.expected_version, "actual_version": exc.actual_version},
    )


# #endregion _build_session_version_conflict_http_exception


# #region _enforce_session_version [C:3] [TYPE Function]
# @BRIEF Convert repository optimistic-lock conflicts into deterministic HTTP 409 responses.
def _enforce_session_version(repository, session, expected_version):
    with belief_scope("_enforce_session_version"):
        try:
            repository.require_session_version(session, expected_version)
        except DatasetReviewSessionVersionConflictError as exc:
            logger.explore("Dataset review optimistic-lock conflict detected", extra={"session_id": exc.session_id, "expected_version": exc.expected_version, "actual_version": exc.actual_version})
            raise _build_session_version_conflict_http_exception(exc) from exc
        return session


# #endregion _enforce_session_version


# #region _get_owned_session_or_404 [C:3] [TYPE Function]
# @BRIEF Resolve one session for current user or collaborator scope, returning 404 when inaccessible.
def _get_owned_session_or_404(repository, session_id, current_user):
    with belief_scope("_get_owned_session_or_404"):
        session = repository.load_session_detail(session_id, current_user.id)
        if session is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Session not found")
        return session


# #endregion _get_owned_session_or_404


# #region _require_owner_mutation_scope [C:2] [TYPE Function]
# @BRIEF Enforce owner-only mutation scope.
def _require_owner_mutation_scope(session, current_user):
    with belief_scope("_require_owner_mutation_scope"):
        if session.user_id != current_user.id:
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Only the owner can mutate dataset review state")
        return session


# #endregion _require_owner_mutation_scope


# #region _prepare_owned_session_mutation [C:3] [TYPE Function]
# @BRIEF Resolve owner-scoped mutation session and enforce optimistic-lock version.
def _prepare_owned_session_mutation(repository, session_id, current_user, expected_version):
    with belief_scope("_prepare_owned_session_mutation"):
        session = _get_owned_session_or_404(repository, session_id, current_user)
        _require_owner_mutation_scope(session, current_user)
        return _enforce_session_version(repository, session, expected_version)


# #endregion _prepare_owned_session_mutation


# #region _commit_owned_session_mutation [C:3] [TYPE Function]
# @BRIEF Centralize session version bumping and commit semantics.
def _commit_owned_session_mutation(repository, session, *, refresh_targets=None):
    with belief_scope("_commit_owned_session_mutation"):
        try:
            repository.commit_session_mutation(session, refresh_targets=refresh_targets)
        except DatasetReviewSessionVersionConflictError as exc:
            raise _build_session_version_conflict_http_exception(exc) from exc
        return session


# #endregion _commit_owned_session_mutation


# #region _record_session_event [C:1] [TYPE Function]
# @BRIEF Persist one explicit audit event for an owned mutation endpoint.
def _record_session_event(repository, session, current_user, *, event_type, event_summary, event_details=None):
    repository.event_logger.log_for_session(session, actor_user_id=current_user.id, event_type=event_type, event_summary=event_summary, event_details=event_details or {})


# #endregion _record_session_event


# #region _serialize_semantic_field [C:1] [TYPE Function]
# @BRIEF Map one semantic field into stable DTO.
def _serialize_semantic_field(field):
    payload = SemanticFieldEntryDto.model_validate(field, from_attributes=True)
    session_ref = getattr(field, "session", None)
    version_value = getattr(session_ref, "version", None)
    payload.session_version = int(version_value or 0) if version_value is not None else None
    return payload


# #endregion _serialize_semantic_field


# #region _serialize_execution_mapping [C:1] [TYPE Function]
# @BRIEF Map one execution mapping into stable DTO.
def _serialize_execution_mapping(mapping):
    payload = ExecutionMappingDto.model_validate(mapping, from_attributes=True)
    session_ref = getattr(mapping, "session", None)
    version_value = getattr(session_ref, "version", None)
    payload.session_version = int(version_value or 0) if version_value is not None else None
    return payload


# #endregion _serialize_execution_mapping


# #region _serialize_preview [C:1] [TYPE Function]
# @BRIEF Map one preview into stable DTO.
def _serialize_preview(preview, *, session_version_fallback=None):
    payload = CompiledPreviewDto.model_validate(preview, from_attributes=True)
    session_ref = getattr(preview, "session", None)
    version_value = getattr(session_ref, "version", None)
    if version_value is None:
        version_value = session_version_fallback
    payload.session_version = int(version_value or 0) if version_value is not None else None
    return payload


# #endregion _serialize_preview


# #region _serialize_run_context [C:1] [TYPE Function]
# @BRIEF Map one run context into stable DTO.
def _serialize_run_context(run_context):
    payload = DatasetRunContextDto.model_validate(run_context, from_attributes=True)
    session_ref = getattr(run_context, "session", None)
    version_value = getattr(session_ref, "version", None)
    payload.session_version = int(version_value or 0) if version_value is not None else None
    return payload


# #endregion _serialize_run_context


# #region _serialize_clarification_question_payload [C:1] [TYPE Function]
# @BRIEF Convert clarification engine payload into API DTO.
def _serialize_clarification_question_payload(payload):
    if payload is None:
        return None
    return ClarificationQuestionDto.model_validate({
        "question_id": payload.question_id, "clarification_session_id": payload.clarification_session_id,
        "topic_ref": payload.topic_ref, "question_text": payload.question_text,
        "why_it_matters": payload.why_it_matters, "current_guess": payload.current_guess,
        "priority": payload.priority, "state": payload.state, "options": payload.options,
        "answer": None,         "created_at": datetime.now(UTC), "updated_at": datetime.now(UTC),
    })


# #endregion _serialize_clarification_question_payload


# #region _serialize_clarification_state [C:1] [TYPE Function]
# @BRIEF Convert clarification engine state into API response.
def _serialize_clarification_state(state):
    return ClarificationStateResponse(
        clarification_session=ClarificationSessionSummaryResponse(
            clarification_session_id=state.clarification_session.clarification_session_id,
            session_id=state.clarification_session.session_id, status=state.clarification_session.status.value,
            current_question_id=state.clarification_session.current_question_id,
            resolved_count=state.clarification_session.resolved_count,
            remaining_count=state.clarification_session.remaining_count,
            summary_delta=state.clarification_session.summary_delta,
        ),
        current_question=_serialize_clarification_question_payload(state.current_question),
    )


# #endregion _serialize_clarification_state


# #region _serialize_empty_clarification_state [C:1] [TYPE Function]
# @BRIEF Return empty clarification payload.
def _serialize_empty_clarification_state():
    return ClarificationStateResponse(clarification_session=None, current_question=None)


# #endregion _serialize_empty_clarification_state


# #region _get_latest_clarification_session_or_404 [C:1] [TYPE Function]
# @BRIEF Resolve the latest clarification aggregate or raise.
def _get_latest_clarification_session_or_404(session):
    if not session.clarification_sessions:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Clarification session not found")
    return sorted(session.clarification_sessions, key=lambda item: (item.started_at, item.clarification_session_id), reverse=True)[0]


# #endregion _get_latest_clarification_session_or_404


# #region _get_owned_mapping_or_404 [C:2] [TYPE Function]
# @BRIEF Resolve one execution mapping inside one owned session.
def _get_owned_mapping_or_404(session, mapping_id):
    for mapping in session.execution_mappings:
        if mapping.mapping_id == mapping_id:
            return mapping
    raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Execution mapping not found")


# #endregion _get_owned_mapping_or_404


# #region _get_owned_field_or_404 [C:2] [TYPE Function]
# @BRIEF Resolve a semantic field inside one owned session.
def _get_owned_field_or_404(session, field_id):
    for field in session.semantic_fields:
        if field.field_id == field_id:
            return field
    raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Semantic field not found")


# #endregion _get_owned_field_or_404


# #region _map_candidate_provenance [C:1] [TYPE Function]
# @BRIEF Translate accepted semantic candidate type into stable field provenance.
def _map_candidate_provenance(candidate):
    if str(candidate.match_type.value) == "exact":
        return FieldProvenance.DICTIONARY_EXACT
    if str(candidate.match_type.value) == "reference":
        return FieldProvenance.REFERENCE_IMPORTED
    if str(candidate.match_type.value) == "generated":
        return FieldProvenance.AI_GENERATED
    return FieldProvenance.FUZZY_INFERRED


# #endregion _map_candidate_provenance


# #region _resolve_candidate_source_version [C:1] [TYPE Function]
# @BRIEF Resolve the semantic source version for one accepted candidate.
def _resolve_candidate_source_version(field, source_id):
    if not source_id:
        return None
    session = getattr(field, "session", None)
    if session is None:
        return None
    for source in getattr(session, "semantic_sources", []) or []:
        if source.source_id == source_id:
            return source.source_version
    return None


# #endregion _resolve_candidate_source_version


# #region _update_semantic_field_state [C:3] [TYPE Function]
# @BRIEF Apply field-level semantic manual override or candidate acceptance.
# @POST  Manual overrides always set manual provenance plus lock.
def _update_semantic_field_state(field, request, changed_by):
    has_manual_override = any(v is not None for v in [request.verbose_name, request.description, request.display_format])
    selected_candidate = None
    if request.candidate_id:
        selected_candidate = next((c for c in field.candidates if c.candidate_id == request.candidate_id), None)
        if selected_candidate is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Semantic candidate not found")

    if has_manual_override:
        field.verbose_name = request.verbose_name
        field.description = request.description
        field.display_format = request.display_format
        field.provenance = FieldProvenance.MANUAL_OVERRIDE
        field.source_id = None
        field.source_version = None
        field.confidence_rank = None
        field.is_locked = True
        field.has_conflict = False
        field.needs_review = False
        field.last_changed_by = changed_by
        for c in field.candidates:
            c.status = CandidateStatus.SUPERSEDED
        return field

    if selected_candidate is not None:
        field.verbose_name = selected_candidate.proposed_verbose_name
        field.description = selected_candidate.proposed_description
        field.display_format = selected_candidate.proposed_display_format
        field.provenance = _map_candidate_provenance(selected_candidate)
        field.source_id = selected_candidate.source_id
        field.source_version = _resolve_candidate_source_version(field, selected_candidate.source_id)
        field.confidence_rank = selected_candidate.candidate_rank
        field.is_locked = bool(request.lock_field or field.is_locked)
        field.has_conflict = len(field.candidates) > 1
        field.needs_review = False
        field.last_changed_by = changed_by
        for c in field.candidates:
            c.status = CandidateStatus.ACCEPTED if c.candidate_id == selected_candidate.candidate_id else CandidateStatus.SUPERSEDED
        return field

    raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Provide candidate_id or at least one manual override field")


# #endregion _update_semantic_field_state


# #region _build_sql_lab_redirect_url [C:1] [TYPE Function]
# @BRIEF Build SQL Lab redirect URL.
def _build_sql_lab_redirect_url(environment_url, sql_lab_session_ref):
    base_url = str(environment_url or "").rstrip("/")
    session_ref = str(sql_lab_session_ref or "").strip()
    if not base_url:
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Superset environment URL is not configured")
    if not session_ref:
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="SQL Lab session reference is missing")
    return f"{base_url}/superset/sqllab?queryId={session_ref}"


# #endregion _build_sql_lab_redirect_url


# #region _build_documentation_export [C:2] [TYPE Function]
# @BRIEF Produce session documentation export content.
def _build_documentation_export(session, export_format):
    profile = session.profile
    findings = sorted(session.findings, key=lambda item: (item.severity.value, item.code))
    if export_format == ArtifactFormat.MARKDOWN:
        lines = [f"# Dataset Review: {session.dataset_ref}", "", f"- Session ID: {session.session_id}", f"- Environment: {session.environment_id}", f"- Readiness: {session.readiness_state.value}", f"- Recommended action: {session.recommended_action.value}", "", "## Business Summary", profile.business_summary if profile else "No profile summary available.", "", "## Findings"]
        if findings:
            for f in findings:
                lines.append(f"- [{f.severity.value}] {f.title}: {f.message}")
        else:
            lines.append("- No findings recorded.")
        return {"storage_ref": f"inline://dataset-review/{session.session_id}/documentation.md", "content": {"markdown": "\n".join(lines)}}
    content = {"session": _serialize_session_summary(session).model_dump(mode="json"), "profile": profile and {"dataset_name": profile.dataset_name, "business_summary": profile.business_summary, "confidence_state": profile.confidence_state.value, "dataset_type": profile.dataset_type}, "findings": [{"code": f.code, "severity": f.severity.value, "title": f.title, "message": f.message, "resolution_state": f.resolution_state.value} for f in findings]}
    return {"storage_ref": f"inline://dataset-review/{session.session_id}/documentation.json", "content": content}


# #endregion _build_documentation_export


# #region _build_validation_export [C:2] [TYPE Function]
# @BRIEF Produce validation-focused export content.
def _build_validation_export(session, export_format):
    findings = sorted(session.findings, key=lambda item: (item.severity.value, item.code))
    if export_format == ArtifactFormat.MARKDOWN:
        lines = [f"# Validation Report: {session.dataset_ref}", "", f"- Session ID: {session.session_id}", f"- Readiness: {session.readiness_state.value}", "", "## Findings"]
        if findings:
            for f in findings:
                lines.append(f"- `{f.code}` [{f.severity.value}] {f.message}")
        else:
            lines.append("- No findings recorded.")
        return {"storage_ref": f"inline://dataset-review/{session.session_id}/validation.md", "content": {"markdown": "\n".join(lines)}}
    content = {"session_id": session.session_id, "dataset_ref": session.dataset_ref, "readiness_state": session.readiness_state.value, "findings": [{"finding_id": f.finding_id, "area": f.area.value, "severity": f.severity.value, "code": f.code, "title": f.title, "message": f.message, "resolution_state": f.resolution_state.value} for f in findings]}
    return {"storage_ref": f"inline://dataset-review/{session.session_id}/validation.json", "content": content}


# #endregion _build_validation_export


# #endregion DatasetReviewDependencies
