"""Structured document evidence extraction for Deal Desk close certification."""

from __future__ import annotations

import hashlib
import json
import re
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Any, Literal, Protocol

from pydantic import BaseModel, ConfigDict, Field, computed_field

from g_gremlin import sfdc as sfdc_helpers
from g_gremlin.adobe_sign import AdobeAgreementSnapshot, AdobeSignClient, AdobeSignError
from g_gremlin.credentials.provider import SecretNotFoundError
from g_gremlin.docusign import DocuSignClient, DocuSignEnvelopeSnapshot, DocuSignError
from g_gremlin.pandadoc import PandaDocClient, PandaDocDocumentSnapshot, PandaDocError
from g_gremlin.autopilot.ai_provider import (
    AIProviderConfig,
    AIProviderStore,
    ai_generate_document_text,
    build_model_capability_profile,
    model_satisfies_workflow,
    workflow_model_requirement,
)

from .config import DealDeskConfig
from .documents import (
    CloseDocumentCandidateV1,
    CloseDocumentFormatKind,
    CloseDocumentInventoryV1,
    CloseDocumentProviderHint,
    CloseDocumentRoutedExtractionPath,
)

CloseDocumentExtractionStatus = Literal["not_attempted", "unsupported", "partial", "extracted"]
CloseDocumentExtractionMethod = Literal[
    "provider_api",
    "document_vision",
    "salesforce_contentversion_metadata",
]
CloseDocumentCanonicalFieldKey = Literal[
    "signature_present",
    "signer_count",
    "executed_at",
    "effective_date",
    "service_start_date",
    "service_end_date",
    "amount",
    "term_months",
    "legal_entity",
    "product_summary",
]
CloseDocumentFieldComparisonOutcome = Literal[
    "matched",
    "mismatch",
    "conflict",
    "unverified",
    "document_signal",
]

_SUPPORTED_METADATA_PROVIDERS: set[CloseDocumentProviderHint] = {"docusign", "pandadoc", "adobe"}
_QUERYABLE_FIELD_RE = re.compile(r"^[A-Za-z0-9_.]+$")
_PENDING_SIGNATURE_TOKENS = (
    "awaiting signature",
    "pending signature",
    "sent for signature",
    "draft",
    "viewed",
)
_SIGNED_STATUS_TOKENS = ("completed", "signed", "executed", "countersigned")
_DATE_FORMATS = (
    "%Y-%m-%d",
    "%Y/%m/%d",
    "%m/%d/%Y",
    "%m-%d-%Y",
    "%b %d, %Y",
    "%B %d, %Y",
)
_METHOD_PRIORITY: dict[CloseDocumentExtractionMethod, int] = {
    "provider_api": 3,
    "document_vision": 2,
    "salesforce_contentversion_metadata": 1,
}
_HIGH_FIDELITY_METHODS: set[CloseDocumentExtractionMethod] = {"provider_api", "document_vision"}
_SIGNATURE_KEYS: tuple[CloseDocumentCanonicalFieldKey, ...] = ("signature_present", "signer_count")
_COMPARABLE_FIELD_KEYS: tuple[CloseDocumentCanonicalFieldKey, ...] = (
    "amount",
    "term_months",
    "service_start_date",
    "service_end_date",
)
_ALL_FIELD_KEYS: tuple[CloseDocumentCanonicalFieldKey, ...] = (
    "signature_present",
    "signer_count",
    "executed_at",
    "effective_date",
    "service_start_date",
    "service_end_date",
    "amount",
    "term_months",
    "legal_entity",
    "product_summary",
)
_DOCUSIGN_MANAGED_ENVELOPE_OBJECT = "dsfs__DocuSign_Envelope__c"
_DOCUSIGN_MANAGED_RECIPIENT_OBJECT = "dsfs__DocuSign_Envelope_Recipient__c"
_DOCUSIGN_MANAGED_TAB_OBJECT = "dsfs__DocuSign_Envelope_Recipient_Tab__c"
_PANDADOC_DOCUMENT_OBJECT = "pandadoc__PandaDocDocument__c"
_ADOBE_AGREEMENT_OBJECT = "echosign_dev1__SIGN_Agreement__c"
_ADOBE_RECIPIENT_OBJECT = "echosign_dev1__SIGN_Recipients__c"
_DOCUSIGN_UNSIGNED_STATUS_TOKENS = (
    "not signed",
    "sent",
    "delivered",
    "created",
    "draft",
    "pending",
    "declined",
    "voided",
    "cancelled",
    "expired",
)
_DOCUSIGN_ENVELOPE_ID_RE = re.compile(
    r"(?:envelopeid|envelope id|envelope)\s*[_:=#()\-\s]*([A-F0-9-]{8,40})",
    flags=re.IGNORECASE,
)
_PANDADOC_UUID_RE = re.compile(
    r"(?:uuid|document id|documentid)\s*[_:=#()\-\s]*([A-F0-9-]{8,40})",
    flags=re.IGNORECASE,
)
_ADOBE_AGREEMENT_ID_RE = re.compile(
    r"(?:agreementid|agreement id)\s*[_:=#()\-\s]*([A-Za-z0-9-]{8,40})",
    flags=re.IGNORECASE,
)
_PANDADOC_SIGNED_STATUS_TOKENS = (
    "completed",
    "document completed",
    "approved",
    "document approved",
    "paid",
    "document paid",
)
_PANDADOC_UNSIGNED_STATUS_TOKENS = (
    "declined",
    "document declined",
    "expired",
    "document expired",
    "sent",
    "document sent",
    "viewed",
    "document viewed",
    "waiting approval",
    "document waiting approval",
    "draft",
    "document draft",
    "uploaded",
    "document uploaded",
)
_ADOBE_SIGNED_STATUS_TOKENS = (
    "signed",
    "approved",
    "accepted",
    "form filled",
    "delivered",
)
_ADOBE_UNSIGNED_STATUS_TOKENS = (
    "out for signature",
    "out for approval",
    "waiting for signature",
    "waiting for approval",
    "draft",
    "prefill",
    "delegated",
    "canceled declined",
    "cancelled declined",
    "expired",
)


class _ExtractionModel(BaseModel):
    model_config = ConfigDict(extra="ignore")

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump(exclude_none=False, by_alias=True)

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "_ExtractionModel":
        return cls.model_validate(data)


class EvidenceFieldV1(_ExtractionModel):
    version: int = 1
    canonical_key: CloseDocumentCanonicalFieldKey
    extracted_value: Any = None
    extraction_method: CloseDocumentExtractionMethod
    field_confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    source_locator: str | None = None
    document_id: str
    version_id: str | None = None
    content_hash: str | None = None
    document_format: CloseDocumentFormatKind = "unknown"
    routed_extraction_path: CloseDocumentRoutedExtractionPath | None = None
    source_fingerprint: str | None = None
    raw_snippet: str | None = None

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "EvidenceField/v1"


class CloseDocumentFieldComparisonV1(_ExtractionModel):
    version: int = 1
    canonical_key: CloseDocumentCanonicalFieldKey
    crm_field_name: str | None = None
    crm_field_value: Any = None
    extracted_value: Any = None
    extraction_method: CloseDocumentExtractionMethod | None = None
    field_confidence: float | None = Field(default=None, ge=0.0, le=1.0)
    outcome: CloseDocumentFieldComparisonOutcome = "unverified"
    document_id: str | None = None
    version_id: str | None = None
    content_hash: str | None = None
    document_format: CloseDocumentFormatKind = "unknown"
    routed_extraction_path: CloseDocumentRoutedExtractionPath | None = None
    source_fingerprint: str | None = None
    source_locator: str | None = None
    raw_snippet: str | None = None
    delta_summary: str | None = None

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "CloseDocumentFieldComparison/v1"


class CloseDocumentEvidenceV1(_ExtractionModel):
    version: int = 1
    evidence_id: str
    document_id: str
    version_id: str | None = None
    title: str
    provider_hint: CloseDocumentProviderHint = "unknown"
    extraction_method: CloseDocumentExtractionMethod = "salesforce_contentversion_metadata"
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    content_hash: str | None = None
    document_format: CloseDocumentFormatKind = "unknown"
    routed_extraction_path: CloseDocumentRoutedExtractionPath | None = None
    source_fingerprint: str | None = None
    signature_present: bool | None = None
    signer_count: int | None = None
    executed_at: str | None = None
    effective_date: str | None = None
    service_start_date: str | None = None
    service_end_date: str | None = None
    amount: float | None = None
    term_months: int | None = None
    legal_entity: str | None = None
    product_summary: str | None = None
    summary: str = ""
    metadata_excerpt: str | None = None
    fields: list[EvidenceFieldV1] = Field(default_factory=list)

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "CloseDocumentEvidence/v1"


class CloseDocumentExtractionIssueV1(_ExtractionModel):
    version: int = 1
    code: str
    severity: Literal["blocking", "warning", "info"]
    classification: Literal["approval_required", "block_close", "informational"]
    message: str
    field_name: str | None = None
    canonical_key: CloseDocumentCanonicalFieldKey | None = None
    extraction_method: CloseDocumentExtractionMethod | None = None
    field_confidence: float | None = Field(default=None, ge=0.0, le=1.0)
    document_id: str | None = None
    version_id: str | None = None
    content_hash: str | None = None
    source_locator: str | None = None
    raw_snippet: str | None = None
    suggested_action: str | None = None

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "CloseDocumentExtractionIssue/v1"


class CloseDocumentExtractionResultV1(_ExtractionModel):
    version: int = 1
    opportunity_id: str
    status: CloseDocumentExtractionStatus = "not_attempted"
    supported_candidate_count: int = 0
    evidence_count: int = 0
    comparison_count: int = 0
    discrepancy_count: int = 0
    summary: str = ""
    evidence: list[CloseDocumentEvidenceV1] = Field(default_factory=list)
    comparisons: list[CloseDocumentFieldComparisonV1] = Field(default_factory=list)
    issues: list[CloseDocumentExtractionIssueV1] = Field(default_factory=list)

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "CloseDocumentExtractionResult/v1"


class _DocumentVisionFieldPayload(_ExtractionModel):
    canonical_key: CloseDocumentCanonicalFieldKey
    extracted_value: Any = None
    field_confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    source_locator: str | None = None
    raw_snippet: str | None = None


class _DocumentVisionPayload(_ExtractionModel):
    summary: str = ""
    fields: list[_DocumentVisionFieldPayload] = Field(default_factory=list)


@dataclass(frozen=True)
class _LabeledMatch:
    label: str
    raw_value: str
    raw_snippet: str
    source_locator: str


@dataclass(frozen=True)
class _ParsedField:
    value: Any
    source_locator: str
    raw_snippet: str


@dataclass(frozen=True)
class _DocuSignManagedPackageSchema:
    opportunity_lookup_field: str
    envelope_id_field: str | None = None
    envelope_status_field: str | None = None
    envelope_completed_at_field: str | None = None
    envelope_sent_at_field: str | None = None
    envelope_subject_field: str | None = None
    recipient_envelope_lookup_field: str | None = None
    recipient_status_field: str | None = None
    recipient_signed_at_field: str | None = None
    recipient_name_field: str | None = None
    recipient_email_field: str | None = None
    recipient_routing_order_field: str | None = None
    tab_envelope_lookup_field: str | None = None
    tab_recipient_lookup_field: str | None = None
    tab_label_field: str | None = None
    tab_value_field: str | None = None


@dataclass(frozen=True)
class _DocuSignEnvelopeBundle:
    envelope_record_id: str
    envelope_id: str | None
    subject: str | None
    status: str | None
    completed_at: str | None
    sent_at: str | None
    envelope_row: dict[str, Any]
    recipient_rows: list[dict[str, Any]]
    tab_rows: list[dict[str, Any]]


@dataclass(frozen=True)
class _PandaDocDocumentSchema:
    opportunity_lookup_field: str | None = None
    parent_id_field: str | None = None
    uuid_field: str | None = None
    status_field: str | None = None
    completed_at_field: str | None = None
    sent_at_field: str | None = None
    title_field: str | None = None
    amount_field: str | None = None
    effective_date_field: str | None = None
    service_start_date_field: str | None = None
    service_end_date_field: str | None = None
    term_months_field: str | None = None
    legal_entity_field: str | None = None
    product_summary_field: str | None = None


@dataclass(frozen=True)
class _AdobeAgreementSchema:
    opportunity_lookup_field: str | None = None
    parent_id_field: str | None = None
    agreement_external_id_field: str | None = None
    status_field: str | None = None
    completed_at_field: str | None = None
    sent_at_field: str | None = None
    title_field: str | None = None
    amount_field: str | None = None
    effective_date_field: str | None = None
    service_start_date_field: str | None = None
    service_end_date_field: str | None = None
    term_months_field: str | None = None
    legal_entity_field: str | None = None
    product_summary_field: str | None = None
    recipient_agreement_lookup_field: str | None = None
    recipient_status_field: str | None = None
    recipient_signed_at_field: str | None = None
    recipient_email_field: str | None = None
    recipient_name_field: str | None = None


@dataclass(frozen=True)
class _AdobeAgreementBundle:
    agreement_record_id: str
    agreement_external_id: str | None
    title: str | None
    status: str | None
    completed_at: str | None
    sent_at: str | None
    agreement_row: dict[str, Any]
    recipient_rows: list[dict[str, Any]]


class DocumentEvidenceProvider(Protocol):
    extraction_method: CloseDocumentExtractionMethod

    def supports_candidate(self, *, candidate: CloseDocumentCandidateV1) -> bool:
        ...

    def extract(
        self,
        *,
        candidate: CloseDocumentCandidateV1,
        row: dict[str, Any],
        version_bytes: bytes | None,
        content_hash: str | None,
        state_dir: Path | None,
        org_alias: str | None,
        config: DealDeskConfig,
    ) -> CloseDocumentEvidenceV1 | None:
        ...


class DocuSignManagedPackageProvider:
    extraction_method: CloseDocumentExtractionMethod = "provider_api"

    def __init__(self, *, org_alias: str | None) -> None:
        self.org_alias = org_alias
        self._schema_loaded = False
        self._schema: _DocuSignManagedPackageSchema | None = None
        self._bundle_cache: dict[str, list[_DocuSignEnvelopeBundle]] = {}

    def supports_candidate(self, *, candidate: CloseDocumentCandidateV1) -> bool:
        if candidate.source_object != "ContentDocumentLink":
            return False
        return candidate.provider_hint == "docusign" and self._get_schema() is not None

    def extract(
        self,
        *,
        candidate: CloseDocumentCandidateV1,
        row: dict[str, Any],
        version_bytes: bytes | None,
        content_hash: str | None,
        state_dir: Path | None,
        org_alias: str | None,
        config: DealDeskConfig,
    ) -> CloseDocumentEvidenceV1 | None:
        del state_dir, org_alias, config
        schema = self._get_schema()
        if schema is None:
            return None
        bundles = self._get_bundles(candidate.linked_entity_id, schema=schema)
        if not bundles:
            return None
        bundle = _select_docusign_bundle_for_candidate(
            candidate=candidate,
            row=row,
            version_bytes=version_bytes,
            bundles=bundles,
        )
        if bundle is None:
            return None
        return _extract_docusign_managed_package_evidence_from_bundle(
            candidate=candidate,
            row=row,
            bundle=bundle,
            schema=schema,
            content_hash=content_hash,
        )

    def _get_schema(self) -> _DocuSignManagedPackageSchema | None:
        if not self._schema_loaded:
            self._schema = _load_docusign_managed_package_schema(org_alias=self.org_alias)
            self._schema_loaded = True
        return self._schema

    def _get_bundles(
        self,
        opportunity_id: str,
        *,
        schema: _DocuSignManagedPackageSchema,
    ) -> list[_DocuSignEnvelopeBundle]:
        normalized_id = str(opportunity_id or "").strip()
        if not normalized_id:
            return []
        cached = self._bundle_cache.get(normalized_id)
        if cached is not None:
            return cached
        bundles = _load_docusign_envelope_bundles(
            opportunity_id=normalized_id,
            org_alias=self.org_alias,
            schema=schema,
        )
        self._bundle_cache[normalized_id] = bundles
        return bundles


class PandaDocDocumentProvider:
    extraction_method: CloseDocumentExtractionMethod = "provider_api"

    def __init__(self, *, org_alias: str | None) -> None:
        self.org_alias = org_alias
        self._schema_loaded = False
        self._schema: _PandaDocDocumentSchema | None = None
        self._row_cache: dict[str, list[dict[str, Any]]] = {}

    def supports_candidate(self, *, candidate: CloseDocumentCandidateV1) -> bool:
        if candidate.source_object != "ContentDocumentLink":
            return False
        return candidate.provider_hint == "pandadoc" and self._get_schema() is not None

    def extract(
        self,
        *,
        candidate: CloseDocumentCandidateV1,
        row: dict[str, Any],
        version_bytes: bytes | None,
        content_hash: str | None,
        state_dir: Path | None,
        org_alias: str | None,
        config: DealDeskConfig,
    ) -> CloseDocumentEvidenceV1 | None:
        del state_dir, org_alias, config
        schema = self._get_schema()
        if schema is None:
            return None
        document_rows = self._get_rows(candidate.linked_entity_id, schema=schema)
        if not document_rows:
            return None
        matched_row = _select_pandadoc_row_for_candidate(
            candidate=candidate,
            row=row,
            version_bytes=version_bytes,
            document_rows=document_rows,
            schema=schema,
        )
        if matched_row is None:
            return None
        return _extract_pandadoc_document_evidence_from_row(
            candidate=candidate,
            content_version_row=row,
            document_row=matched_row,
            schema=schema,
            content_hash=content_hash,
        )

    def _get_schema(self) -> _PandaDocDocumentSchema | None:
        if not self._schema_loaded:
            self._schema = _load_pandadoc_document_schema(org_alias=self.org_alias)
            self._schema_loaded = True
        return self._schema

    def _get_rows(
        self,
        opportunity_id: str,
        *,
        schema: _PandaDocDocumentSchema,
    ) -> list[dict[str, Any]]:
        normalized_id = str(opportunity_id or "").strip()
        if not normalized_id:
            return []
        cached = self._row_cache.get(normalized_id)
        if cached is not None:
            return cached
        rows = _load_pandadoc_document_rows(
            opportunity_id=normalized_id,
            org_alias=self.org_alias,
            schema=schema,
        )
        self._row_cache[normalized_id] = rows
        return rows


class DocuSignApiProvider:
    extraction_method: CloseDocumentExtractionMethod = "provider_api"

    def __init__(self) -> None:
        self._client_loaded = False
        self._client: DocuSignClient | None = None
        self._snapshot_cache: dict[str, DocuSignEnvelopeSnapshot | None] = {}

    def supports_candidate(self, *, candidate: CloseDocumentCandidateV1) -> bool:
        if candidate.source_object != "ContentDocumentLink":
            return False
        return candidate.provider_hint == "docusign" and self._get_client() is not None

    def extract(
        self,
        *,
        candidate: CloseDocumentCandidateV1,
        row: dict[str, Any],
        version_bytes: bytes | None,
        content_hash: str | None,
        state_dir: Path | None,
        org_alias: str | None,
        config: DealDeskConfig,
    ) -> CloseDocumentEvidenceV1 | None:
        del state_dir, org_alias, config
        client = self._get_client()
        if client is None:
            return None
        envelope_hints = _extract_docusign_envelope_id_hints(
            candidate=candidate,
            row=row,
            version_bytes=version_bytes,
        )
        if not envelope_hints:
            return None
        for envelope_id in sorted(envelope_hints):
            snapshot = self._get_snapshot(client=client, envelope_id=envelope_id)
            if snapshot is None:
                continue
            extracted = _extract_docusign_api_evidence_from_snapshot(
                candidate=candidate,
                row=row,
                snapshot=snapshot,
                content_hash=content_hash,
            )
            if extracted is not None:
                return extracted
        return None

    def _get_client(self) -> DocuSignClient | None:
        if not self._client_loaded:
            self._client = _build_docusign_api_client()
            self._client_loaded = True
        return self._client

    def _get_snapshot(
        self,
        *,
        client: DocuSignClient,
        envelope_id: str,
    ) -> DocuSignEnvelopeSnapshot | None:
        normalized_id = _normalize_docusign_envelope_id(envelope_id)
        if normalized_id is None:
            return None
        cached = self._snapshot_cache.get(normalized_id)
        if normalized_id in self._snapshot_cache:
            return cached
        try:
            snapshot = client.get_envelope_snapshot(normalized_id)
        except DocuSignError:
            snapshot = None
        self._snapshot_cache[normalized_id] = snapshot
        return snapshot


class PandaDocApiProvider:
    extraction_method: CloseDocumentExtractionMethod = "provider_api"

    def __init__(self) -> None:
        self._client_loaded = False
        self._client: PandaDocClient | None = None
        self._snapshot_cache: dict[str, PandaDocDocumentSnapshot | None] = {}

    def supports_candidate(self, *, candidate: CloseDocumentCandidateV1) -> bool:
        if candidate.source_object != "ContentDocumentLink":
            return False
        return candidate.provider_hint == "pandadoc" and self._get_client() is not None

    def extract(
        self,
        *,
        candidate: CloseDocumentCandidateV1,
        row: dict[str, Any],
        version_bytes: bytes | None,
        content_hash: str | None,
        state_dir: Path | None,
        org_alias: str | None,
        config: DealDeskConfig,
    ) -> CloseDocumentEvidenceV1 | None:
        del version_bytes, state_dir, org_alias, config
        client = self._get_client()
        if client is None:
            return None
        uuid_hints = _extract_pandadoc_uuid_hints(candidate=candidate, row=row)
        if not uuid_hints:
            return None
        for document_uuid in sorted(uuid_hints):
            snapshot = self._get_snapshot(client=client, document_uuid=document_uuid)
            if snapshot is None:
                continue
            extracted = _extract_pandadoc_api_evidence_from_snapshot(
                candidate=candidate,
                content_version_row=row,
                snapshot=snapshot,
                content_hash=content_hash,
            )
            if extracted is not None:
                return extracted
        return None

    def _get_client(self) -> PandaDocClient | None:
        if not self._client_loaded:
            self._client = _build_pandadoc_api_client()
            self._client_loaded = True
        return self._client

    def _get_snapshot(
        self,
        *,
        client: PandaDocClient,
        document_uuid: str,
    ) -> PandaDocDocumentSnapshot | None:
        normalized_uuid = _normalize_pandadoc_uuid(document_uuid)
        if normalized_uuid is None:
            return None
        cached = self._snapshot_cache.get(normalized_uuid)
        if normalized_uuid in self._snapshot_cache:
            return cached
        try:
            snapshot = client.get_document_snapshot(normalized_uuid)
        except PandaDocError:
            snapshot = None
        self._snapshot_cache[normalized_uuid] = snapshot
        return snapshot


class AdobeSignApiProvider:
    extraction_method: CloseDocumentExtractionMethod = "provider_api"

    def __init__(self) -> None:
        self._client_loaded = False
        self._client: AdobeSignClient | None = None
        self._snapshot_cache: dict[str, AdobeAgreementSnapshot | None] = {}

    def supports_candidate(self, *, candidate: CloseDocumentCandidateV1) -> bool:
        if candidate.source_object != "ContentDocumentLink":
            return False
        return candidate.provider_hint == "adobe" and self._get_client() is not None

    def extract(
        self,
        *,
        candidate: CloseDocumentCandidateV1,
        row: dict[str, Any],
        version_bytes: bytes | None,
        content_hash: str | None,
        state_dir: Path | None,
        org_alias: str | None,
        config: DealDeskConfig,
    ) -> CloseDocumentEvidenceV1 | None:
        del state_dir, org_alias, config
        client = self._get_client()
        if client is None:
            return None
        agreement_hints = _extract_adobe_agreement_id_hints(
            candidate=candidate,
            row=row,
            version_bytes=version_bytes,
        )
        if not agreement_hints:
            return None
        for agreement_id in sorted(agreement_hints):
            snapshot = self._get_snapshot(client=client, agreement_id=agreement_id)
            if snapshot is None:
                continue
            extracted = _extract_adobe_api_evidence_from_snapshot(
                candidate=candidate,
                content_version_row=row,
                snapshot=snapshot,
                content_hash=content_hash,
            )
            if extracted is not None:
                return extracted
        return None

    def _get_client(self) -> AdobeSignClient | None:
        if not self._client_loaded:
            self._client = _build_adobe_sign_api_client()
            self._client_loaded = True
        return self._client

    def _get_snapshot(
        self,
        *,
        client: AdobeSignClient,
        agreement_id: str,
    ) -> AdobeAgreementSnapshot | None:
        normalized_id = _normalize_adobe_agreement_id(agreement_id)
        if normalized_id is None:
            return None
        cached = self._snapshot_cache.get(normalized_id)
        if normalized_id in self._snapshot_cache:
            return cached
        try:
            snapshot = client.get_agreement_snapshot(normalized_id)
        except AdobeSignError:
            snapshot = None
        self._snapshot_cache[normalized_id] = snapshot
        return snapshot


class AdobeAgreementProvider:
    extraction_method: CloseDocumentExtractionMethod = "provider_api"

    def __init__(self, *, org_alias: str | None) -> None:
        self.org_alias = org_alias
        self._schema_loaded = False
        self._schema: _AdobeAgreementSchema | None = None
        self._bundle_cache: dict[str, list[_AdobeAgreementBundle]] = {}

    def supports_candidate(self, *, candidate: CloseDocumentCandidateV1) -> bool:
        if candidate.source_object != "ContentDocumentLink":
            return False
        return candidate.provider_hint == "adobe" and self._get_schema() is not None

    def extract(
        self,
        *,
        candidate: CloseDocumentCandidateV1,
        row: dict[str, Any],
        version_bytes: bytes | None,
        content_hash: str | None,
        state_dir: Path | None,
        org_alias: str | None,
        config: DealDeskConfig,
    ) -> CloseDocumentEvidenceV1 | None:
        del state_dir, org_alias, config
        schema = self._get_schema()
        if schema is None:
            return None
        bundles = self._get_bundles(candidate.linked_entity_id, schema=schema)
        if not bundles:
            return None
        bundle = _select_adobe_bundle_for_candidate(
            candidate=candidate,
            row=row,
            version_bytes=version_bytes,
            bundles=bundles,
            schema=schema,
        )
        if bundle is None:
            return None
        return _extract_adobe_agreement_evidence_from_bundle(
            candidate=candidate,
            content_version_row=row,
            bundle=bundle,
            schema=schema,
            content_hash=content_hash,
        )

    def _get_schema(self) -> _AdobeAgreementSchema | None:
        if not self._schema_loaded:
            self._schema = _load_adobe_agreement_schema(org_alias=self.org_alias)
            self._schema_loaded = True
        return self._schema

    def _get_bundles(
        self,
        opportunity_id: str,
        *,
        schema: _AdobeAgreementSchema,
    ) -> list[_AdobeAgreementBundle]:
        normalized_id = str(opportunity_id or "").strip()
        if not normalized_id:
            return []
        cached = self._bundle_cache.get(normalized_id)
        if cached is not None:
            return cached
        bundles = _load_adobe_agreement_bundles(
            opportunity_id=normalized_id,
            org_alias=self.org_alias,
            schema=schema,
        )
        self._bundle_cache[normalized_id] = bundles
        return bundles


class MetadataParseProvider:
    extraction_method: CloseDocumentExtractionMethod = "salesforce_contentversion_metadata"

    def supports_candidate(self, *, candidate: CloseDocumentCandidateV1) -> bool:
        return (
            candidate.source_object == "ContentDocumentLink"
            and candidate.provider_hint in _SUPPORTED_METADATA_PROVIDERS
        )

    def extract(
        self,
        *,
        candidate: CloseDocumentCandidateV1,
        row: dict[str, Any],
        version_bytes: bytes | None,
        content_hash: str | None,
        state_dir: Path | None,
        org_alias: str | None,
        config: DealDeskConfig,
    ) -> CloseDocumentEvidenceV1 | None:
        del version_bytes, state_dir, org_alias, config
        return _extract_metadata_evidence_from_version_row(
            candidate=candidate,
            row=row,
            content_hash=content_hash,
        )


class DocumentVisionProvider:
    extraction_method: CloseDocumentExtractionMethod = "document_vision"

    def __init__(self, *, state_dir: Path | None) -> None:
        self.state_dir = state_dir

    def supports_candidate(self, *, candidate: CloseDocumentCandidateV1) -> bool:
        if str(candidate.file_extension or "").strip().lower() != "pdf":
            return False
        return _document_vision_runtime_available(self.state_dir)

    def extract(
        self,
        *,
        candidate: CloseDocumentCandidateV1,
        row: dict[str, Any],
        version_bytes: bytes | None,
        content_hash: str | None,
        state_dir: Path | None,
        org_alias: str | None,
        config: DealDeskConfig,
    ) -> CloseDocumentEvidenceV1 | None:
        del state_dir, org_alias, config
        if self.state_dir is None or not version_bytes:
            return None
        ai_config = _load_document_vision_config(self.state_dir)
        if ai_config is None:
            return None
        normalized_title = str(row.get("Title") or candidate.title or "").strip() or "document.pdf"
        normalized_hash = content_hash or f"sha256:{hashlib.sha256(version_bytes).hexdigest()}"
        try:
            raw_response = ai_generate_document_text(
                ai_config,
                _document_vision_prompt(),
                document_bytes=version_bytes,
                filename=normalized_title,
                document_mime_type="application/pdf",
                max_output_tokens=1400,
            )
        except Exception:
            return None
        return _extract_document_vision_evidence_from_response(
            candidate=candidate,
            row=row,
            raw_response=raw_response,
            content_hash=normalized_hash,
        )


def build_document_evidence_providers(
    *,
    state_dir: str | Path | None = None,
    org_alias: str | None = None,
) -> list[DocumentEvidenceProvider]:
    normalized_state_dir = Path(state_dir).resolve() if state_dir else None
    return [
        DocuSignManagedPackageProvider(org_alias=org_alias),
        DocuSignApiProvider(),
        PandaDocDocumentProvider(org_alias=org_alias),
        PandaDocApiProvider(),
        AdobeAgreementProvider(org_alias=org_alias),
        AdobeSignApiProvider(),
        MetadataParseProvider(),
        DocumentVisionProvider(state_dir=normalized_state_dir),
    ]


def preflight_close_document_inventory(
    *,
    inventory: CloseDocumentInventoryV1,
    org_alias: str | None,
    state_dir: str | Path | None = None,
) -> CloseDocumentInventoryV1:
    if not inventory.candidates:
        return inventory
    normalized_state_dir = Path(state_dir).resolve() if state_dir else None
    providers = build_document_evidence_providers(state_dir=normalized_state_dir, org_alias=org_alias)
    try:
        version_rows = _load_content_versions(
            document_ids=[
                candidate.document_id
                for candidate in inventory.candidates
                if candidate.source_object == "ContentDocumentLink"
            ],
            org_alias=org_alias,
        )
    except Exception:
        version_rows = []
    row_by_document_id = {
        str(row.get("ContentDocumentId") or "").strip(): row
        for row in version_rows
        if str(row.get("ContentDocumentId") or "").strip()
    }
    candidates: list[CloseDocumentCandidateV1] = []
    for candidate in inventory.candidates:
        row = row_by_document_id.get(candidate.document_id)
        version_id, version_bytes, content_hash = _load_candidate_binary(
            candidate=candidate,
            row=row,
            org_alias=org_alias,
        )
        document_format, format_confidence, format_notes = _preflight_document_format(
            candidate=candidate,
            version_bytes=version_bytes,
        )
        routed_path = _planned_routed_extraction_path(
            candidate=candidate,
            providers=providers,
        )
        if document_format in {"native_text", "fillable_form"} and routed_path == "document_vision_fallback":
            format_notes = [
                *format_notes,
                "Native-text or fillable-form parsing is not enabled yet; vision remains a fallback path for this document.",
            ]
        candidates.append(
            candidate.model_copy(
                update={
                    "observed_version_id": version_id,
                    "observed_content_hash": content_hash,
                    "document_format": document_format,
                    "format_confidence": format_confidence,
                    "format_notes": format_notes,
                    "routed_extraction_path": routed_path,
                }
            )
        )
    return inventory.model_copy(update={"candidates": candidates})


def extract_structured_close_document_evidence(
    *,
    opportunity_id: str,
    org_alias: str | None,
    config: DealDeskConfig,
    inventory: CloseDocumentInventoryV1,
    state_dir: str | Path | None = None,
) -> CloseDocumentExtractionResultV1:
    normalized_state_dir = Path(state_dir).resolve() if state_dir else None
    if any(
        (not str(candidate.observed_content_hash or "").strip() or candidate.routed_extraction_path is None)
        for candidate in inventory.candidates
    ):
        inventory = preflight_close_document_inventory(
            inventory=inventory,
            org_alias=org_alias,
            state_dir=normalized_state_dir,
        )
    if not inventory.required:
        return CloseDocumentExtractionResultV1(
            opportunity_id=opportunity_id,
            status="not_attempted",
            summary="Structured document extraction skipped because document evidence is not required by current config.",
        )
    if inventory.status in {"missing", "ambiguous"}:
        return CloseDocumentExtractionResultV1(
            opportunity_id=opportunity_id,
            status="not_attempted",
            summary="Structured document extraction is waiting for document inventory to resolve.",
        )

    likely_candidates = [
        candidate
        for candidate in inventory.candidates
        if candidate.classification in {"signed_order_form", "contract", "signature_metadata"}
    ]
    if not likely_candidates:
        return CloseDocumentExtractionResultV1(
            opportunity_id=opportunity_id,
            status="not_attempted",
            summary="No likely close-evidence documents were available for structured extraction.",
        )

    providers = build_document_evidence_providers(
        state_dir=normalized_state_dir,
        org_alias=org_alias,
    )
    supported_candidates = [
        candidate
        for candidate in likely_candidates
        if any(provider.supports_candidate(candidate=candidate) for provider in providers)
    ]
    if not supported_candidates:
        issue = CloseDocumentExtractionIssueV1(
            code="DD_DOCUMENT_EVIDENCE_UNSUPPORTED",
            severity="warning",
            classification="approval_required",
            message=(
                "Close evidence is present, but no supported provider-backed or metadata-backed "
                "document verification path is available yet."
            ),
            suggested_action="Review the attached order form or contract artifact manually until richer extraction is enabled.",
        )
        return CloseDocumentExtractionResultV1(
            opportunity_id=opportunity_id,
            status="unsupported",
            supported_candidate_count=0,
            discrepancy_count=1,
            summary=issue.message,
            issues=[issue],
        )

    opportunity_snapshot = _load_opportunity_snapshot(
        opportunity_id=opportunity_id,
        org_alias=org_alias,
        config=config,
    )
    version_rows = _load_content_versions(
        document_ids=[
            candidate.document_id
            for candidate in supported_candidates
            if candidate.source_object == "ContentDocumentLink"
        ],
        org_alias=org_alias,
    )

    candidate_by_document_id = {candidate.document_id: candidate for candidate in supported_candidates}
    row_by_document_id = {
        str(row.get("ContentDocumentId") or "").strip(): row
        for row in version_rows
        if str(row.get("ContentDocumentId") or "").strip()
    }
    evidence: list[CloseDocumentEvidenceV1] = []
    for candidate in supported_candidates:
        row = row_by_document_id.get(candidate.document_id) or _synthetic_candidate_row(candidate=candidate)
        version_id, version_bytes, content_hash = _load_candidate_binary(
            candidate=candidate,
            row=row,
            org_alias=org_alias,
        )
        for provider in providers:
            if not provider.supports_candidate(candidate=candidate):
                continue
            extracted = provider.extract(
                candidate=candidate,
                row=row,
                version_bytes=version_bytes,
                content_hash=content_hash,
                state_dir=normalized_state_dir,
                org_alias=org_alias,
                config=config,
            )
            if extracted is not None:
                evidence.append(
                    _apply_candidate_provenance_to_evidence(
                        candidate=candidate,
                        evidence=extracted,
                    )
                )
                break

    if not evidence:
        issue = CloseDocumentExtractionIssueV1(
            code="DD_DOCUMENT_EVIDENCE_UNSUPPORTED",
            severity="warning",
            classification="approval_required",
            message=(
                "Supported close documents were discovered, but no document fields could be extracted "
                "from the currently enabled verification providers."
            ),
            suggested_action="Review the e-sign artifact manually or enable a higher-fidelity document verification path.",
        )
        return CloseDocumentExtractionResultV1(
            opportunity_id=opportunity_id,
            status="unsupported",
            supported_candidate_count=len(supported_candidates),
            discrepancy_count=1,
            summary=issue.message,
            issues=[issue],
        )

    comparisons = _build_field_comparisons(
        evidence=evidence,
        opportunity_snapshot=opportunity_snapshot,
        config=config,
    )
    issues = _comparison_issues(
        comparisons=comparisons,
        evidence=evidence,
        config=config,
    )
    status = _extraction_status(
        evidence=evidence,
        comparisons=comparisons,
        issues=issues,
    )
    if status == "partial" and not any(
        issue.code in {"DD_DOCUMENT_EVIDENCE_PARTIAL", "DD_DOCUMENT_VERIFICATION_LOW_CONFIDENCE"}
        for issue in issues
    ):
        issues.append(
            CloseDocumentExtractionIssueV1(
                code="DD_DOCUMENT_EVIDENCE_PARTIAL",
                severity="warning",
                classification="approval_required",
                message=(
                    "Document verification extracted some close evidence, but one or more key fields "
                    "still need human review before final close progression."
                ),
                suggested_action="Review the extracted close evidence with RevOps before final close progression.",
            )
        )
    ordered_issues = _sort_issues(issues)
    return CloseDocumentExtractionResultV1(
        opportunity_id=opportunity_id,
        status=status,
        supported_candidate_count=len(supported_candidates),
        evidence_count=len(evidence),
        comparison_count=len(comparisons),
        discrepancy_count=len(ordered_issues),
        summary=_extraction_summary(
            status=status,
            evidence=evidence,
            issues=ordered_issues,
        ),
        evidence=evidence,
        comparisons=comparisons,
        issues=ordered_issues,
    )


def _load_opportunity_snapshot(
    *,
    opportunity_id: str,
    org_alias: str | None,
    config: DealDeskConfig,
) -> dict[str, Any]:
    fields = {
        "Id",
        "CloseDate",
        config.amount_rules.amount_field,
    }
    for name in (
        config.date_rules.contract_start_field,
        config.date_rules.contract_end_field,
        config.amount_rules.term_months_field,
    ):
        if name:
            fields.add(name)
    selected_fields = sorted(
        field_name
        for field_name in fields
        if isinstance(field_name, str) and _QUERYABLE_FIELD_RE.fullmatch(field_name)
    )
    escaped_id = sfdc_helpers.escape_soql_value(opportunity_id)
    rows = sfdc_helpers.run_soql(
        f"SELECT {', '.join(selected_fields)} FROM Opportunity WHERE Id = '{escaped_id}' LIMIT 1",
        org_alias=org_alias,
    )
    if not rows:
        raise RuntimeError(f"Opportunity {opportunity_id} was not found.")
    return rows[0] if isinstance(rows[0], dict) else {}


def _load_content_versions(
    *,
    document_ids: list[str],
    org_alias: str | None,
) -> list[dict[str, Any]]:
    normalized_ids = [str(document_id or "").strip() for document_id in document_ids if str(document_id or "").strip()]
    if not normalized_ids:
        return []
    in_clause = ", ".join(f"'{sfdc_helpers.escape_soql_value(document_id)}'" for document_id in normalized_ids)
    return sfdc_helpers.run_soql(
        (
            "SELECT Id, ContentDocumentId, Title, Description, PathOnClient, FileType, FileExtension, "
            "CreatedDate, ContentModifiedDate, Checksum "
            "FROM ContentVersion "
            f"WHERE IsLatest = true AND ContentDocumentId IN ({in_clause}) "
            "ORDER BY ContentModifiedDate DESC LIMIT 200"
        ),
        org_alias=org_alias,
    )


def _load_content_binary(*, version_id: str, org_alias: str | None) -> tuple[bytes | None, str | None]:
    normalized_version_id = str(version_id or "").strip()
    if not normalized_version_id:
        return None, None
    try:
        payload = sfdc_helpers.download_content_version_data(normalized_version_id, org_alias=org_alias)
    except Exception:
        return None, None
    if not payload:
        return None, None
    return payload, f"sha256:{hashlib.sha256(payload).hexdigest()}"


def _load_attachment_binary(*, attachment_id: str, org_alias: str | None) -> tuple[bytes | None, str | None]:
    normalized_attachment_id = str(attachment_id or "").strip()
    if not normalized_attachment_id:
        return None, None
    try:
        payload = sfdc_helpers.download_attachment_body(normalized_attachment_id, org_alias=org_alias)
    except Exception:
        return None, None
    if not payload:
        return None, None
    return payload, f"sha256:{hashlib.sha256(payload).hexdigest()}"


def _load_candidate_binary(
    *,
    candidate: CloseDocumentCandidateV1,
    row: dict[str, Any] | None,
    org_alias: str | None,
) -> tuple[str | None, bytes | None, str | None]:
    if candidate.source_object == "ContentDocumentLink":
        version_id = str(row.get("Id") or "").strip() if row is not None else ""
        version_bytes, content_hash = _load_content_binary(version_id=version_id, org_alias=org_alias)
        return (version_id or None, version_bytes, content_hash)
    attachment_id = str(candidate.document_id or "").strip()
    version_bytes, content_hash = _load_attachment_binary(attachment_id=attachment_id, org_alias=org_alias)
    return (attachment_id or None, version_bytes, content_hash)


def _synthetic_candidate_row(*, candidate: CloseDocumentCandidateV1) -> dict[str, Any]:
    return {
        "Id": candidate.document_id,
        "ContentDocumentId": candidate.document_id if candidate.source_object == "ContentDocumentLink" else None,
        "Title": candidate.title,
        "Name": candidate.title,
        "Description": "",
        "PathOnClient": candidate.title,
        "FileType": candidate.content_type or candidate.file_extension or "",
        "FileExtension": candidate.file_extension or "",
        "ContentType": candidate.content_type or "",
        "CreatedDate": candidate.created_at or "",
    }


def _preflight_document_format(
    *,
    candidate: CloseDocumentCandidateV1,
    version_bytes: bytes | None,
) -> tuple[CloseDocumentFormatKind, float, list[str]]:
    if str(candidate.file_extension or "").strip().lower() != "pdf":
        return "unknown", 0.0, ["Format preflight currently inspects PDF content only."]
    if not version_bytes:
        return "unknown", 0.0, ["PDF bytes were not available during format preflight."]
    if not version_bytes.startswith(b"%PDF"):
        return "unknown", 0.12, ["File extension indicates PDF, but the observed bytes did not parse as a standard PDF header."]
    text = version_bytes[:900000].decode("latin-1", errors="ignore")
    has_form = any(token in text for token in ("/AcroForm", "/XFA", "/Subtype /Widget", "/Subtype/Widget"))
    text_ops = sum(text.count(token) for token in (" BT", " ET", " Tj", " TJ", "ToUnicode"))
    image_ops = text.count("/Subtype /Image") + text.count("/Image")
    if has_form:
        return "fillable_form", 0.94, ["Structured form field markers were detected in the PDF."]
    if text_ops >= 6 and image_ops == 0:
        return "native_text", 0.88, ["PDF text operators were detected without image-only signatures."]
    if text_ops >= 6 and image_ops > 0:
        return "mixed", 0.78, ["The PDF contains both machine-readable text operators and embedded image content."]
    if image_ops > 0 and text_ops < 4:
        return "scanned", 0.8, ["The PDF appears image-heavy and lacks strong machine-readable text signals."]
    if text_ops >= 2:
        return "native_text", 0.56, ["The PDF has weak but present text-layer signals."]
    return "unknown", 0.28, ["Format signals were inconclusive during PDF preflight."]


def _planned_routed_extraction_path(
    *,
    candidate: CloseDocumentCandidateV1,
    providers: list[DocumentEvidenceProvider],
) -> CloseDocumentRoutedExtractionPath:
    supported_methods = {
        getattr(provider, "extraction_method", None)
        for provider in providers
        if provider.supports_candidate(candidate=candidate)
    }
    if "provider_api" in supported_methods:
        return "provider_api"
    if "salesforce_contentversion_metadata" in supported_methods:
        return "salesforce_contentversion_metadata"
    if "document_vision" in supported_methods:
        return "document_vision_fallback"
    return "unavailable"


def _routed_path_for_method(method: CloseDocumentExtractionMethod) -> CloseDocumentRoutedExtractionPath:
    if method == "document_vision":
        return "document_vision_fallback"
    return method


def _apply_candidate_provenance_to_evidence(
    *,
    candidate: CloseDocumentCandidateV1,
    evidence: CloseDocumentEvidenceV1,
) -> CloseDocumentEvidenceV1:
    routed_path = _routed_path_for_method(evidence.extraction_method)
    fields = [
        field.model_copy(
            update={
                "document_format": candidate.document_format,
                "routed_extraction_path": routed_path,
                "content_hash": field.content_hash or evidence.content_hash or candidate.observed_content_hash,
                "version_id": field.version_id or evidence.version_id or candidate.observed_version_id,
            }
        )
        for field in evidence.fields
    ]
    return evidence.model_copy(
        update={
            "version_id": evidence.version_id or candidate.observed_version_id,
            "content_hash": evidence.content_hash or candidate.observed_content_hash,
            "document_format": candidate.document_format,
            "routed_extraction_path": routed_path,
            "fields": fields,
        }
    )


def _load_docusign_managed_package_schema(
    *,
    org_alias: str | None,
) -> _DocuSignManagedPackageSchema | None:
    envelope_fields = _describe_sobject_fields(_DOCUSIGN_MANAGED_ENVELOPE_OBJECT, org_alias=org_alias)
    if envelope_fields is None:
        return None
    recipient_fields = _describe_sobject_fields(_DOCUSIGN_MANAGED_RECIPIENT_OBJECT, org_alias=org_alias)
    tab_fields = _describe_sobject_fields(_DOCUSIGN_MANAGED_TAB_OBJECT, org_alias=org_alias)

    opportunity_lookup_field = _select_describe_field_name(
        envelope_fields,
        exact_names=("dsfs__Opportunity__c",),
        reference_to="Opportunity",
    )
    if opportunity_lookup_field is None:
        return None

    return _DocuSignManagedPackageSchema(
        opportunity_lookup_field=opportunity_lookup_field,
        envelope_id_field=_select_describe_field_name(
            envelope_fields,
            exact_names=("dsfs__Envelope_ID__c", "dsfs__EnvelopeId__c"),
            required_tokens=("envelope", "id"),
        ),
        envelope_status_field=_select_describe_field_name(
            envelope_fields,
            exact_names=("dsfs__Envelope_Status__c", "dsfs__Status__c"),
            required_tokens=("status",),
        ),
        envelope_completed_at_field=_select_describe_field_name(
            envelope_fields,
            exact_names=("dsfs__Completed_Date_Time__c", "dsfs__Completed_Date__c"),
            required_tokens=("completed",),
            allowed_types=("date", "datetime"),
        ),
        envelope_sent_at_field=_select_describe_field_name(
            envelope_fields,
            exact_names=("dsfs__Sent_Date_Time__c", "dsfs__DateSent__c", "dsfs__Sent_Date__c"),
            required_tokens=("sent",),
            allowed_types=("date", "datetime"),
        ),
        envelope_subject_field=_select_describe_field_name(
            envelope_fields,
            exact_names=("dsfs__Subject__c",),
            required_tokens=("subject",),
        ),
        recipient_envelope_lookup_field=_select_describe_field_name(
            recipient_fields,
            exact_names=("dsfs__Envelope__c", "dsfs__DocuSign_Envelope__c"),
            reference_to=_DOCUSIGN_MANAGED_ENVELOPE_OBJECT,
        )
        if recipient_fields is not None
        else None,
        recipient_status_field=_select_describe_field_name(
            recipient_fields,
            exact_names=("dsfs__Recipient_Status__c", "dsfs__Status__c"),
            required_tokens=("status",),
        )
        if recipient_fields is not None
        else None,
        recipient_signed_at_field=_select_describe_field_name(
            recipient_fields,
            exact_names=("dsfs__DateSigned__c", "dsfs__Signed_Date_Time__c"),
            required_tokens=("signed",),
            allowed_types=("date", "datetime"),
        )
        if recipient_fields is not None
        else None,
        recipient_name_field=_select_describe_field_name(
            recipient_fields,
            exact_names=("dsfs__Name__c", "Name"),
            required_tokens=("name",),
        )
        if recipient_fields is not None
        else None,
        recipient_email_field=_select_describe_field_name(
            recipient_fields,
            exact_names=("dsfs__Email__c",),
            required_tokens=("email",),
        )
        if recipient_fields is not None
        else None,
        recipient_routing_order_field=_select_describe_field_name(
            recipient_fields,
            exact_names=("dsfs__Routing_Order__c",),
            required_tokens=("routing", "order"),
        )
        if recipient_fields is not None
        else None,
        tab_envelope_lookup_field=_select_describe_field_name(
            tab_fields,
            exact_names=("dsfs__Envelope__c", "dsfs__DocuSign_Envelope__c"),
            reference_to=_DOCUSIGN_MANAGED_ENVELOPE_OBJECT,
        )
        if tab_fields is not None
        else None,
        tab_recipient_lookup_field=_select_describe_field_name(
            tab_fields,
            exact_names=("dsfs__Recipient__c", "dsfs__Envelope_Recipient__c"),
            reference_to=_DOCUSIGN_MANAGED_RECIPIENT_OBJECT,
        )
        if tab_fields is not None
        else None,
        tab_label_field=_select_describe_field_name(
            tab_fields,
            exact_names=("dsfs__Tab_Label__c", "dsfs__Label__c", "Name"),
            required_tokens=("label",),
        )
        if tab_fields is not None
        else None,
        tab_value_field=_select_describe_field_name(
            tab_fields,
            exact_names=("dsfs__Tab_Value__c", "dsfs__Value__c", "dsfs__TabValue__c"),
            required_tokens=("value",),
        )
        if tab_fields is not None
        else None,
    )


def _load_pandadoc_document_schema(
    *,
    org_alias: str | None,
) -> _PandaDocDocumentSchema | None:
    fields = _describe_sobject_fields(_PANDADOC_DOCUMENT_OBJECT, org_alias=org_alias)
    if fields is None:
        return None
    opportunity_lookup_field = _select_describe_field_name(
        fields,
        exact_names=("pandadoc__Opportunity__c",),
        reference_to="Opportunity",
    )
    parent_id_field = _select_describe_field_name(
        fields,
        exact_names=("pandadoc__ParentId__c",),
        required_tokens=("parent", "id"),
    )
    if opportunity_lookup_field is None and parent_id_field is None:
        return None
    return _PandaDocDocumentSchema(
        opportunity_lookup_field=opportunity_lookup_field,
        parent_id_field=parent_id_field,
        uuid_field=_select_describe_field_name(
            fields,
            exact_names=("pandadoc__UUID__c",),
            required_tokens=("uuid",),
        ),
        status_field=_select_describe_field_name(
            fields,
            exact_names=("pandadoc__Status__c", "Status"),
            required_tokens=("status",),
        ),
        completed_at_field=_select_describe_field_name(
            fields,
            exact_names=("pandadoc__Document_Completed_Date__c", "pandadoc__Completed_Date__c"),
            required_tokens=("completed", "date"),
            allowed_types=("date", "datetime"),
        ),
        sent_at_field=_select_describe_field_name(
            fields,
            exact_names=("pandadoc__Document_Sent_Date__c", "pandadoc__Sent_Date__c"),
            required_tokens=("sent", "date"),
            allowed_types=("date", "datetime"),
        ),
        title_field=_select_describe_field_name(
            fields,
            exact_names=("pandadoc__Document_Full_Name__c", "Name"),
            required_tokens=("document", "full", "name"),
        )
        or "Name",
        amount_field=_select_describe_field_name(
            fields,
            exact_names=("pandadoc__Document_Value__c", "pandadoc__Value__c"),
            required_tokens=("value",),
            allowed_types=("currency", "double", "int"),
        ),
        effective_date_field=_select_describe_field_name(
            fields,
            exact_names=("pandadoc__Effective_Date__c",),
            required_tokens=("effective", "date"),
            allowed_types=("date", "datetime"),
        ),
        service_start_date_field=_select_describe_field_name(
            fields,
            exact_names=("pandadoc__Service_Start_Date__c", "pandadoc__Start_Date__c"),
            required_tokens=("start", "date"),
            allowed_types=("date", "datetime"),
        ),
        service_end_date_field=_select_describe_field_name(
            fields,
            exact_names=("pandadoc__Service_End_Date__c", "pandadoc__End_Date__c"),
            required_tokens=("end", "date"),
            allowed_types=("date", "datetime"),
        ),
        term_months_field=_select_describe_field_name(
            fields,
            exact_names=("pandadoc__Term_Months__c", "pandadoc__Contract_Term_Months__c"),
            required_tokens=("term",),
            allowed_types=("double", "int"),
        ),
        legal_entity_field=_select_describe_field_name(
            fields,
            exact_names=("pandadoc__Legal_Entity__c",),
            required_tokens=("legal", "entity"),
        ),
        product_summary_field=_select_describe_field_name(
            fields,
            exact_names=("pandadoc__Product_Summary__c", "pandadoc__Products__c"),
            required_tokens=("product",),
        ),
    )


def _load_adobe_agreement_schema(
    *,
    org_alias: str | None,
) -> _AdobeAgreementSchema | None:
    agreement_fields = _describe_sobject_fields(_ADOBE_AGREEMENT_OBJECT, org_alias=org_alias)
    if agreement_fields is None:
        return None
    recipient_fields = _describe_sobject_fields(_ADOBE_RECIPIENT_OBJECT, org_alias=org_alias)

    opportunity_lookup_field = _select_describe_field_name(
        agreement_fields,
        exact_names=("echosign_dev1__Opportunity__c",),
        reference_to="Opportunity",
    )
    parent_id_field = _select_describe_field_name(
        agreement_fields,
        exact_names=("echosign_dev1__Master_Object_Id__c", "echosign_dev1__ParentId__c"),
        required_tokens=("object", "id"),
    )
    if opportunity_lookup_field is None and parent_id_field is None:
        return None

    status_field = _select_describe_field_name(
        agreement_fields,
        exact_names=("echosign_dev1__Status__c", "Status"),
        required_tokens=("status",),
    )
    if status_field is None:
        return None

    return _AdobeAgreementSchema(
        opportunity_lookup_field=opportunity_lookup_field,
        parent_id_field=parent_id_field,
        agreement_external_id_field=_select_describe_field_name(
            agreement_fields,
            exact_names=("echosign_dev1__Agreement_Id__c",),
            required_tokens=("agreement", "id"),
        ),
        status_field=status_field,
        completed_at_field=_select_describe_field_name(
            agreement_fields,
            exact_names=("echosign_dev1__Date_Signed__c", "echosign_dev1__Signed_Date__c"),
            required_tokens=("signed", "date"),
            allowed_types=("date", "datetime"),
        ),
        sent_at_field=_select_describe_field_name(
            agreement_fields,
            exact_names=("echosign_dev1__Date_Sent__c", "echosign_dev1__Sent_Date__c"),
            required_tokens=("sent", "date"),
            allowed_types=("date", "datetime"),
        ),
        title_field=_select_describe_field_name(
            agreement_fields,
            exact_names=("echosign_dev1__Name__c", "Name"),
            required_tokens=("name",),
        )
        or "Name",
        amount_field=_select_describe_field_name(
            agreement_fields,
            exact_names=("echosign_dev1__Agreement_Value__c", "echosign_dev1__Amount__c"),
            required_tokens=("value",),
            allowed_types=("currency", "double", "int"),
        ),
        effective_date_field=_select_describe_field_name(
            agreement_fields,
            exact_names=("echosign_dev1__Effective_Date__c",),
            required_tokens=("effective", "date"),
            allowed_types=("date", "datetime"),
        ),
        service_start_date_field=_select_describe_field_name(
            agreement_fields,
            exact_names=("echosign_dev1__Service_Start_Date__c", "echosign_dev1__Start_Date__c"),
            required_tokens=("start", "date"),
            allowed_types=("date", "datetime"),
        ),
        service_end_date_field=_select_describe_field_name(
            agreement_fields,
            exact_names=("echosign_dev1__Service_End_Date__c", "echosign_dev1__End_Date__c"),
            required_tokens=("end", "date"),
            allowed_types=("date", "datetime"),
        ),
        term_months_field=_select_describe_field_name(
            agreement_fields,
            exact_names=("echosign_dev1__Term_Months__c",),
            required_tokens=("term",),
            allowed_types=("double", "int"),
        ),
        legal_entity_field=_select_describe_field_name(
            agreement_fields,
            exact_names=("echosign_dev1__Legal_Entity__c",),
            required_tokens=("legal", "entity"),
        ),
        product_summary_field=_select_describe_field_name(
            agreement_fields,
            exact_names=("echosign_dev1__Product_Summary__c", "echosign_dev1__Products__c"),
            required_tokens=("product",),
        ),
        recipient_agreement_lookup_field=_select_describe_field_name(
            recipient_fields,
            exact_names=("echosign_dev1__Agreement__c", "echosign_dev1__SIGN_Agreement__c"),
            reference_to=_ADOBE_AGREEMENT_OBJECT,
        )
        if recipient_fields is not None
        else None,
        recipient_status_field=_select_describe_field_name(
            recipient_fields,
            exact_names=("echosign_dev1__Status__c", "Status"),
            required_tokens=("status",),
        )
        if recipient_fields is not None
        else None,
        recipient_signed_at_field=_select_describe_field_name(
            recipient_fields,
            exact_names=("echosign_dev1__Date_Signed__c", "echosign_dev1__Signed_Date__c"),
            required_tokens=("signed", "date"),
            allowed_types=("date", "datetime"),
        )
        if recipient_fields is not None
        else None,
        recipient_email_field=_select_describe_field_name(
            recipient_fields,
            exact_names=("echosign_dev1__Email__c",),
            required_tokens=("email",),
        )
        if recipient_fields is not None
        else None,
        recipient_name_field=_select_describe_field_name(
            recipient_fields,
            exact_names=("echosign_dev1__Name__c", "Name"),
            required_tokens=("name",),
        )
        if recipient_fields is not None
        else None,
    )


def _describe_sobject_fields(
    sobject: str,
    *,
    org_alias: str | None,
) -> list[dict[str, Any]] | None:
    try:
        payload = sfdc_helpers.describe_sobject(sobject, org_alias=org_alias)
    except Exception:
        return None
    if not isinstance(payload, dict):
        return None
    result = payload.get("result")
    if not isinstance(result, dict):
        return None
    fields = result.get("fields")
    if not isinstance(fields, list):
        return None
    return [field for field in fields if isinstance(field, dict)]


def _select_describe_field_name(
    fields: list[dict[str, Any]] | None,
    *,
    exact_names: tuple[str, ...] = (),
    required_tokens: tuple[str, ...] = (),
    allowed_types: tuple[str, ...] = (),
    reference_to: str | None = None,
) -> str | None:
    if not fields:
        return None
    exact_lookup = {str(name).strip().lower() for name in exact_names if str(name).strip()}
    normalized_allowed_types = {str(name).strip().lower() for name in allowed_types if str(name).strip()}
    normalized_reference = str(reference_to or "").strip() or None
    for field in fields:
        name = str(field.get("name") or "").strip()
        if name and name.lower() in exact_lookup:
            if _describe_field_matches(
                field,
                required_tokens=required_tokens,
                allowed_types=normalized_allowed_types,
                reference_to=normalized_reference,
            ):
                return name
    ranked: list[tuple[int, str]] = []
    for field in fields:
        name = str(field.get("name") or "").strip()
        if not name:
            continue
        if not _describe_field_matches(
            field,
            required_tokens=required_tokens,
            allowed_types=normalized_allowed_types,
            reference_to=normalized_reference,
        ):
            continue
        ranked.append((_describe_field_score(field, required_tokens=required_tokens), name))
    if not ranked:
        return None
    ranked.sort(reverse=True)
    return ranked[0][1]


def _describe_field_matches(
    field: dict[str, Any],
    *,
    required_tokens: tuple[str, ...],
    allowed_types: set[str],
    reference_to: str | None,
) -> bool:
    if allowed_types:
        field_type = str(field.get("type") or "").strip().lower()
        if field_type not in allowed_types:
            return False
    if reference_to:
        references = _describe_field_reference_targets(field)
        if reference_to not in references:
            return False
    search_text = _searchable_text(
        " ".join(
            [
                str(field.get("name") or "").strip(),
                str(field.get("label") or "").strip(),
            ]
        )
    )
    return all(token in search_text for token in required_tokens)


def _describe_field_score(
    field: dict[str, Any],
    *,
    required_tokens: tuple[str, ...],
) -> int:
    search_text = _searchable_text(
        " ".join(
            [
                str(field.get("name") or "").strip(),
                str(field.get("label") or "").strip(),
            ]
        )
    )
    score = sum(5 for token in required_tokens if token and token in search_text)
    if str(field.get("name") or "").strip() == "Name":
        score -= 1
    return score


def _describe_field_reference_targets(field: dict[str, Any]) -> set[str]:
    raw = field.get("referenceTo")
    if isinstance(raw, list):
        return {str(item).strip() for item in raw if str(item).strip()}
    if isinstance(raw, str) and raw.strip():
        return {raw.strip()}
    return set()


def _build_docusign_api_client() -> DocuSignClient | None:
    try:
        return DocuSignClient()
    except SecretNotFoundError:
        return None
    except DocuSignError:
        return None


def _build_pandadoc_api_client() -> PandaDocClient | None:
    try:
        return PandaDocClient()
    except SecretNotFoundError:
        return None
    except PandaDocError:
        return None


def _build_adobe_sign_api_client() -> AdobeSignClient | None:
    try:
        return AdobeSignClient()
    except SecretNotFoundError:
        return None
    except AdobeSignError:
        return None


def _queryable_field_names(*field_names: str | None) -> list[str]:
    selected: list[str] = []
    seen: set[str] = set()
    for field_name in field_names:
        normalized = str(field_name or "").strip()
        if not normalized or normalized in seen:
            continue
        if not _QUERYABLE_FIELD_RE.fullmatch(normalized):
            continue
        seen.add(normalized)
        selected.append(normalized)
    return selected


def _load_docusign_envelope_bundles(
    *,
    opportunity_id: str,
    org_alias: str | None,
    schema: _DocuSignManagedPackageSchema,
) -> list[_DocuSignEnvelopeBundle]:
    escaped_id = sfdc_helpers.escape_soql_value(opportunity_id)
    envelope_fields = _queryable_field_names(
        "Id",
        schema.opportunity_lookup_field,
        schema.envelope_id_field,
        schema.envelope_status_field,
        schema.envelope_completed_at_field,
        schema.envelope_sent_at_field,
        schema.envelope_subject_field,
        "Name",
    )
    if not envelope_fields:
        return []
    order_field = schema.envelope_completed_at_field or schema.envelope_sent_at_field or "LastModifiedDate"
    try:
        envelope_rows = sfdc_helpers.run_soql(
            (
                f"SELECT {', '.join(envelope_fields)} "
                f"FROM {_DOCUSIGN_MANAGED_ENVELOPE_OBJECT} "
                f"WHERE {schema.opportunity_lookup_field} = '{escaped_id}' "
                f"ORDER BY {order_field} DESC LIMIT 25"
            ),
            org_alias=org_alias,
        )
    except Exception:
        return []
    if not envelope_rows:
        return []

    envelope_record_ids = [
        str(row.get("Id") or "").strip()
        for row in envelope_rows
        if str(row.get("Id") or "").strip()
    ]
    recipient_rows = _load_docusign_recipient_rows(
        envelope_record_ids=envelope_record_ids,
        org_alias=org_alias,
        schema=schema,
    )
    recipient_rows_by_envelope: dict[str, list[dict[str, Any]]] = {}
    recipient_ids: list[str] = []
    for row in recipient_rows:
        envelope_record_id = str(row.get(schema.recipient_envelope_lookup_field or "") or "").strip()
        if envelope_record_id:
            recipient_rows_by_envelope.setdefault(envelope_record_id, []).append(row)
        recipient_id = str(row.get("Id") or "").strip()
        if recipient_id:
            recipient_ids.append(recipient_id)

    tab_rows_by_envelope = _load_docusign_tab_rows_by_envelope(
        envelope_record_ids=envelope_record_ids,
        recipient_ids=recipient_ids,
        recipient_rows=recipient_rows,
        org_alias=org_alias,
        schema=schema,
    )

    bundles: list[_DocuSignEnvelopeBundle] = []
    for envelope_row in envelope_rows:
        envelope_record_id = str(envelope_row.get("Id") or "").strip()
        if not envelope_record_id:
            continue
        bundles.append(
            _DocuSignEnvelopeBundle(
                envelope_record_id=envelope_record_id,
                envelope_id=_normalize_docusign_envelope_id(
                    envelope_row.get(schema.envelope_id_field or "")
                ),
                subject=_first_non_empty(
                    envelope_row.get(schema.envelope_subject_field or ""),
                    envelope_row.get("Name"),
                ),
                status=_first_non_empty(envelope_row.get(schema.envelope_status_field or "")),
                completed_at=_normalize_date(envelope_row.get(schema.envelope_completed_at_field or "")),
                sent_at=_normalize_date(envelope_row.get(schema.envelope_sent_at_field or "")),
                envelope_row=dict(envelope_row),
                recipient_rows=list(recipient_rows_by_envelope.get(envelope_record_id, [])),
                tab_rows=list(tab_rows_by_envelope.get(envelope_record_id, [])),
            )
        )
    return bundles


def _load_docusign_recipient_rows(
    *,
    envelope_record_ids: list[str],
    org_alias: str | None,
    schema: _DocuSignManagedPackageSchema,
) -> list[dict[str, Any]]:
    if not envelope_record_ids or not schema.recipient_envelope_lookup_field:
        return []
    in_clause = ", ".join(
        f"'{sfdc_helpers.escape_soql_value(record_id)}'"
        for record_id in envelope_record_ids
    )
    select_fields = _queryable_field_names(
        "Id",
        schema.recipient_envelope_lookup_field,
        schema.recipient_status_field,
        schema.recipient_signed_at_field,
        schema.recipient_name_field,
        schema.recipient_email_field,
        schema.recipient_routing_order_field,
        "Name",
    )
    if not select_fields:
        return []
    try:
        return sfdc_helpers.run_soql(
            (
                f"SELECT {', '.join(select_fields)} "
                f"FROM {_DOCUSIGN_MANAGED_RECIPIENT_OBJECT} "
                f"WHERE {schema.recipient_envelope_lookup_field} IN ({in_clause}) "
                "LIMIT 200"
            ),
            org_alias=org_alias,
        )
    except Exception:
        return []


def _load_docusign_tab_rows_by_envelope(
    *,
    envelope_record_ids: list[str],
    recipient_ids: list[str],
    recipient_rows: list[dict[str, Any]],
    org_alias: str | None,
    schema: _DocuSignManagedPackageSchema,
) -> dict[str, list[dict[str, Any]]]:
    if not schema.tab_label_field or not schema.tab_value_field:
        return {}
    select_fields = _queryable_field_names(
        "Id",
        schema.tab_envelope_lookup_field,
        schema.tab_recipient_lookup_field,
        schema.tab_label_field,
        schema.tab_value_field,
    )
    if not select_fields:
        return {}

    raw_rows: list[dict[str, Any]]
    if schema.tab_envelope_lookup_field and envelope_record_ids:
        in_clause = ", ".join(
            f"'{sfdc_helpers.escape_soql_value(record_id)}'"
            for record_id in envelope_record_ids
        )
        try:
            raw_rows = sfdc_helpers.run_soql(
                (
                    f"SELECT {', '.join(select_fields)} "
                    f"FROM {_DOCUSIGN_MANAGED_TAB_OBJECT} "
                    f"WHERE {schema.tab_envelope_lookup_field} IN ({in_clause}) "
                    "LIMIT 500"
                ),
                org_alias=org_alias,
            )
        except Exception:
            raw_rows = []
    elif schema.tab_recipient_lookup_field and recipient_ids:
        in_clause = ", ".join(
            f"'{sfdc_helpers.escape_soql_value(record_id)}'"
            for record_id in recipient_ids
        )
        try:
            raw_rows = sfdc_helpers.run_soql(
                (
                    f"SELECT {', '.join(select_fields)} "
                    f"FROM {_DOCUSIGN_MANAGED_TAB_OBJECT} "
                    f"WHERE {schema.tab_recipient_lookup_field} IN ({in_clause}) "
                    "LIMIT 500"
                ),
                org_alias=org_alias,
            )
        except Exception:
            raw_rows = []
    else:
        raw_rows = []
    if not raw_rows:
        return {}

    recipient_to_envelope = {
        str(row.get("Id") or "").strip(): str(row.get(schema.recipient_envelope_lookup_field or "") or "").strip()
        for row in recipient_rows
        if str(row.get("Id") or "").strip()
    }
    grouped: dict[str, list[dict[str, Any]]] = {}
    for row in raw_rows:
        envelope_record_id = str(row.get(schema.tab_envelope_lookup_field or "") or "").strip()
        if not envelope_record_id and schema.tab_recipient_lookup_field:
            recipient_id = str(row.get(schema.tab_recipient_lookup_field or "") or "").strip()
            envelope_record_id = recipient_to_envelope.get(recipient_id, "")
        if envelope_record_id:
            grouped.setdefault(envelope_record_id, []).append(row)
    return grouped


def _select_docusign_bundle_for_candidate(
    *,
    candidate: CloseDocumentCandidateV1,
    row: dict[str, Any],
    version_bytes: bytes | None,
    bundles: list[_DocuSignEnvelopeBundle],
) -> _DocuSignEnvelopeBundle | None:
    if not bundles:
        return None
    if len(bundles) == 1:
        return bundles[0]
    envelope_hints = _extract_docusign_envelope_id_hints(
        candidate=candidate,
        row=row,
        version_bytes=version_bytes,
    )
    hint_matches = [
        bundle
        for bundle in bundles
        if bundle.envelope_id and bundle.envelope_id in envelope_hints
    ]
    if len(hint_matches) == 1:
        return hint_matches[0]

    candidate_title = _title_match_tokens(candidate.title)
    scored = [
        (_title_match_score(candidate_title, _title_match_tokens(bundle.subject)), bundle)
        for bundle in bundles
    ]
    scored = [item for item in scored if item[0] > 0.0]
    if not scored:
        return None
    scored.sort(key=lambda item: item[0], reverse=True)
    if len(scored) == 1 and scored[0][0] >= 0.5:
        return scored[0][1]
    if len(scored) > 1 and scored[0][0] >= 0.65 and scored[0][0] > scored[1][0]:
        return scored[0][1]
    return None


def _extract_docusign_envelope_id_hints(
    *,
    candidate: CloseDocumentCandidateV1,
    row: dict[str, Any],
    version_bytes: bytes | None,
) -> set[str]:
    del version_bytes
    hints: set[str] = set()
    texts = (
        candidate.title,
        str(row.get("Title") or "").strip(),
        str(row.get("Description") or "").strip(),
        str(row.get("PathOnClient") or "").strip(),
    )
    for text in texts:
        for match in _DOCUSIGN_ENVELOPE_ID_RE.finditer(str(text or "")):
            normalized = _normalize_docusign_envelope_id(match.group(1))
            if normalized:
                hints.add(normalized)
    return hints


def _normalize_docusign_envelope_id(value: Any) -> str | None:
    normalized = re.sub(r"[^A-Za-z0-9]", "", str(value or "")).strip().upper()
    if len(normalized) < 8:
        return None
    return normalized


def _title_match_tokens(value: Any) -> set[str]:
    text = _searchable_text(value)
    return {
        token
        for token in text.split()
        if token and token not in {"docusign", "envelope", "pdf", "signed", "document", "doc"}
    }


def _title_match_score(left: set[str], right: set[str]) -> float:
    if not left or not right:
        return 0.0
    union = left | right
    if not union:
        return 0.0
    return len(left & right) / len(union)


def _searchable_text(value: Any) -> str:
    text = str(value or "")
    text = re.sub(r"([a-z0-9])([A-Z])", r"\1 \2", text)
    text = re.sub(r"[^A-Za-z0-9]+", " ", text)
    return " ".join(text.lower().split())


def _first_non_empty(*values: Any) -> str | None:
    for value in values:
        normalized = str(value or "").strip()
        if normalized:
            return normalized
    return None


def _extract_docusign_managed_package_evidence_from_bundle(
    *,
    candidate: CloseDocumentCandidateV1,
    row: dict[str, Any],
    bundle: _DocuSignEnvelopeBundle,
    schema: _DocuSignManagedPackageSchema,
    content_hash: str | None,
) -> CloseDocumentEvidenceV1 | None:
    version_id = str(row.get("Id") or "").strip() or None
    title = str(row.get("Title") or candidate.title or "").strip() or candidate.title
    source_fingerprint = _docusign_bundle_source_fingerprint(bundle=bundle)

    fields: list[EvidenceFieldV1] = []

    signature_field = _build_provider_field(
        candidate=candidate,
        version_id=version_id,
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        canonical_key="signature_present",
        parsed=_docusign_signature_present(bundle=bundle, schema=schema),
        confidence=0.99,
    )
    if signature_field is not None:
        fields.append(signature_field)

    signer_count_field = _build_provider_field(
        candidate=candidate,
        version_id=version_id,
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        canonical_key="signer_count",
        parsed=_docusign_signer_count(bundle=bundle, schema=schema),
        confidence=0.97,
    )
    if signer_count_field is not None:
        fields.append(signer_count_field)

    executed_field = _build_provider_field(
        candidate=candidate,
        version_id=version_id,
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        canonical_key="executed_at",
        parsed=_docusign_executed_at(bundle=bundle, schema=schema),
        confidence=0.98 if bundle.completed_at else 0.96,
    )
    if executed_field is not None:
        fields.append(executed_field)

    tab_mappings: tuple[tuple[CloseDocumentCanonicalFieldKey, tuple[str, ...], str], ...] = (
        ("effective_date", ("effective date", "effective"), "date"),
        ("service_start_date", ("service start", "start date", "term start", "subscription start"), "date"),
        ("service_end_date", ("service end", "end date", "term end", "subscription end"), "date"),
        ("amount", ("amount", "tcv", "contract value", "total value"), "currency"),
        ("term_months", ("term months", "term", "subscription term"), "int"),
        ("legal_entity", ("legal entity", "contracting entity", "bill to entity"), "text"),
        ("product_summary", ("product", "plan", "sku"), "text"),
    )
    for canonical_key, labels, value_type in tab_mappings:
        field = _build_provider_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key=canonical_key,
            parsed=_parse_docusign_tab_field(
                tab_rows=bundle.tab_rows,
                schema=schema,
                labels=labels,
                value_type=value_type,
            ),
            confidence=0.96,
        )
        if field is not None:
            fields.append(field)

    if not fields:
        return None
    confidence = sum(field.field_confidence for field in fields) / max(1, len(fields))
    return CloseDocumentEvidenceV1(
        evidence_id=_evidence_id(
            method="provider_api",
            document_id=candidate.document_id,
            version_id=version_id,
        ),
        document_id=candidate.document_id,
        version_id=version_id,
        title=title,
        provider_hint="docusign",
        extraction_method="provider_api",
        confidence=max(0.0, min(0.995, confidence)),
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        signature_present=_field_value(fields, "signature_present"),
        signer_count=_field_value(fields, "signer_count"),
        executed_at=_field_value(fields, "executed_at"),
        effective_date=_field_value(fields, "effective_date"),
        service_start_date=_field_value(fields, "service_start_date"),
        service_end_date=_field_value(fields, "service_end_date"),
        amount=_field_value(fields, "amount"),
        term_months=_field_value(fields, "term_months"),
        legal_entity=_field_value(fields, "legal_entity"),
        product_summary=_field_value(fields, "product_summary"),
        summary=_evidence_summary(
            title=title,
            provider_hint="docusign",
            extraction_method="provider_api",
            signature_present=_field_value(fields, "signature_present"),
            amount=_field_value(fields, "amount"),
            term_months=_field_value(fields, "term_months"),
            service_start_date=_field_value(fields, "service_start_date"),
            service_end_date=_field_value(fields, "service_end_date"),
        ),
        fields=fields,
    )


def _docusign_bundle_source_fingerprint(*, bundle: _DocuSignEnvelopeBundle) -> str:
    payload = {
        "envelope": bundle.envelope_row,
        "recipients": bundle.recipient_rows,
        "tabs": bundle.tab_rows,
    }
    digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest()
    return f"sha256:{digest}"


def _build_provider_field(
    *,
    candidate: CloseDocumentCandidateV1,
    version_id: str | None,
    content_hash: str | None,
    source_fingerprint: str,
    canonical_key: CloseDocumentCanonicalFieldKey,
    parsed: _ParsedField | None,
    confidence: float,
) -> EvidenceFieldV1 | None:
    if parsed is None:
        return None
    return EvidenceFieldV1(
        canonical_key=canonical_key,
        extracted_value=parsed.value,
        extraction_method="provider_api",
        field_confidence=max(0.0, min(0.995, confidence)),
        source_locator=parsed.source_locator,
        document_id=candidate.document_id,
        version_id=version_id,
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        raw_snippet=parsed.raw_snippet,
    )


def _extract_docusign_api_evidence_from_snapshot(
    *,
    candidate: CloseDocumentCandidateV1,
    row: dict[str, Any],
    snapshot: DocuSignEnvelopeSnapshot,
    content_hash: str | None,
) -> CloseDocumentEvidenceV1 | None:
    version_id = str(row.get("Id") or "").strip() or None
    title = str(row.get("Title") or candidate.title or "").strip() or candidate.title
    source_fingerprint = _docusign_api_source_fingerprint(snapshot=snapshot)
    fields: list[EvidenceFieldV1] = []

    for canonical_key, parsed, confidence in (
        (
            "signature_present",
            _build_docusign_api_status_field(
                envelope=snapshot.envelope,
                recipients=snapshot.recipients,
            ),
            0.99,
        ),
        (
            "signer_count",
            _build_docusign_api_signer_count(
                envelope=snapshot.envelope,
                recipients=snapshot.recipients,
            ),
            0.97,
        ),
        (
            "executed_at",
            _build_docusign_api_executed_at(
                envelope=snapshot.envelope,
                recipients=snapshot.recipients,
            ),
            0.98 if _normalize_date(snapshot.envelope.get("completedDateTime")) else 0.96,
        ),
    ):
        field = _build_provider_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key=canonical_key,
            parsed=parsed,
            confidence=confidence,
        )
        if field is not None:
            fields.append(field)

    form_data_mappings: tuple[tuple[CloseDocumentCanonicalFieldKey, tuple[str, ...], str], ...] = (
        ("effective_date", ("effective date", "effective"), "date"),
        ("service_start_date", ("service start", "start date", "term start", "subscription start"), "date"),
        ("service_end_date", ("service end", "end date", "term end", "subscription end"), "date"),
        ("amount", ("amount", "tcv", "contract value", "total value"), "currency"),
        ("term_months", ("term months", "term", "subscription term"), "int"),
        ("legal_entity", ("legal entity", "contracting entity", "bill to entity"), "text"),
        ("product_summary", ("product", "plan", "sku"), "text"),
    )
    for canonical_key, labels, value_type in form_data_mappings:
        field = _build_provider_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key=canonical_key,
            parsed=_parse_docusign_form_data_field(
                form_data_rows=snapshot.form_data,
                labels=labels,
                value_type=value_type,
            ),
            confidence=0.96,
        )
        if field is not None:
            fields.append(field)

    if not fields:
        return None
    confidence = sum(field.field_confidence for field in fields) / max(1, len(fields))
    return CloseDocumentEvidenceV1(
        evidence_id=_evidence_id(
            method="provider_api",
            document_id=candidate.document_id,
            version_id=version_id,
        ),
        document_id=candidate.document_id,
        version_id=version_id,
        title=title,
        provider_hint="docusign",
        extraction_method="provider_api",
        confidence=max(0.0, min(0.995, confidence)),
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        signature_present=_field_value(fields, "signature_present"),
        signer_count=_field_value(fields, "signer_count"),
        executed_at=_field_value(fields, "executed_at"),
        effective_date=_field_value(fields, "effective_date"),
        service_start_date=_field_value(fields, "service_start_date"),
        service_end_date=_field_value(fields, "service_end_date"),
        amount=_field_value(fields, "amount"),
        term_months=_field_value(fields, "term_months"),
        legal_entity=_field_value(fields, "legal_entity"),
        product_summary=_field_value(fields, "product_summary"),
        summary=_evidence_summary(
            title=title,
            provider_hint="docusign",
            extraction_method="provider_api",
            signature_present=_field_value(fields, "signature_present"),
            amount=_field_value(fields, "amount"),
            term_months=_field_value(fields, "term_months"),
            service_start_date=_field_value(fields, "service_start_date"),
            service_end_date=_field_value(fields, "service_end_date"),
        ),
        fields=fields,
    )


def _docusign_api_source_fingerprint(*, snapshot: DocuSignEnvelopeSnapshot) -> str:
    payload = {
        "envelope": snapshot.envelope,
        "recipients": snapshot.recipients,
        "form_data": snapshot.form_data,
    }
    digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest()
    return f"sha256:{digest}"


def _build_docusign_api_status_field(
    *,
    envelope: dict[str, Any],
    recipients: list[dict[str, Any]],
) -> _ParsedField | None:
    normalized_status = _provider_signature_status(envelope.get("status"))
    if normalized_status is not None:
        return _ParsedField(
            normalized_status,
            "docusign.rest.envelope.status",
            f"status={envelope.get('status')}",
        )
    signed_count = 0
    unsigned_count = 0
    for recipient in recipients:
        status = _provider_signature_status(recipient.get("status"))
        if status is True:
            signed_count += 1
        elif status is False:
            unsigned_count += 1
    if signed_count > 0 and unsigned_count == 0:
        return _ParsedField(
            True,
            "docusign.rest.recipients.status",
            f"signed_recipients={signed_count}",
        )
    if unsigned_count > 0:
        return _ParsedField(
            False,
            "docusign.rest.recipients.status",
            f"unsigned_recipients={unsigned_count}",
        )
    return None


def _build_docusign_api_signer_count(
    *,
    envelope: dict[str, Any],
    recipients: list[dict[str, Any]],
) -> _ParsedField | None:
    signed_count = 0
    total_count = 0
    for recipient in recipients:
        total_count += 1
        if _provider_signature_status(recipient.get("status")) is True:
            signed_count += 1
    if signed_count > 0:
        return _ParsedField(
            signed_count,
            "docusign.rest.recipients.status",
            f"signed_recipients={signed_count}",
        )
    signature_present = _provider_signature_status(envelope.get("status"))
    if signature_present is False and total_count > 0:
        return _ParsedField(0, "docusign.rest.recipients.status", "signed_recipients=0")
    if signature_present is True and total_count > 0:
        return _ParsedField(
            total_count,
            "docusign.rest.recipients.status",
            f"signed_recipients~={total_count}",
        )
    return None


def _build_docusign_api_executed_at(
    *,
    envelope: dict[str, Any],
    recipients: list[dict[str, Any]],
) -> _ParsedField | None:
    completed_at = _normalize_date(
        envelope.get("completedDateTime")
        or envelope.get("completedDate")
        or envelope.get("statusChangedDateTime")
    )
    if completed_at:
        return _ParsedField(
            completed_at,
            "docusign.rest.envelope.completedDateTime",
            f"completedDateTime={completed_at}",
        )
    signed_dates = sorted(
        date
        for date in (
            _normalize_date(
                recipient.get("signedDateTime")
                or recipient.get("dateSigned")
                or recipient.get("deliveredDateTime")
            )
            for recipient in recipients
        )
        if date
    )
    if not signed_dates:
        return None
    return _ParsedField(
        signed_dates[-1],
        "docusign.rest.recipients.signedDateTime",
        f"signedDateTime={signed_dates[-1]}",
    )


def _parse_docusign_form_data_field(
    *,
    form_data_rows: list[dict[str, Any]],
    labels: tuple[str, ...],
    value_type: Literal["currency", "date", "int", "text"],
) -> _ParsedField | None:
    match = _docusign_form_data_match(form_data_rows=form_data_rows, labels=labels)
    if match is None:
        return None
    if value_type == "currency":
        value = _coerce_float(match.raw_value)
    elif value_type == "date":
        value = _normalize_date(match.raw_value)
    elif value_type == "int":
        value = _coerce_int(match.raw_value)
    else:
        value = str(match.raw_value).strip() or None
    if value in (None, ""):
        return None
    return _ParsedField(value, match.source_locator, match.raw_snippet)


def _docusign_form_data_match(
    *,
    form_data_rows: list[dict[str, Any]],
    labels: tuple[str, ...],
) -> _LabeledMatch | None:
    best: tuple[int, _LabeledMatch] | None = None
    for row in form_data_rows:
        label = _first_non_empty(
            row.get("name"),
            row.get("tabLabel"),
            row.get("originalName"),
            row.get("label"),
        )
        value = _first_non_empty(
            row.get("value"),
            row.get("fieldValue"),
            row.get("originalValue"),
        )
        if not label or not value:
            continue
        searchable_label = _searchable_text(label)
        for target in labels:
            target_text = _searchable_text(target)
            if not target_text:
                continue
            if searchable_label == target_text:
                score = 100
            elif target_text in searchable_label:
                score = 80
            elif all(token in searchable_label for token in target_text.split()):
                score = 60
            else:
                continue
            match = _LabeledMatch(
                label=label,
                raw_value=value,
                raw_snippet=f"{label}={value}",
                source_locator=f"docusign.rest.form_data:{label}",
            )
            if best is None or score > best[0]:
                best = (score, match)
    return best[1] if best is not None else None


def _docusign_signature_present(
    *,
    bundle: _DocuSignEnvelopeBundle,
    schema: _DocuSignManagedPackageSchema,
) -> _ParsedField | None:
    normalized_status = _provider_signature_status(bundle.status)
    if normalized_status is not None and schema.envelope_status_field:
        return _ParsedField(
            normalized_status,
            f"{_DOCUSIGN_MANAGED_ENVELOPE_OBJECT}.{schema.envelope_status_field}",
            f"{schema.envelope_status_field}={bundle.status}",
        )
    recipient_status_field = schema.recipient_status_field or "status"
    signed_count = 0
    unsigned_count = 0
    for recipient_row in bundle.recipient_rows:
        status = _provider_signature_status(recipient_row.get(schema.recipient_status_field or ""))
        if status is True:
            signed_count += 1
        elif status is False:
            unsigned_count += 1
    if signed_count > 0 and unsigned_count == 0:
        return _ParsedField(
            True,
            f"{_DOCUSIGN_MANAGED_RECIPIENT_OBJECT}.{recipient_status_field}",
            f"{signed_count} recipient(s) signed",
        )
    if unsigned_count > 0:
        return _ParsedField(
            False,
            f"{_DOCUSIGN_MANAGED_RECIPIENT_OBJECT}.{recipient_status_field}",
            f"{unsigned_count} recipient(s) not signed",
        )
    return None


def _docusign_signer_count(
    *,
    bundle: _DocuSignEnvelopeBundle,
    schema: _DocuSignManagedPackageSchema,
) -> _ParsedField | None:
    signed_count = 0
    total_count = 0
    for recipient_row in bundle.recipient_rows:
        total_count += 1
        if _provider_signature_status(recipient_row.get(schema.recipient_status_field or "")) is True:
            signed_count += 1
    if signed_count > 0:
        return _ParsedField(
            signed_count,
            f"{_DOCUSIGN_MANAGED_RECIPIENT_OBJECT}.{schema.recipient_status_field or 'status'}",
            f"signed_recipients={signed_count}",
        )
    signature_present = _provider_signature_status(bundle.status)
    if signature_present is False and total_count > 0:
        return _ParsedField(
            0,
            f"{_DOCUSIGN_MANAGED_RECIPIENT_OBJECT}.{schema.recipient_status_field or 'status'}",
            "signed_recipients=0",
        )
    if signature_present is True and total_count > 0:
        return _ParsedField(
            total_count,
            f"{_DOCUSIGN_MANAGED_RECIPIENT_OBJECT}.{schema.recipient_status_field or 'status'}",
            f"signed_recipients~={total_count}",
        )
    return None


def _docusign_executed_at(
    *,
    bundle: _DocuSignEnvelopeBundle,
    schema: _DocuSignManagedPackageSchema,
) -> _ParsedField | None:
    if bundle.completed_at and schema.envelope_completed_at_field:
        return _ParsedField(
            bundle.completed_at,
            f"{_DOCUSIGN_MANAGED_ENVELOPE_OBJECT}.{schema.envelope_completed_at_field}",
            f"{schema.envelope_completed_at_field}={bundle.completed_at}",
        )
    signed_dates = [
        _normalize_date(row.get(schema.recipient_signed_at_field or ""))
        for row in bundle.recipient_rows
    ]
    normalized_dates = sorted(date for date in signed_dates if date)
    if not normalized_dates:
        return None
    return _ParsedField(
        normalized_dates[-1],
        f"{_DOCUSIGN_MANAGED_RECIPIENT_OBJECT}.{schema.recipient_signed_at_field or 'signed_at'}",
        f"signed_at={normalized_dates[-1]}",
    )


def _provider_signature_status(value: Any) -> bool | None:
    normalized = _searchable_text(value)
    if not normalized:
        return None
    if any(token in normalized for token in _DOCUSIGN_UNSIGNED_STATUS_TOKENS):
        return False
    if any(token in normalized for token in _SIGNED_STATUS_TOKENS):
        return True
    return None


def _parse_docusign_tab_field(
    *,
    tab_rows: list[dict[str, Any]],
    schema: _DocuSignManagedPackageSchema,
    labels: tuple[str, ...],
    value_type: Literal["currency", "date", "int", "text"],
) -> _ParsedField | None:
    match = _docusign_tab_match(tab_rows=tab_rows, schema=schema, labels=labels)
    if match is None:
        return None
    if value_type == "currency":
        value = _coerce_float(match.raw_value)
    elif value_type == "date":
        value = _normalize_date(match.raw_value)
    elif value_type == "int":
        value = _coerce_int(match.raw_value)
    else:
        value = str(match.raw_value).strip() or None
    if value in (None, ""):
        return None
    return _ParsedField(value, match.source_locator, match.raw_snippet)


def _docusign_tab_match(
    *,
    tab_rows: list[dict[str, Any]],
    schema: _DocuSignManagedPackageSchema,
    labels: tuple[str, ...],
) -> _LabeledMatch | None:
    if not schema.tab_label_field or not schema.tab_value_field:
        return None
    best: tuple[int, _LabeledMatch] | None = None
    for row in tab_rows:
        label = str(row.get(schema.tab_label_field) or "").strip()
        value = str(row.get(schema.tab_value_field) or "").strip()
        if not label or not value:
            continue
        searchable_label = _searchable_text(label)
        for target in labels:
            target_text = _searchable_text(target)
            if not target_text:
                continue
            if searchable_label == target_text:
                score = 100
            elif target_text in searchable_label:
                score = 80
            elif all(token in searchable_label for token in target_text.split()):
                score = 60
            else:
                continue
            match = _LabeledMatch(
                label=label,
                raw_value=value,
                raw_snippet=f"{label}={value}",
                source_locator=f"{_DOCUSIGN_MANAGED_TAB_OBJECT}.{schema.tab_label_field}:{label}",
            )
            if best is None or score > best[0]:
                best = (score, match)
    return best[1] if best is not None else None


def _load_pandadoc_document_rows(
    *,
    opportunity_id: str,
    org_alias: str | None,
    schema: _PandaDocDocumentSchema,
) -> list[dict[str, Any]]:
    select_fields = _queryable_field_names(
        "Id",
        schema.opportunity_lookup_field,
        schema.parent_id_field,
        schema.uuid_field,
        schema.status_field,
        schema.completed_at_field,
        schema.sent_at_field,
        schema.title_field,
        schema.amount_field,
        schema.effective_date_field,
        schema.service_start_date_field,
        schema.service_end_date_field,
        schema.term_months_field,
        schema.legal_entity_field,
        schema.product_summary_field,
        "Name",
    )
    if not select_fields:
        return []
    escaped_id = sfdc_helpers.escape_soql_value(opportunity_id)
    where_clauses: list[str] = []
    if schema.opportunity_lookup_field:
        where_clauses.append(f"{schema.opportunity_lookup_field} = '{escaped_id}'")
    if schema.parent_id_field:
        where_clauses.append(f"{schema.parent_id_field} = '{escaped_id}'")
    if not where_clauses:
        return []
    order_field = schema.completed_at_field or schema.sent_at_field or "LastModifiedDate"
    try:
        return sfdc_helpers.run_soql(
            (
                f"SELECT {', '.join(select_fields)} "
                f"FROM {_PANDADOC_DOCUMENT_OBJECT} "
                f"WHERE {' OR '.join(where_clauses)} "
                f"ORDER BY {order_field} DESC LIMIT 25"
            ),
            org_alias=org_alias,
        )
    except Exception:
        return []


def _select_pandadoc_row_for_candidate(
    *,
    candidate: CloseDocumentCandidateV1,
    row: dict[str, Any],
    version_bytes: bytes | None,
    document_rows: list[dict[str, Any]],
    schema: _PandaDocDocumentSchema,
) -> dict[str, Any] | None:
    del version_bytes
    if not document_rows:
        return None
    if len(document_rows) == 1:
        return document_rows[0]
    uuid_hints = _extract_pandadoc_uuid_hints(candidate=candidate, row=row)
    uuid_matches = [
        document_row
        for document_row in document_rows
        if _normalize_pandadoc_uuid(document_row.get(schema.uuid_field or "")) in uuid_hints
    ]
    if len(uuid_matches) == 1:
        return uuid_matches[0]

    candidate_title = _title_match_tokens(candidate.title)
    scored = [
        (
            _title_match_score(
                candidate_title,
                _title_match_tokens(_pandadoc_row_title(document_row=document_row, schema=schema)),
            ),
            document_row,
        )
        for document_row in document_rows
    ]
    scored = [item for item in scored if item[0] > 0.0]
    if not scored:
        return None
    scored.sort(key=lambda item: item[0], reverse=True)
    if len(scored) == 1 and scored[0][0] >= 0.5:
        return scored[0][1]
    if len(scored) > 1 and scored[0][0] >= 0.65 and scored[0][0] > scored[1][0]:
        return scored[0][1]
    return None


def _extract_pandadoc_uuid_hints(
    *,
    candidate: CloseDocumentCandidateV1,
    row: dict[str, Any],
) -> set[str]:
    hints: set[str] = set()
    texts = (
        candidate.title,
        str(row.get("Title") or "").strip(),
        str(row.get("Description") or "").strip(),
        str(row.get("PathOnClient") or "").strip(),
    )
    for text in texts:
        for match in _PANDADOC_UUID_RE.finditer(str(text or "")):
            normalized = _normalize_pandadoc_uuid(match.group(1))
            if normalized:
                hints.add(normalized)
    return hints


def _normalize_pandadoc_uuid(value: Any) -> str | None:
    normalized = re.sub(r"[^A-Za-z0-9]", "", str(value or "")).strip().upper()
    if len(normalized) < 8:
        return None
    return normalized


def _pandadoc_row_title(*, document_row: dict[str, Any], schema: _PandaDocDocumentSchema) -> str:
    return (
        _first_non_empty(
            document_row.get(schema.title_field or ""),
            document_row.get("Name"),
        )
        or "PandaDoc document"
    )


def _extract_pandadoc_document_evidence_from_row(
    *,
    candidate: CloseDocumentCandidateV1,
    content_version_row: dict[str, Any],
    document_row: dict[str, Any],
    schema: _PandaDocDocumentSchema,
    content_hash: str | None,
) -> CloseDocumentEvidenceV1 | None:
    version_id = str(content_version_row.get("Id") or "").strip() or None
    title = str(content_version_row.get("Title") or candidate.title or "").strip() or candidate.title
    source_fingerprint = _pandadoc_row_source_fingerprint(document_row=document_row)
    fields: list[EvidenceFieldV1] = []

    provider_fields: tuple[tuple[CloseDocumentCanonicalFieldKey, _ParsedField | None], ...] = (
        (
            "signature_present",
            _build_pandadoc_status_field(
                raw_status=document_row.get(schema.status_field or ""),
                schema=schema,
            ),
        ),
        (
            "executed_at",
            _build_pandadoc_date_field(
                value=document_row.get(schema.completed_at_field or ""),
                source_locator=f"{_PANDADOC_DOCUMENT_OBJECT}.{schema.completed_at_field}"
                if schema.completed_at_field
                else None,
            ),
        ),
        (
            "effective_date",
            _build_pandadoc_date_field(
                value=document_row.get(schema.effective_date_field or ""),
                source_locator=f"{_PANDADOC_DOCUMENT_OBJECT}.{schema.effective_date_field}"
                if schema.effective_date_field
                else None,
            ),
        ),
        (
            "service_start_date",
            _build_pandadoc_date_field(
                value=document_row.get(schema.service_start_date_field or ""),
                source_locator=f"{_PANDADOC_DOCUMENT_OBJECT}.{schema.service_start_date_field}"
                if schema.service_start_date_field
                else None,
            ),
        ),
        (
            "service_end_date",
            _build_pandadoc_date_field(
                value=document_row.get(schema.service_end_date_field or ""),
                source_locator=f"{_PANDADOC_DOCUMENT_OBJECT}.{schema.service_end_date_field}"
                if schema.service_end_date_field
                else None,
            ),
        ),
        (
            "amount",
            _build_pandadoc_numeric_field(
                value=document_row.get(schema.amount_field or ""),
                source_locator=f"{_PANDADOC_DOCUMENT_OBJECT}.{schema.amount_field}"
                if schema.amount_field
                else None,
                as_int=False,
            ),
        ),
        (
            "term_months",
            _build_pandadoc_numeric_field(
                value=document_row.get(schema.term_months_field or ""),
                source_locator=f"{_PANDADOC_DOCUMENT_OBJECT}.{schema.term_months_field}"
                if schema.term_months_field
                else None,
                as_int=True,
            ),
        ),
        (
            "legal_entity",
            _build_pandadoc_text_field(
                value=document_row.get(schema.legal_entity_field or ""),
                source_locator=f"{_PANDADOC_DOCUMENT_OBJECT}.{schema.legal_entity_field}"
                if schema.legal_entity_field
                else None,
            ),
        ),
        (
            "product_summary",
            _build_pandadoc_text_field(
                value=document_row.get(schema.product_summary_field or ""),
                source_locator=f"{_PANDADOC_DOCUMENT_OBJECT}.{schema.product_summary_field}"
                if schema.product_summary_field
                else None,
            ),
        ),
    )
    for canonical_key, parsed in provider_fields:
        field = _build_provider_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key=canonical_key,
            parsed=parsed,
            confidence=0.98 if canonical_key == "signature_present" else 0.95,
        )
        if field is not None:
            fields.append(field)
    if not fields:
        return None
    confidence = sum(field.field_confidence for field in fields) / max(1, len(fields))
    return CloseDocumentEvidenceV1(
        evidence_id=_evidence_id(
            method="provider_api",
            document_id=candidate.document_id,
            version_id=version_id,
        ),
        document_id=candidate.document_id,
        version_id=version_id,
        title=title,
        provider_hint="pandadoc",
        extraction_method="provider_api",
        confidence=max(0.0, min(0.99, confidence)),
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        signature_present=_field_value(fields, "signature_present"),
        signer_count=_field_value(fields, "signer_count"),
        executed_at=_field_value(fields, "executed_at"),
        effective_date=_field_value(fields, "effective_date"),
        service_start_date=_field_value(fields, "service_start_date"),
        service_end_date=_field_value(fields, "service_end_date"),
        amount=_field_value(fields, "amount"),
        term_months=_field_value(fields, "term_months"),
        legal_entity=_field_value(fields, "legal_entity"),
        product_summary=_field_value(fields, "product_summary"),
        summary=_evidence_summary(
            title=title,
            provider_hint="pandadoc",
            extraction_method="provider_api",
            signature_present=_field_value(fields, "signature_present"),
            amount=_field_value(fields, "amount"),
            term_months=_field_value(fields, "term_months"),
            service_start_date=_field_value(fields, "service_start_date"),
            service_end_date=_field_value(fields, "service_end_date"),
        ),
        fields=fields,
    )


def _extract_pandadoc_api_evidence_from_snapshot(
    *,
    candidate: CloseDocumentCandidateV1,
    content_version_row: dict[str, Any],
    snapshot: PandaDocDocumentSnapshot,
    content_hash: str | None,
) -> CloseDocumentEvidenceV1 | None:
    version_id = str(content_version_row.get("Id") or "").strip() or None
    title = str(content_version_row.get("Title") or candidate.title or "").strip() or candidate.title
    source_fingerprint = _pandadoc_api_source_fingerprint(snapshot=snapshot)
    details = snapshot.details
    api_rows = _pandadoc_api_field_rows(snapshot=snapshot)
    fields: list[EvidenceFieldV1] = []

    provider_fields: tuple[tuple[CloseDocumentCanonicalFieldKey, _ParsedField | None, float], ...] = (
        (
            "signature_present",
            _build_pandadoc_api_status_field(details=details),
            0.99,
        ),
        (
            "signer_count",
            _build_pandadoc_api_signer_count_field(details=details),
            0.97,
        ),
        (
            "executed_at",
            _build_pandadoc_api_executed_at_field(details=details),
            0.98 if _normalize_date(details.get("date_completed")) else 0.96,
        ),
        (
            "amount",
            _build_pandadoc_api_amount_field(details=details),
            0.97,
        ),
        (
            "effective_date",
            _parse_pandadoc_api_named_field(api_rows=api_rows, labels=("effective date", "effective"), value_type="date"),
            0.96,
        ),
        (
            "service_start_date",
            _parse_pandadoc_api_named_field(
                api_rows=api_rows,
                labels=("service start", "start date", "term start", "subscription start"),
                value_type="date",
            ),
            0.96,
        ),
        (
            "service_end_date",
            _parse_pandadoc_api_named_field(
                api_rows=api_rows,
                labels=("service end", "end date", "term end", "subscription end"),
                value_type="date",
            ),
            0.96,
        ),
        (
            "term_months",
            _parse_pandadoc_api_named_field(
                api_rows=api_rows,
                labels=("term months", "term", "subscription term"),
                value_type="int",
            ),
            0.96,
        ),
        (
            "legal_entity",
            _parse_pandadoc_api_named_field(
                api_rows=api_rows,
                labels=("legal entity", "contracting entity", "bill to entity"),
                value_type="text",
            ),
            0.95,
        ),
        (
            "product_summary",
            _build_pandadoc_api_product_summary_field(details=details)
            or _parse_pandadoc_api_named_field(
                api_rows=api_rows,
                labels=("product", "plan", "sku"),
                value_type="text",
            ),
            0.94,
        ),
    )
    for canonical_key, parsed, confidence in provider_fields:
        field = _build_provider_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key=canonical_key,
            parsed=parsed,
            confidence=confidence,
        )
        if field is not None:
            fields.append(field)

    if not fields:
        return None
    confidence = sum(field.field_confidence for field in fields) / max(1, len(fields))
    return CloseDocumentEvidenceV1(
        evidence_id=_evidence_id(
            method="provider_api",
            document_id=candidate.document_id,
            version_id=version_id,
        ),
        document_id=candidate.document_id,
        version_id=version_id,
        title=title,
        provider_hint="pandadoc",
        extraction_method="provider_api",
        confidence=max(0.0, min(0.99, confidence)),
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        signature_present=_field_value(fields, "signature_present"),
        signer_count=_field_value(fields, "signer_count"),
        executed_at=_field_value(fields, "executed_at"),
        effective_date=_field_value(fields, "effective_date"),
        service_start_date=_field_value(fields, "service_start_date"),
        service_end_date=_field_value(fields, "service_end_date"),
        amount=_field_value(fields, "amount"),
        term_months=_field_value(fields, "term_months"),
        legal_entity=_field_value(fields, "legal_entity"),
        product_summary=_field_value(fields, "product_summary"),
        summary=_evidence_summary(
            title=title,
            provider_hint="pandadoc",
            extraction_method="provider_api",
            signature_present=_field_value(fields, "signature_present"),
            amount=_field_value(fields, "amount"),
            term_months=_field_value(fields, "term_months"),
            service_start_date=_field_value(fields, "service_start_date"),
            service_end_date=_field_value(fields, "service_end_date"),
        ),
        fields=fields,
    )


def _pandadoc_row_source_fingerprint(*, document_row: dict[str, Any]) -> str:
    digest = hashlib.sha256(json.dumps(document_row, sort_keys=True).encode("utf-8")).hexdigest()
    return f"sha256:{digest}"


def _pandadoc_api_source_fingerprint(*, snapshot: PandaDocDocumentSnapshot) -> str:
    payload = {
        "details": snapshot.details,
        "fields": snapshot.fields,
    }
    digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest()
    return f"sha256:{digest}"


def _build_pandadoc_status_field(
    *,
    raw_status: Any,
    schema: _PandaDocDocumentSchema,
) -> _ParsedField | None:
    normalized = _pandadoc_signature_status(raw_status)
    if normalized is None or not schema.status_field:
        return None
    return _ParsedField(
        normalized,
        f"{_PANDADOC_DOCUMENT_OBJECT}.{schema.status_field}",
        f"{schema.status_field}={raw_status}",
    )


def _build_pandadoc_date_field(*, value: Any, source_locator: str | None) -> _ParsedField | None:
    normalized = _normalize_date(value)
    if normalized is None or not source_locator:
        return None
    return _ParsedField(normalized, source_locator, f"{source_locator}={normalized}")


def _build_pandadoc_numeric_field(
    *,
    value: Any,
    source_locator: str | None,
    as_int: bool,
) -> _ParsedField | None:
    if not source_locator:
        return None
    normalized = _coerce_int(value) if as_int else _coerce_float(value)
    if normalized is None:
        return None
    return _ParsedField(normalized, source_locator, f"{source_locator}={value}")


def _build_pandadoc_text_field(*, value: Any, source_locator: str | None) -> _ParsedField | None:
    normalized = str(value or "").strip()
    if not normalized or not source_locator:
        return None
    return _ParsedField(normalized, source_locator, f"{source_locator}={normalized}")


def _build_pandadoc_api_status_field(*, details: dict[str, Any]) -> _ParsedField | None:
    raw_status = details.get("status")
    normalized = _pandadoc_signature_status(raw_status)
    if normalized is None:
        return None
    return _ParsedField(
        normalized,
        "pandadoc.rest.details.status",
        f"status={raw_status}",
    )


def _build_pandadoc_api_signer_count_field(*, details: dict[str, Any]) -> _ParsedField | None:
    recipients = details.get("recipients")
    if not isinstance(recipients, list):
        recipients = []
    signed_count = 0
    total_count = 0
    for recipient in recipients:
        if not isinstance(recipient, dict):
            continue
        total_count += 1
        if _pandadoc_signature_status(recipient.get("status")) is True:
            signed_count += 1
    if signed_count > 0:
        return _ParsedField(
            signed_count,
            "pandadoc.rest.details.recipients",
            f"signed_recipients={signed_count}",
        )
    signature_present = _pandadoc_signature_status(details.get("status"))
    if signature_present is False and total_count > 0:
        return _ParsedField(0, "pandadoc.rest.details.recipients", "signed_recipients=0")
    if signature_present is True and total_count > 0:
        return _ParsedField(
            total_count,
            "pandadoc.rest.details.recipients",
            f"signed_recipients~={total_count}",
        )
    return None


def _build_pandadoc_api_executed_at_field(*, details: dict[str, Any]) -> _ParsedField | None:
    for key in ("date_completed", "completed_at", "completedAt", "date_status_changed", "date_modified"):
        normalized = _normalize_date(details.get(key))
        if normalized:
            return _ParsedField(
                normalized,
                f"pandadoc.rest.details.{key}",
                f"{key}={normalized}",
            )
    recipients = details.get("recipients")
    if not isinstance(recipients, list):
        return None
    recipient_dates = sorted(
        date
        for date in (
            _normalize_date(
                recipient.get("date_completed")
                or recipient.get("completed_at")
                or recipient.get("date_modified")
                or recipient.get("last_action_date")
            )
            for recipient in recipients
            if isinstance(recipient, dict)
        )
        if date
    )
    if not recipient_dates:
        return None
    return _ParsedField(
        recipient_dates[-1],
        "pandadoc.rest.details.recipients",
        f"completed_at={recipient_dates[-1]}",
    )


def _build_pandadoc_api_amount_field(*, details: dict[str, Any]) -> _ParsedField | None:
    for locator, value in (
        ("pandadoc.rest.details.grand_total", details.get("grand_total")),
        ("pandadoc.rest.details.value", details.get("value")),
    ):
        parsed = _build_pandadoc_numeric_field(
            value=value,
            source_locator=locator,
            as_int=False,
        )
        if parsed is not None:
            return parsed
    pricing = details.get("pricing")
    if isinstance(pricing, dict):
        for locator, value in (
            ("pandadoc.rest.details.pricing.grand_total", pricing.get("grand_total")),
            ("pandadoc.rest.details.pricing.value", pricing.get("value")),
        ):
            parsed = _build_pandadoc_numeric_field(
                value=value,
                source_locator=locator,
                as_int=False,
            )
            if parsed is not None:
                return parsed
    return None


def _build_pandadoc_api_product_summary_field(*, details: dict[str, Any]) -> _ParsedField | None:
    pricing = details.get("pricing")
    if not isinstance(pricing, dict):
        return None
    raw_items = pricing.get("items")
    if not isinstance(raw_items, list):
        return None
    item_names = [
        str(item.get("name") or "").strip()
        for item in raw_items
        if isinstance(item, dict) and str(item.get("name") or "").strip()
    ]
    if not item_names:
        return None
    summary = ", ".join(item_names[:5])
    return _ParsedField(
        summary,
        "pandadoc.rest.details.pricing.items",
        summary,
    )


def _pandadoc_api_field_rows(*, snapshot: PandaDocDocumentSnapshot) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    for source_name, raw_collection in (
        ("fields", snapshot.fields),
        ("details.fields", snapshot.details.get("fields")),
        ("details.tokens", snapshot.details.get("tokens")),
    ):
        if not isinstance(raw_collection, list):
            continue
        for item in raw_collection:
            if not isinstance(item, dict):
                continue
            rows.append({"source": source_name, "raw": item})
    return rows


def _parse_pandadoc_api_named_field(
    *,
    api_rows: list[dict[str, Any]],
    labels: tuple[str, ...],
    value_type: Literal["currency", "date", "int", "text"],
) -> _ParsedField | None:
    match = _pandadoc_api_match(api_rows=api_rows, labels=labels)
    if match is None:
        return None
    if value_type == "currency":
        value = _coerce_float(match.raw_value)
    elif value_type == "date":
        value = _normalize_date(match.raw_value)
    elif value_type == "int":
        value = _coerce_int(match.raw_value)
    else:
        value = str(match.raw_value).strip() or None
    if value in (None, ""):
        return None
    return _ParsedField(value, match.source_locator, match.raw_snippet)


def _pandadoc_api_match(
    *,
    api_rows: list[dict[str, Any]],
    labels: tuple[str, ...],
) -> _LabeledMatch | None:
    best: tuple[int, _LabeledMatch] | None = None
    for item in api_rows:
        if not isinstance(item, dict):
            continue
        row = item.get("raw")
        if not isinstance(row, dict):
            continue
        label = _first_non_empty(
            row.get("title"),
            row.get("name"),
            row.get("field"),
            row.get("merge_field"),
            row.get("token"),
            row.get("key"),
        )
        value = _pandadoc_api_value(row)
        if not label or value is None:
            continue
        searchable_label = _searchable_text(label)
        for target in labels:
            target_text = _searchable_text(target)
            if not target_text:
                continue
            if searchable_label == target_text:
                score = 100
            elif target_text in searchable_label:
                score = 80
            elif all(token in searchable_label for token in target_text.split()):
                score = 60
            else:
                continue
            source_name = str(item.get("source") or "fields")
            match = _LabeledMatch(
                label=label,
                raw_value=str(value),
                raw_snippet=f"{label}={value}",
                source_locator=f"pandadoc.rest.{source_name}:{label}",
            )
            if best is None or score > best[0]:
                best = (score, match)
    return best[1] if best is not None else None


def _pandadoc_api_value(row: dict[str, Any]) -> str | None:
    for key in ("value", "field_value", "formatted_value", "content", "text", "answer"):
        raw = row.get(key)
        if isinstance(raw, dict):
            nested = _first_non_empty(raw.get("value"), raw.get("text"), raw.get("name"))
            if nested:
                return str(nested)
            continue
        if raw is None:
            continue
        normalized = str(raw).strip()
        if normalized:
            return normalized
    return None


def _pandadoc_signature_status(value: Any) -> bool | None:
    normalized = _searchable_text(value)
    if not normalized:
        return None
    if any(token in normalized for token in _PANDADOC_UNSIGNED_STATUS_TOKENS):
        return False
    if any(token in normalized for token in _PANDADOC_SIGNED_STATUS_TOKENS):
        return True
    return None


def _load_adobe_agreement_bundles(
    *,
    opportunity_id: str,
    org_alias: str | None,
    schema: _AdobeAgreementSchema,
) -> list[_AdobeAgreementBundle]:
    select_fields = _queryable_field_names(
        "Id",
        schema.opportunity_lookup_field,
        schema.parent_id_field,
        schema.agreement_external_id_field,
        schema.status_field,
        schema.completed_at_field,
        schema.sent_at_field,
        schema.title_field,
        schema.amount_field,
        schema.effective_date_field,
        schema.service_start_date_field,
        schema.service_end_date_field,
        schema.term_months_field,
        schema.legal_entity_field,
        schema.product_summary_field,
        "Name",
    )
    if not select_fields:
        return []
    escaped_id = sfdc_helpers.escape_soql_value(opportunity_id)
    where_clauses: list[str] = []
    if schema.opportunity_lookup_field:
        where_clauses.append(f"{schema.opportunity_lookup_field} = '{escaped_id}'")
    if schema.parent_id_field:
        where_clauses.append(f"{schema.parent_id_field} = '{escaped_id}'")
    if not where_clauses:
        return []
    order_field = schema.completed_at_field or schema.sent_at_field or "LastModifiedDate"
    try:
        agreement_rows = sfdc_helpers.run_soql(
            (
                f"SELECT {', '.join(select_fields)} "
                f"FROM {_ADOBE_AGREEMENT_OBJECT} "
                f"WHERE {' OR '.join(where_clauses)} "
                f"ORDER BY {order_field} DESC LIMIT 25"
            ),
            org_alias=org_alias,
        )
    except Exception:
        return []
    if not agreement_rows:
        return []
    agreement_ids = [
        str(row.get("Id") or "").strip()
        for row in agreement_rows
        if str(row.get("Id") or "").strip()
    ]
    recipient_rows = _load_adobe_recipient_rows(
        agreement_ids=agreement_ids,
        org_alias=org_alias,
        schema=schema,
    )
    recipient_rows_by_agreement: dict[str, list[dict[str, Any]]] = {}
    for row in recipient_rows:
        agreement_id = str(row.get(schema.recipient_agreement_lookup_field or "") or "").strip()
        if agreement_id:
            recipient_rows_by_agreement.setdefault(agreement_id, []).append(row)

    bundles: list[_AdobeAgreementBundle] = []
    for agreement_row in agreement_rows:
        agreement_record_id = str(agreement_row.get("Id") or "").strip()
        if not agreement_record_id:
            continue
        bundles.append(
            _AdobeAgreementBundle(
                agreement_record_id=agreement_record_id,
                agreement_external_id=_normalize_adobe_agreement_id(
                    agreement_row.get(schema.agreement_external_id_field or "")
                ),
                title=_first_non_empty(
                    agreement_row.get(schema.title_field or ""),
                    agreement_row.get("Name"),
                ),
                status=_first_non_empty(agreement_row.get(schema.status_field or "")),
                completed_at=_normalize_date(agreement_row.get(schema.completed_at_field or "")),
                sent_at=_normalize_date(agreement_row.get(schema.sent_at_field or "")),
                agreement_row=dict(agreement_row),
                recipient_rows=list(recipient_rows_by_agreement.get(agreement_record_id, [])),
            )
        )
    return bundles


def _load_adobe_recipient_rows(
    *,
    agreement_ids: list[str],
    org_alias: str | None,
    schema: _AdobeAgreementSchema,
) -> list[dict[str, Any]]:
    if not agreement_ids or not schema.recipient_agreement_lookup_field:
        return []
    select_fields = _queryable_field_names(
        "Id",
        schema.recipient_agreement_lookup_field,
        schema.recipient_status_field,
        schema.recipient_signed_at_field,
        schema.recipient_email_field,
        schema.recipient_name_field,
        "Name",
    )
    if not select_fields:
        return []
    in_clause = ", ".join(
        f"'{sfdc_helpers.escape_soql_value(agreement_id)}'"
        for agreement_id in agreement_ids
    )
    try:
        return sfdc_helpers.run_soql(
            (
                f"SELECT {', '.join(select_fields)} "
                f"FROM {_ADOBE_RECIPIENT_OBJECT} "
                f"WHERE {schema.recipient_agreement_lookup_field} IN ({in_clause}) "
                "LIMIT 200"
            ),
            org_alias=org_alias,
        )
    except Exception:
        return []


def _select_adobe_bundle_for_candidate(
    *,
    candidate: CloseDocumentCandidateV1,
    row: dict[str, Any],
    version_bytes: bytes | None,
    bundles: list[_AdobeAgreementBundle],
    schema: _AdobeAgreementSchema,
) -> _AdobeAgreementBundle | None:
    del schema
    if not bundles:
        return None
    if len(bundles) == 1:
        return bundles[0]
    agreement_hints = _extract_adobe_agreement_id_hints(
        candidate=candidate,
        row=row,
        version_bytes=version_bytes,
    )
    hint_matches = [
        bundle
        for bundle in bundles
        if bundle.agreement_external_id and bundle.agreement_external_id in agreement_hints
    ]
    if len(hint_matches) == 1:
        return hint_matches[0]
    candidate_title = _title_match_tokens(candidate.title)
    scored = [
        (_title_match_score(candidate_title, _title_match_tokens(bundle.title)), bundle)
        for bundle in bundles
    ]
    scored = [item for item in scored if item[0] > 0.0]
    if not scored:
        return None
    scored.sort(key=lambda item: item[0], reverse=True)
    if len(scored) == 1 and scored[0][0] >= 0.5:
        return scored[0][1]
    if len(scored) > 1 and scored[0][0] >= 0.65 and scored[0][0] > scored[1][0]:
        return scored[0][1]
    return None


def _extract_adobe_agreement_id_hints(
    *,
    candidate: CloseDocumentCandidateV1,
    row: dict[str, Any],
    version_bytes: bytes | None,
) -> set[str]:
    del version_bytes
    hints: set[str] = set()
    texts = (
        candidate.title,
        str(row.get("Title") or "").strip(),
        str(row.get("Description") or "").strip(),
        str(row.get("PathOnClient") or "").strip(),
    )
    for text in texts:
        for match in _ADOBE_AGREEMENT_ID_RE.finditer(str(text or "")):
            normalized = _normalize_adobe_agreement_id(match.group(1))
            if normalized:
                hints.add(normalized)
    return hints


def _normalize_adobe_agreement_id(value: Any) -> str | None:
    normalized = re.sub(r"[^A-Za-z0-9-]", "", str(value or "")).strip().upper()
    if len(normalized) < 8:
        return None
    return normalized


def _extract_adobe_agreement_evidence_from_bundle(
    *,
    candidate: CloseDocumentCandidateV1,
    content_version_row: dict[str, Any],
    bundle: _AdobeAgreementBundle,
    schema: _AdobeAgreementSchema,
    content_hash: str | None,
) -> CloseDocumentEvidenceV1 | None:
    version_id = str(content_version_row.get("Id") or "").strip() or None
    title = str(content_version_row.get("Title") or candidate.title or "").strip() or candidate.title
    source_fingerprint = _adobe_bundle_source_fingerprint(bundle=bundle)
    fields: list[EvidenceFieldV1] = []

    provider_fields: tuple[tuple[CloseDocumentCanonicalFieldKey, _ParsedField | None, float], ...] = (
        (
            "signature_present",
            _build_adobe_status_field(raw_status=bundle.status, schema=schema),
            0.99,
        ),
        (
            "signer_count",
            _build_adobe_signer_count(bundle=bundle, schema=schema),
            0.97,
        ),
        (
            "executed_at",
            _build_adobe_date_field(
                value=bundle.completed_at,
                source_locator=(
                    f"{_ADOBE_AGREEMENT_OBJECT}.{schema.completed_at_field}"
                    if schema.completed_at_field
                    else None
                ),
            )
            or _build_adobe_executed_at_from_recipients(bundle=bundle, schema=schema),
            0.98 if bundle.completed_at else 0.96,
        ),
        (
            "effective_date",
            _build_adobe_date_field(
                value=bundle.agreement_row.get(schema.effective_date_field or ""),
                source_locator=(
                    f"{_ADOBE_AGREEMENT_OBJECT}.{schema.effective_date_field}"
                    if schema.effective_date_field
                    else None
                ),
            ),
            0.95,
        ),
        (
            "service_start_date",
            _build_adobe_date_field(
                value=bundle.agreement_row.get(schema.service_start_date_field or ""),
                source_locator=(
                    f"{_ADOBE_AGREEMENT_OBJECT}.{schema.service_start_date_field}"
                    if schema.service_start_date_field
                    else None
                ),
            ),
            0.95,
        ),
        (
            "service_end_date",
            _build_adobe_date_field(
                value=bundle.agreement_row.get(schema.service_end_date_field or ""),
                source_locator=(
                    f"{_ADOBE_AGREEMENT_OBJECT}.{schema.service_end_date_field}"
                    if schema.service_end_date_field
                    else None
                ),
            ),
            0.95,
        ),
        (
            "amount",
            _build_adobe_numeric_field(
                value=bundle.agreement_row.get(schema.amount_field or ""),
                source_locator=(
                    f"{_ADOBE_AGREEMENT_OBJECT}.{schema.amount_field}"
                    if schema.amount_field
                    else None
                ),
                as_int=False,
            ),
            0.95,
        ),
        (
            "term_months",
            _build_adobe_numeric_field(
                value=bundle.agreement_row.get(schema.term_months_field or ""),
                source_locator=(
                    f"{_ADOBE_AGREEMENT_OBJECT}.{schema.term_months_field}"
                    if schema.term_months_field
                    else None
                ),
                as_int=True,
            ),
            0.95,
        ),
        (
            "legal_entity",
            _build_adobe_text_field(
                value=bundle.agreement_row.get(schema.legal_entity_field or ""),
                source_locator=(
                    f"{_ADOBE_AGREEMENT_OBJECT}.{schema.legal_entity_field}"
                    if schema.legal_entity_field
                    else None
                ),
            ),
            0.94,
        ),
        (
            "product_summary",
            _build_adobe_text_field(
                value=bundle.agreement_row.get(schema.product_summary_field or ""),
                source_locator=(
                    f"{_ADOBE_AGREEMENT_OBJECT}.{schema.product_summary_field}"
                    if schema.product_summary_field
                    else None
                ),
            ),
            0.94,
        ),
    )
    for canonical_key, parsed, confidence in provider_fields:
        field = _build_provider_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key=canonical_key,
            parsed=parsed,
            confidence=confidence,
        )
        if field is not None:
            fields.append(field)
    if not fields:
        return None
    confidence = sum(field.field_confidence for field in fields) / max(1, len(fields))
    return CloseDocumentEvidenceV1(
        evidence_id=_evidence_id(
            method="provider_api",
            document_id=candidate.document_id,
            version_id=version_id,
        ),
        document_id=candidate.document_id,
        version_id=version_id,
        title=title,
        provider_hint="adobe",
        extraction_method="provider_api",
        confidence=max(0.0, min(0.99, confidence)),
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        signature_present=_field_value(fields, "signature_present"),
        signer_count=_field_value(fields, "signer_count"),
        executed_at=_field_value(fields, "executed_at"),
        effective_date=_field_value(fields, "effective_date"),
        service_start_date=_field_value(fields, "service_start_date"),
        service_end_date=_field_value(fields, "service_end_date"),
        amount=_field_value(fields, "amount"),
        term_months=_field_value(fields, "term_months"),
        legal_entity=_field_value(fields, "legal_entity"),
        product_summary=_field_value(fields, "product_summary"),
        summary=_evidence_summary(
            title=title,
            provider_hint="adobe",
            extraction_method="provider_api",
            signature_present=_field_value(fields, "signature_present"),
            amount=_field_value(fields, "amount"),
            term_months=_field_value(fields, "term_months"),
            service_start_date=_field_value(fields, "service_start_date"),
            service_end_date=_field_value(fields, "service_end_date"),
        ),
        fields=fields,
    )


def _extract_adobe_api_evidence_from_snapshot(
    *,
    candidate: CloseDocumentCandidateV1,
    content_version_row: dict[str, Any],
    snapshot: AdobeAgreementSnapshot,
    content_hash: str | None,
) -> CloseDocumentEvidenceV1 | None:
    version_id = str(content_version_row.get("Id") or "").strip() or None
    title = str(content_version_row.get("Title") or candidate.title or "").strip() or candidate.title
    source_fingerprint = _adobe_api_source_fingerprint(snapshot=snapshot)
    agreement = snapshot.agreement
    fields: list[EvidenceFieldV1] = []

    provider_fields: tuple[tuple[CloseDocumentCanonicalFieldKey, _ParsedField | None, float], ...] = (
        (
            "signature_present",
            _build_adobe_api_status_field(agreement=agreement),
            0.99,
        ),
        (
            "signer_count",
            _build_adobe_api_signer_count_field(agreement=agreement),
            0.97,
        ),
        (
            "executed_at",
            _build_adobe_api_executed_at_field(agreement=agreement),
            0.98 if _normalize_date(agreement.get("displayDate") or agreement.get("dateSigned")) else 0.96,
        ),
        (
            "effective_date",
            _parse_adobe_api_named_field(
                form_data_rows=snapshot.form_data,
                labels=("effective date", "effective"),
                value_type="date",
            ),
            0.96,
        ),
        (
            "service_start_date",
            _parse_adobe_api_named_field(
                form_data_rows=snapshot.form_data,
                labels=("service start", "start date", "term start", "subscription start"),
                value_type="date",
            ),
            0.96,
        ),
        (
            "service_end_date",
            _parse_adobe_api_named_field(
                form_data_rows=snapshot.form_data,
                labels=("service end", "end date", "term end", "subscription end"),
                value_type="date",
            ),
            0.96,
        ),
        (
            "amount",
            _parse_adobe_api_named_field(
                form_data_rows=snapshot.form_data,
                labels=("amount", "tcv", "contract value", "total value"),
                value_type="currency",
            ),
            0.96,
        ),
        (
            "term_months",
            _parse_adobe_api_named_field(
                form_data_rows=snapshot.form_data,
                labels=("term months", "term", "subscription term"),
                value_type="int",
            ),
            0.96,
        ),
        (
            "legal_entity",
            _parse_adobe_api_named_field(
                form_data_rows=snapshot.form_data,
                labels=("legal entity", "contracting entity", "bill to entity"),
                value_type="text",
            ),
            0.95,
        ),
        (
            "product_summary",
            _parse_adobe_api_named_field(
                form_data_rows=snapshot.form_data,
                labels=("product", "plan", "sku"),
                value_type="text",
            ),
            0.94,
        ),
    )
    for canonical_key, parsed, confidence in provider_fields:
        field = _build_provider_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key=canonical_key,
            parsed=parsed,
            confidence=confidence,
        )
        if field is not None:
            fields.append(field)
    if not fields:
        return None
    confidence = sum(field.field_confidence for field in fields) / max(1, len(fields))
    return CloseDocumentEvidenceV1(
        evidence_id=_evidence_id(
            method="provider_api",
            document_id=candidate.document_id,
            version_id=version_id,
        ),
        document_id=candidate.document_id,
        version_id=version_id,
        title=title,
        provider_hint="adobe",
        extraction_method="provider_api",
        confidence=max(0.0, min(0.99, confidence)),
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        signature_present=_field_value(fields, "signature_present"),
        signer_count=_field_value(fields, "signer_count"),
        executed_at=_field_value(fields, "executed_at"),
        effective_date=_field_value(fields, "effective_date"),
        service_start_date=_field_value(fields, "service_start_date"),
        service_end_date=_field_value(fields, "service_end_date"),
        amount=_field_value(fields, "amount"),
        term_months=_field_value(fields, "term_months"),
        legal_entity=_field_value(fields, "legal_entity"),
        product_summary=_field_value(fields, "product_summary"),
        summary=_evidence_summary(
            title=title,
            provider_hint="adobe",
            extraction_method="provider_api",
            signature_present=_field_value(fields, "signature_present"),
            amount=_field_value(fields, "amount"),
            term_months=_field_value(fields, "term_months"),
            service_start_date=_field_value(fields, "service_start_date"),
            service_end_date=_field_value(fields, "service_end_date"),
        ),
        fields=fields,
    )


def _adobe_bundle_source_fingerprint(*, bundle: _AdobeAgreementBundle) -> str:
    payload = {
        "agreement": bundle.agreement_row,
        "recipients": bundle.recipient_rows,
    }
    digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest()
    return f"sha256:{digest}"


def _adobe_api_source_fingerprint(*, snapshot: AdobeAgreementSnapshot) -> str:
    payload = {
        "agreement": snapshot.agreement,
        "form_data": snapshot.form_data,
    }
    digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest()
    return f"sha256:{digest}"


def _build_adobe_status_field(
    *,
    raw_status: Any,
    schema: _AdobeAgreementSchema,
) -> _ParsedField | None:
    normalized = _adobe_signature_status(raw_status)
    if normalized is None or not schema.status_field:
        return None
    return _ParsedField(
        normalized,
        f"{_ADOBE_AGREEMENT_OBJECT}.{schema.status_field}",
        f"{schema.status_field}={raw_status}",
    )


def _build_adobe_signer_count(
    *,
    bundle: _AdobeAgreementBundle,
    schema: _AdobeAgreementSchema,
) -> _ParsedField | None:
    signed_count = 0
    total_count = 0
    for recipient_row in bundle.recipient_rows:
        total_count += 1
        if _adobe_signature_status(recipient_row.get(schema.recipient_status_field or "")) is True:
            signed_count += 1
    if signed_count > 0:
        return _ParsedField(
            signed_count,
            f"{_ADOBE_RECIPIENT_OBJECT}.{schema.recipient_status_field or 'status'}",
            f"signed_recipients={signed_count}",
        )
    signature_present = _adobe_signature_status(bundle.status)
    if signature_present is False and total_count > 0:
        return _ParsedField(
            0,
            f"{_ADOBE_RECIPIENT_OBJECT}.{schema.recipient_status_field or 'status'}",
            "signed_recipients=0",
        )
    if signature_present is True and total_count > 0:
        return _ParsedField(
            total_count,
            f"{_ADOBE_RECIPIENT_OBJECT}.{schema.recipient_status_field or 'status'}",
            f"signed_recipients~={total_count}",
        )
    return None


def _build_adobe_executed_at_from_recipients(
    *,
    bundle: _AdobeAgreementBundle,
    schema: _AdobeAgreementSchema,
) -> _ParsedField | None:
    signed_dates = [
        _normalize_date(row.get(schema.recipient_signed_at_field or ""))
        for row in bundle.recipient_rows
    ]
    normalized_dates = sorted(date for date in signed_dates if date)
    if not normalized_dates:
        return None
    return _ParsedField(
        normalized_dates[-1],
        f"{_ADOBE_RECIPIENT_OBJECT}.{schema.recipient_signed_at_field or 'signed_at'}",
        f"signed_at={normalized_dates[-1]}",
    )


def _build_adobe_date_field(*, value: Any, source_locator: str | None) -> _ParsedField | None:
    normalized = _normalize_date(value)
    if normalized is None or not source_locator:
        return None
    return _ParsedField(normalized, source_locator, f"{source_locator}={normalized}")


def _build_adobe_numeric_field(
    *,
    value: Any,
    source_locator: str | None,
    as_int: bool,
) -> _ParsedField | None:
    if not source_locator:
        return None
    normalized = _coerce_int(value) if as_int else _coerce_float(value)
    if normalized is None:
        return None
    return _ParsedField(normalized, source_locator, f"{source_locator}={value}")


def _build_adobe_text_field(*, value: Any, source_locator: str | None) -> _ParsedField | None:
    normalized = str(value or "").strip()
    if not normalized or not source_locator:
        return None
    return _ParsedField(normalized, source_locator, f"{source_locator}={normalized}")


def _build_adobe_api_status_field(*, agreement: dict[str, Any]) -> _ParsedField | None:
    raw_status = agreement.get("status")
    normalized = _adobe_signature_status(raw_status)
    if normalized is None:
        return None
    return _ParsedField(
        normalized,
        "adobe_sign.rest.agreement.status",
        f"status={raw_status}",
    )


def _build_adobe_api_signer_count_field(*, agreement: dict[str, Any]) -> _ParsedField | None:
    participant_sets = agreement.get("participantSetsInfo")
    if not isinstance(participant_sets, list):
        participant_sets = []
    signed_count = 0
    total_count = 0
    for participant_set in participant_sets:
        if not isinstance(participant_set, dict):
            continue
        member_infos = participant_set.get("memberInfos")
        members = member_infos if isinstance(member_infos, list) else []
        total_count += len([item for item in members if isinstance(item, dict)])
        if _adobe_signature_status(participant_set.get("status")) is True:
            signed_count += len([item for item in members if isinstance(item, dict)])
    if signed_count > 0:
        return _ParsedField(
            signed_count,
            "adobe_sign.rest.agreement.participantSetsInfo",
            f"signed_recipients={signed_count}",
        )
    signature_present = _adobe_signature_status(agreement.get("status"))
    if signature_present is False and total_count > 0:
        return _ParsedField(0, "adobe_sign.rest.agreement.participantSetsInfo", "signed_recipients=0")
    if signature_present is True and total_count > 0:
        return _ParsedField(
            total_count,
            "adobe_sign.rest.agreement.participantSetsInfo",
            f"signed_recipients~={total_count}",
        )
    return None


def _build_adobe_api_executed_at_field(*, agreement: dict[str, Any]) -> _ParsedField | None:
    for key in ("displayDate", "dateSigned", "signedDate", "lastEventDate", "modifiedDate"):
        normalized = _normalize_date(agreement.get(key))
        if normalized:
            return _ParsedField(
                normalized,
                f"adobe_sign.rest.agreement.{key}",
                f"{key}={normalized}",
            )
    participant_sets = agreement.get("participantSetsInfo")
    if not isinstance(participant_sets, list):
        return None
    recipient_dates: list[str] = []
    for participant_set in participant_sets:
        if not isinstance(participant_set, dict):
            continue
        for key in ("completedDate", "dateSigned", "signedDate"):
            normalized = _normalize_date(participant_set.get(key))
            if normalized:
                recipient_dates.append(normalized)
    if not recipient_dates:
        return None
    recipient_dates.sort()
    return _ParsedField(
        recipient_dates[-1],
        "adobe_sign.rest.agreement.participantSetsInfo",
        f"signed_at={recipient_dates[-1]}",
    )


def _parse_adobe_api_named_field(
    *,
    form_data_rows: list[dict[str, Any]],
    labels: tuple[str, ...],
    value_type: Literal["currency", "date", "int", "text"],
) -> _ParsedField | None:
    match = _adobe_api_match(form_data_rows=form_data_rows, labels=labels)
    if match is None:
        return None
    if value_type == "currency":
        value = _coerce_float(match.raw_value)
    elif value_type == "date":
        value = _normalize_date(match.raw_value)
    elif value_type == "int":
        value = _coerce_int(match.raw_value)
    else:
        value = str(match.raw_value).strip() or None
    if value in (None, ""):
        return None
    return _ParsedField(value, match.source_locator, match.raw_snippet)


def _adobe_api_match(
    *,
    form_data_rows: list[dict[str, Any]],
    labels: tuple[str, ...],
) -> _LabeledMatch | None:
    best: tuple[int, _LabeledMatch] | None = None
    for row in form_data_rows:
        if not isinstance(row, dict):
            continue
        label = _first_non_empty(
            row.get("name"),
            row.get("fieldName"),
            row.get("label"),
        )
        value = _adobe_api_value(row)
        if not label or value is None:
            continue
        searchable_label = _searchable_text(label)
        for target in labels:
            target_text = _searchable_text(target)
            if not target_text:
                continue
            if searchable_label == target_text:
                score = 100
            elif target_text in searchable_label:
                score = 80
            elif all(token in searchable_label for token in target_text.split()):
                score = 60
            else:
                continue
            match = _LabeledMatch(
                label=label,
                raw_value=str(value),
                raw_snippet=f"{label}={value}",
                source_locator=f"adobe_sign.rest.formData:{label}",
            )
            if best is None or score > best[0]:
                best = (score, match)
    return best[1] if best is not None else None


def _adobe_api_value(row: dict[str, Any]) -> str | None:
    for key in ("defaultValue", "value", "fieldValue", "displayValue"):
        raw = row.get(key)
        if raw is None:
            continue
        normalized = str(raw).strip()
        if normalized:
            return normalized
    return None


def _adobe_signature_status(value: Any) -> bool | None:
    normalized = _searchable_text(value)
    if not normalized:
        return None
    if any(token in normalized for token in _ADOBE_UNSIGNED_STATUS_TOKENS):
        return False
    if any(token in normalized for token in _ADOBE_SIGNED_STATUS_TOKENS):
        return True
    return None


def _extract_document_vision_evidence_from_response(
    *,
    candidate: CloseDocumentCandidateV1,
    row: dict[str, Any],
    raw_response: str,
    content_hash: str | None,
) -> CloseDocumentEvidenceV1 | None:
    payload = _parse_document_vision_payload(raw_response)
    if payload is None or not payload.fields:
        return None
    version_id = str(row.get("Id") or "").strip() or None
    title = str(row.get("Title") or candidate.title or "").strip() or candidate.title
    source_fingerprint = _document_vision_source_fingerprint(payload=payload)
    fields: list[EvidenceFieldV1] = []
    for item in payload.fields:
        field = _build_document_vision_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            item=item,
        )
        if field is not None:
            fields.append(field)
    if not fields:
        return None
    confidence = sum(field.field_confidence for field in fields) / max(1, len(fields))
    return CloseDocumentEvidenceV1(
        evidence_id=_evidence_id(
            method="document_vision",
            document_id=candidate.document_id,
            version_id=version_id,
        ),
        document_id=candidate.document_id,
        version_id=version_id,
        title=title,
        provider_hint=candidate.provider_hint,
        extraction_method="document_vision",
        confidence=max(0.0, min(0.94, confidence)),
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        signature_present=_field_value(fields, "signature_present"),
        signer_count=_field_value(fields, "signer_count"),
        executed_at=_field_value(fields, "executed_at"),
        effective_date=_field_value(fields, "effective_date"),
        service_start_date=_field_value(fields, "service_start_date"),
        service_end_date=_field_value(fields, "service_end_date"),
        amount=_field_value(fields, "amount"),
        term_months=_field_value(fields, "term_months"),
        legal_entity=_field_value(fields, "legal_entity"),
        product_summary=_field_value(fields, "product_summary"),
        summary=str(payload.summary or "").strip()
        or _evidence_summary(
            title=title,
            provider_hint=candidate.provider_hint,
            extraction_method="document_vision",
            signature_present=_field_value(fields, "signature_present"),
            amount=_field_value(fields, "amount"),
            term_months=_field_value(fields, "term_months"),
            service_start_date=_field_value(fields, "service_start_date"),
            service_end_date=_field_value(fields, "service_end_date"),
        ),
        fields=fields,
    )


def _parse_document_vision_payload(raw_response: str) -> _DocumentVisionPayload | None:
    text = str(raw_response or "").strip()
    if not text:
        return None
    if "```" in text:
        text = _strip_document_vision_fence(text)
    parsed: Any
    try:
        parsed = json.loads(text)
    except Exception:
        start = text.find("{")
        end = text.rfind("}")
        if start < 0 or end < start:
            return None
        try:
            parsed = json.loads(text[start : end + 1])
        except Exception:
            return None
    if not isinstance(parsed, dict):
        return None
    try:
        return _DocumentVisionPayload.model_validate(parsed)
    except Exception:
        return None


def _strip_document_vision_fence(text: str) -> str:
    stripped = text.strip()
    if stripped.startswith("```"):
        first_newline = stripped.find("\n")
        if first_newline >= 0:
            stripped = stripped[first_newline + 1 :]
    if stripped.endswith("```"):
        stripped = stripped[:-3]
    return stripped.strip()


def _document_vision_source_fingerprint(*, payload: _DocumentVisionPayload) -> str:
    digest = hashlib.sha256(
        json.dumps(payload.model_dump(exclude_none=False), sort_keys=True).encode("utf-8")
    ).hexdigest()
    return f"sha256:{digest}"


def _build_document_vision_field(
    *,
    candidate: CloseDocumentCandidateV1,
    version_id: str | None,
    content_hash: str | None,
    source_fingerprint: str,
    item: _DocumentVisionFieldPayload,
) -> EvidenceFieldV1 | None:
    coerced_value = _coerce_document_vision_value(
        canonical_key=item.canonical_key,
        value=item.extracted_value,
    )
    if coerced_value is None:
        return None
    locator = str(item.source_locator or "").strip()
    if locator:
        normalized_locator = locator if locator.startswith("document_vision:") else f"document_vision:{locator}"
    else:
        normalized_locator = "document_vision:document"
    raw_snippet = str(item.raw_snippet or "").strip() or None
    return EvidenceFieldV1(
        canonical_key=item.canonical_key,
        extracted_value=coerced_value,
        extraction_method="document_vision",
        field_confidence=max(0.0, min(0.94, float(item.field_confidence or 0.0))),
        source_locator=normalized_locator,
        document_id=candidate.document_id,
        version_id=version_id,
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        raw_snippet=raw_snippet,
    )


def _coerce_document_vision_value(
    *,
    canonical_key: CloseDocumentCanonicalFieldKey,
    value: Any,
) -> Any:
    if canonical_key == "amount":
        return _coerce_float(value)
    if canonical_key in {"term_months", "signer_count"}:
        return _coerce_int(value)
    if canonical_key in {"service_start_date", "service_end_date", "effective_date", "executed_at"}:
        return _normalize_date(value)
    if canonical_key == "signature_present":
        if isinstance(value, bool):
            return value
        lowered = str(value or "").strip().lower()
        if lowered in {"true", "yes", "signed", "completed", "executed"}:
            return True
        if lowered in {"false", "no", "pending", "unsigned", "sent"}:
            return False
        return None
    normalized = str(value or "").strip()
    return normalized or None


def _document_vision_prompt() -> str:
    return (
        "You are extracting close-certification evidence from a signed sales document PDF.\n"
        "Return ONLY JSON. Do not include markdown fences.\n"
        "Use this exact shape:\n"
        '{'
        '"summary":"...",'
        '"fields":['
        '{"canonical_key":"signature_present","extracted_value":true,"field_confidence":0.84,"source_locator":"page 1","raw_snippet":"Signed"},'
        '{"canonical_key":"amount","extracted_value":125000.0,"field_confidence":0.82,"source_locator":"page 2","raw_snippet":"Total Contract Value $125,000"}'
        "]}\n"
        "Allowed canonical_key values only:\n"
        "- signature_present\n"
        "- signer_count\n"
        "- executed_at\n"
        "- effective_date\n"
        "- service_start_date\n"
        "- service_end_date\n"
        "- amount\n"
        "- term_months\n"
        "- legal_entity\n"
        "- product_summary\n"
        "Rules:\n"
        "- Extract only values explicitly supported by the document contents.\n"
        "- If a field is not visible or not reliable, omit it from fields.\n"
        "- Use ISO dates (YYYY-MM-DD) for date fields when possible.\n"
        "- Use numbers for amount, signer_count, and term_months.\n"
        "- Use booleans for signature_present.\n"
        "- source_locator should be a short page reference like 'page 1' or 'pages 1-2'.\n"
        "- raw_snippet must be a short exact snippet from the document that supports the extracted value.\n"
        "- Do not infer CRM values. Do not guess missing fields.\n"
    )


def _extract_metadata_evidence_from_version_row(
    *,
    candidate: CloseDocumentCandidateV1,
    row: dict[str, Any],
    content_hash: str | None,
) -> CloseDocumentEvidenceV1 | None:
    metadata_text = _metadata_text(row)
    if not metadata_text:
        return None

    source_fingerprint = _metadata_source_fingerprint(metadata_text=metadata_text, row=row)
    signature_present = _parse_signature_present(metadata_text)
    signer_count = _parse_int_label(metadata_text, labels=("signers", "recipients"))
    executed_at = _parse_date_label(
        metadata_text,
        labels=("completed", "signed at", "executed at", "execution date"),
    )
    effective_date = _parse_date_label(metadata_text, labels=("effective date", "effective"))
    service_start_date = _parse_date_label(
        metadata_text,
        labels=("service start", "start date", "term start", "subscription start"),
    )
    service_end_date = _parse_date_label(
        metadata_text,
        labels=("service end", "end date", "term end", "subscription end"),
    )
    range_start, range_end = _parse_date_range(metadata_text, label="service term")
    if service_start_date is None:
        service_start_date = range_start
    if service_end_date is None:
        service_end_date = range_end
    amount = _parse_currency_label(metadata_text, labels=("amount", "tcv", "contract value", "total value"))
    term_months = _parse_term_months(metadata_text)
    legal_entity = _parse_text_label(
        metadata_text,
        labels=("legal entity", "contracting entity", "bill to entity"),
    )
    product_summary = _parse_text_label(metadata_text, labels=("product", "plan", "sku"))

    version_id = str(row.get("Id") or "").strip() or None
    title = str(row.get("Title") or candidate.title or "").strip() or candidate.title
    fields = [
        _build_metadata_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key="signature_present",
            parsed=signature_present,
        ),
        _build_metadata_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key="signer_count",
            parsed=signer_count,
        ),
        _build_metadata_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key="executed_at",
            parsed=executed_at,
        ),
        _build_metadata_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key="effective_date",
            parsed=effective_date,
        ),
        _build_metadata_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key="service_start_date",
            parsed=service_start_date,
        ),
        _build_metadata_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key="service_end_date",
            parsed=service_end_date,
        ),
        _build_metadata_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key="amount",
            parsed=amount,
        ),
        _build_metadata_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key="term_months",
            parsed=term_months,
        ),
        _build_metadata_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key="legal_entity",
            parsed=legal_entity,
        ),
        _build_metadata_field(
            candidate=candidate,
            version_id=version_id,
            content_hash=content_hash,
            source_fingerprint=source_fingerprint,
            canonical_key="product_summary",
            parsed=product_summary,
        ),
    ]
    normalized_fields = [field for field in fields if field is not None]
    if not normalized_fields:
        return None
    confidence = sum(field.field_confidence for field in normalized_fields) / max(1, len(normalized_fields))
    return CloseDocumentEvidenceV1(
        evidence_id=_evidence_id(
            method="salesforce_contentversion_metadata",
            document_id=candidate.document_id,
            version_id=version_id,
        ),
        document_id=candidate.document_id,
        version_id=version_id,
        title=title,
        provider_hint=candidate.provider_hint,
        extraction_method="salesforce_contentversion_metadata",
        confidence=max(0.0, min(0.98, confidence)),
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        signature_present=_field_value(normalized_fields, "signature_present"),
        signer_count=_field_value(normalized_fields, "signer_count"),
        executed_at=_field_value(normalized_fields, "executed_at"),
        effective_date=_field_value(normalized_fields, "effective_date"),
        service_start_date=_field_value(normalized_fields, "service_start_date"),
        service_end_date=_field_value(normalized_fields, "service_end_date"),
        amount=_field_value(normalized_fields, "amount"),
        term_months=_field_value(normalized_fields, "term_months"),
        legal_entity=_field_value(normalized_fields, "legal_entity"),
        product_summary=_field_value(normalized_fields, "product_summary"),
        summary=_evidence_summary(
            title=title,
            provider_hint=candidate.provider_hint,
            extraction_method="salesforce_contentversion_metadata",
            signature_present=_field_value(normalized_fields, "signature_present"),
            amount=_field_value(normalized_fields, "amount"),
            term_months=_field_value(normalized_fields, "term_months"),
            service_start_date=_field_value(normalized_fields, "service_start_date"),
            service_end_date=_field_value(normalized_fields, "service_end_date"),
        ),
        metadata_excerpt=_metadata_excerpt(metadata_text),
        fields=normalized_fields,
    )


def _build_metadata_field(
    *,
    candidate: CloseDocumentCandidateV1,
    version_id: str | None,
    content_hash: str | None,
    source_fingerprint: str,
    canonical_key: CloseDocumentCanonicalFieldKey,
    parsed: _ParsedField | None,
) -> EvidenceFieldV1 | None:
    if parsed is None:
        return None
    return EvidenceFieldV1(
        canonical_key=canonical_key,
        extracted_value=parsed.value,
        extraction_method="salesforce_contentversion_metadata",
        field_confidence=_metadata_field_confidence(
            canonical_key=canonical_key,
            provider_hint=candidate.provider_hint,
            source_locator=parsed.source_locator,
        ),
        source_locator=parsed.source_locator,
        document_id=candidate.document_id,
        version_id=version_id,
        content_hash=content_hash,
        source_fingerprint=source_fingerprint,
        raw_snippet=parsed.raw_snippet,
    )


def _build_field_comparisons(
    *,
    evidence: list[CloseDocumentEvidenceV1],
    opportunity_snapshot: dict[str, Any],
    config: DealDeskConfig,
) -> list[CloseDocumentFieldComparisonV1]:
    comparisons: list[CloseDocumentFieldComparisonV1] = []
    for canonical_key in _ALL_FIELD_KEYS:
        candidates = _field_candidates(evidence=evidence, canonical_key=canonical_key)
        crm_field_name = _crm_field_name_for_key(canonical_key=canonical_key, config=config)
        crm_field_value = opportunity_snapshot.get(crm_field_name) if crm_field_name else None
        best = candidates[0] if candidates else None
        conflict = _has_meaningful_field_conflict(canonical_key=canonical_key, candidates=candidates)
        outcome, delta_summary = _comparison_outcome(
            canonical_key=canonical_key,
            best_field=best,
            crm_field_name=crm_field_name,
            crm_field_value=crm_field_value,
            conflict=conflict,
        )
        if best is None and crm_field_name is None:
            continue
        comparisons.append(
            CloseDocumentFieldComparisonV1(
                canonical_key=canonical_key,
                crm_field_name=crm_field_name,
                crm_field_value=crm_field_value,
                extracted_value=best.extracted_value if best is not None else None,
                extraction_method=best.extraction_method if best is not None else None,
                field_confidence=best.field_confidence if best is not None else None,
                outcome=outcome,
                document_id=best.document_id if best is not None else None,
                version_id=best.version_id if best is not None else None,
                content_hash=best.content_hash if best is not None else None,
                document_format=best.document_format if best is not None else "unknown",
                routed_extraction_path=best.routed_extraction_path if best is not None else None,
                source_fingerprint=best.source_fingerprint if best is not None else None,
                source_locator=best.source_locator if best is not None else None,
                raw_snippet=best.raw_snippet if best is not None else None,
                delta_summary=delta_summary,
            )
        )
    return comparisons


def _comparison_issues(
    *,
    comparisons: list[CloseDocumentFieldComparisonV1],
    evidence: list[CloseDocumentEvidenceV1],
    config: DealDeskConfig,
) -> list[CloseDocumentExtractionIssueV1]:
    issues: list[CloseDocumentExtractionIssueV1] = []
    comparison_by_key = {comparison.canonical_key: comparison for comparison in comparisons}
    best_methods = {
        comparison.extraction_method
        for comparison in comparisons
        if comparison.extracted_value is not None and comparison.extraction_method is not None
    }
    if best_methods and not any(method in _HIGH_FIDELITY_METHODS for method in best_methods):
        issues.append(_low_confidence_issue(evidence=evidence))

    signature_issue = _signature_issue(
        signature_present=comparison_by_key.get("signature_present"),
        signer_count=comparison_by_key.get("signer_count"),
    )
    if signature_issue is not None:
        issues.append(signature_issue)

    for canonical_key in _COMPARABLE_FIELD_KEYS:
        comparison = comparison_by_key.get(canonical_key)
        if comparison is None:
            continue
        mismatch_issue = _mismatch_issue_for_comparison(comparison=comparison, config=config)
        if mismatch_issue is not None:
            issues.append(mismatch_issue)

    for comparison in comparisons:
        if comparison.outcome != "conflict":
            continue
        issues.append(_conflict_issue(comparison=comparison))
    return _sort_issues(issues)


def _comparison_outcome(
    *,
    canonical_key: CloseDocumentCanonicalFieldKey,
    best_field: EvidenceFieldV1 | None,
    crm_field_name: str | None,
    crm_field_value: Any,
    conflict: bool,
) -> tuple[CloseDocumentFieldComparisonOutcome, str | None]:
    if best_field is None:
        if crm_field_name:
            return "unverified", f"No document value was extracted for {crm_field_name}."
        return "unverified", "No document value was extracted."
    if conflict:
        return "conflict", "Conflicting document values were extracted across close artifacts."
    if crm_field_name is None:
        return "document_signal", _document_signal_summary(canonical_key=canonical_key, value=best_field.extracted_value)

    crm_normalized = _normalize_field_value(canonical_key=canonical_key, value=crm_field_value)
    extracted_normalized = _normalize_field_value(canonical_key=canonical_key, value=best_field.extracted_value)
    if crm_normalized is None or extracted_normalized is None:
        return "unverified", f"Document verification could not compare {crm_field_name} confidently."
    if crm_normalized == extracted_normalized:
        return "matched", f"{crm_field_name} matches the signed document."
    return "mismatch", (
        f"{crm_field_name} ({_display_value(canonical_key=canonical_key, value=crm_field_value)}) "
        f"does not match the signed document "
        f"({_display_value(canonical_key=canonical_key, value=best_field.extracted_value)})."
    )


def _signature_issue(
    *,
    signature_present: CloseDocumentFieldComparisonV1 | None,
    signer_count: CloseDocumentFieldComparisonV1 | None,
) -> CloseDocumentExtractionIssueV1 | None:
    if signature_present is not None and signature_present.extracted_value is False:
        classification, severity, message = _signature_issue_policy(
            extraction_method=signature_present.extraction_method,
            unsigned_signal="signature_present",
        )
        return CloseDocumentExtractionIssueV1(
            code="DD_DOCUMENT_SIGNATURE_MISSING",
            severity=severity,
            classification=classification,
            message=message,
            canonical_key="signature_present",
            extraction_method=signature_present.extraction_method,
            field_confidence=signature_present.field_confidence,
            document_id=signature_present.document_id,
            version_id=signature_present.version_id,
            content_hash=signature_present.content_hash,
            source_locator=signature_present.source_locator,
            raw_snippet=signature_present.raw_snippet,
            suggested_action="Verify the signature state on the signed artifact before final close progression.",
        )
    if signer_count is not None and _coerce_int(signer_count.extracted_value) == 0:
        classification, severity, message = _signature_issue_policy(
            extraction_method=signer_count.extraction_method,
            unsigned_signal="signer_count",
        )
        return CloseDocumentExtractionIssueV1(
            code="DD_DOCUMENT_SIGNATURE_MISSING",
            severity=severity,
            classification=classification,
            message=message,
            canonical_key="signer_count",
            extraction_method=signer_count.extraction_method,
            field_confidence=signer_count.field_confidence,
            document_id=signer_count.document_id,
            version_id=signer_count.version_id,
            content_hash=signer_count.content_hash,
            source_locator=signer_count.source_locator,
            raw_snippet=signer_count.raw_snippet,
            suggested_action="Verify the signature state on the signed artifact before final close progression.",
        )
    return None


def _signature_issue_policy(
    *,
    extraction_method: CloseDocumentExtractionMethod | None,
    unsigned_signal: str,
) -> tuple[Literal["approval_required", "block_close", "informational"], Literal["blocking", "warning", "info"], str]:
    method = extraction_method or "salesforce_contentversion_metadata"
    if method == "provider_api":
        return (
            "block_close",
            "blocking",
            "Provider-verified document evidence shows the close artifact is not fully signed.",
        )
    if method == "document_vision":
        return (
            "approval_required",
            "warning",
            "Document vision extraction suggests the close artifact may not be fully signed and needs human review.",
        )
    signal_label = "signature status" if unsigned_signal == "signature_present" else "signer count"
    return (
        "informational",
        "info",
        f"Metadata-derived {signal_label} suggests the close artifact may not be fully signed; verify manually.",
    )


def _mismatch_issue_for_comparison(
    *,
    comparison: CloseDocumentFieldComparisonV1,
    config: DealDeskConfig,
) -> CloseDocumentExtractionIssueV1 | None:
    del config
    if comparison.outcome != "mismatch":
        return None
    code = {
        "amount": "DD_DOCUMENT_AMOUNT_MISMATCH",
        "term_months": "DD_DOCUMENT_TERM_MISMATCH",
        "service_start_date": "DD_DOCUMENT_SERVICE_START_MISMATCH",
        "service_end_date": "DD_DOCUMENT_SERVICE_END_MISMATCH",
    }.get(comparison.canonical_key)
    if code is None:
        return None
    method = comparison.extraction_method or "salesforce_contentversion_metadata"
    if method == "salesforce_contentversion_metadata":
        classification: Literal["approval_required", "informational"] = "informational"
        severity: Literal["warning", "info"] = "info"
        message = (
            f"Metadata-derived document verification found a mismatch for {comparison.crm_field_name}; "
            "verify the signed artifact manually before relying on this signal."
        )
    else:
        classification = "approval_required"
        severity = "warning"
        method_label = "Provider-verified" if method == "provider_api" else "Document vision"
        message = f"{method_label} document verification found a mismatch for {comparison.crm_field_name}."
    return CloseDocumentExtractionIssueV1(
        code=code,
        severity=severity,
        classification=classification,
        message=message,
        field_name=comparison.crm_field_name,
        canonical_key=comparison.canonical_key,
        extraction_method=comparison.extraction_method,
        field_confidence=comparison.field_confidence,
        document_id=comparison.document_id,
        version_id=comparison.version_id,
        content_hash=comparison.content_hash,
        source_locator=comparison.source_locator,
        raw_snippet=comparison.raw_snippet,
        suggested_action=f"Review {comparison.crm_field_name or comparison.canonical_key} against the signed document before final close progression.",
    )


def _conflict_issue(*, comparison: CloseDocumentFieldComparisonV1) -> CloseDocumentExtractionIssueV1:
    return CloseDocumentExtractionIssueV1(
        code="DD_DOCUMENT_FIELD_CONFLICT",
        severity="warning",
        classification="approval_required",
        message=(
            f"Multiple close artifacts produced conflicting values for "
            f"{comparison.crm_field_name or comparison.canonical_key}."
        ),
        field_name=comparison.crm_field_name,
        canonical_key=comparison.canonical_key,
        extraction_method=comparison.extraction_method,
        field_confidence=comparison.field_confidence,
        document_id=comparison.document_id,
        version_id=comparison.version_id,
        content_hash=comparison.content_hash,
        source_locator=comparison.source_locator,
        raw_snippet=comparison.raw_snippet,
        suggested_action="Review the conflicting signed artifacts with RevOps before final close progression.",
    )


def _low_confidence_issue(*, evidence: list[CloseDocumentEvidenceV1]) -> CloseDocumentExtractionIssueV1:
    first = evidence[0] if evidence else None
    return CloseDocumentExtractionIssueV1(
        code="DD_DOCUMENT_VERIFICATION_LOW_CONFIDENCE",
        severity="warning",
        classification="approval_required",
        message=(
            "Only metadata-derived document verification was available. "
            "Manual review is still required before final close progression."
        ),
        extraction_method="salesforce_contentversion_metadata",
        field_confidence=(first.confidence if first is not None else None),
        document_id=(first.document_id if first is not None else None),
        version_id=(first.version_id if first is not None else None),
        content_hash=(first.content_hash if first is not None else None),
        suggested_action="Review the signed artifact manually or enable a higher-fidelity document verification path.",
    )


def _field_candidates(
    *,
    evidence: list[CloseDocumentEvidenceV1],
    canonical_key: CloseDocumentCanonicalFieldKey,
) -> list[EvidenceFieldV1]:
    fields = [
        field
        for item in evidence
        for field in item.fields
        if field.canonical_key == canonical_key and field.extracted_value is not None
    ]
    return sorted(fields, key=_field_candidate_rank, reverse=True)


def _field_candidate_rank(field: EvidenceFieldV1) -> tuple[int, float, int, int]:
    return (
        _METHOD_PRIORITY.get(field.extraction_method, 0),
        float(field.field_confidence or 0.0),
        1 if field.content_hash else 0,
        1 if field.version_id else 0,
    )


def _has_meaningful_field_conflict(
    *,
    canonical_key: CloseDocumentCanonicalFieldKey,
    candidates: list[EvidenceFieldV1],
) -> bool:
    if len(candidates) < 2:
        return False
    best = candidates[0]
    best_method_rank = _METHOD_PRIORITY.get(best.extraction_method, 0)
    best_value = _normalize_field_value(canonical_key=canonical_key, value=best.extracted_value)
    for other in candidates[1:]:
        if _METHOD_PRIORITY.get(other.extraction_method, 0) != best_method_rank:
            continue
        if abs(float(other.field_confidence or 0.0) - float(best.field_confidence or 0.0)) > 0.08:
            continue
        other_value = _normalize_field_value(canonical_key=canonical_key, value=other.extracted_value)
        if other_value != best_value:
            return True
    return False


def _crm_field_name_for_key(
    *,
    canonical_key: CloseDocumentCanonicalFieldKey,
    config: DealDeskConfig,
) -> str | None:
    mapping = {
        "amount": str(config.amount_rules.amount_field or "").strip() or None,
        "term_months": str(config.amount_rules.term_months_field or "").strip() or None,
        "service_start_date": str(config.date_rules.contract_start_field or "").strip() or None,
        "service_end_date": str(config.date_rules.contract_end_field or "").strip() or None,
    }
    return mapping.get(canonical_key)


def _extraction_status(
    *,
    evidence: list[CloseDocumentEvidenceV1],
    comparisons: list[CloseDocumentFieldComparisonV1],
    issues: list[CloseDocumentExtractionIssueV1],
) -> CloseDocumentExtractionStatus:
    if not evidence:
        return "unsupported"
    if any(issue.code == "DD_DOCUMENT_EVIDENCE_UNSUPPORTED" for issue in issues):
        return "unsupported"
    has_signature_signal = any(
        comparison.canonical_key in _SIGNATURE_KEYS and comparison.extracted_value is not None
        for comparison in comparisons
    )
    has_high_fidelity_signature_signal = any(
        comparison.canonical_key in _SIGNATURE_KEYS
        and comparison.extracted_value is not None
        and comparison.extraction_method in _HIGH_FIDELITY_METHODS
        for comparison in comparisons
    )
    comparable_field_count = sum(
        comparison.extracted_value is not None
        for comparison in comparisons
        if comparison.canonical_key in _COMPARABLE_FIELD_KEYS
    )
    has_high_fidelity_comparable_signal = any(
        comparison.canonical_key in _COMPARABLE_FIELD_KEYS
        for comparison in comparisons
        if comparison.extracted_value is not None
        and comparison.extraction_method in _HIGH_FIDELITY_METHODS
    )
    if not has_signature_signal or comparable_field_count == 0:
        return "partial"
    if not has_high_fidelity_signature_signal:
        return "partial"
    if not has_high_fidelity_comparable_signal:
        return "partial"
    return "extracted"


def _extraction_summary(
    *,
    status: CloseDocumentExtractionStatus,
    evidence: list[CloseDocumentEvidenceV1],
    issues: list[CloseDocumentExtractionIssueV1],
) -> str:
    if status == "not_attempted":
        return "Structured document evidence extraction was not attempted."
    if status == "unsupported":
        return issues[0].message if issues else "Structured document evidence could not be extracted."
    methods = ", ".join(sorted({_method_label(item.extraction_method) for item in evidence}))
    providers = ", ".join(sorted({item.provider_hint for item in evidence if item.provider_hint != "unknown"}))
    base = (
        f"Structured document verification extracted {len(evidence)} evidence row"
        f"{'' if len(evidence) == 1 else 's'}"
    )
    if methods:
        base = f"{base} via {methods}"
    if providers:
        base = f"{base} ({providers})"
    if issues:
        return f"{base}. {issues[0].message}"
    return f"{base}."


def _metadata_text(row: dict[str, Any]) -> str:
    parts = [
        str(row.get("Title") or "").strip(),
        str(row.get("Description") or "").strip(),
        str(row.get("PathOnClient") or "").strip(),
        str(row.get("FileType") or "").strip(),
    ]
    return "\n".join(part for part in parts if part).strip()


def _metadata_excerpt(text: str, *, limit: int = 220) -> str | None:
    normalized = " ".join(text.split())
    if not normalized:
        return None
    if len(normalized) <= limit:
        return normalized
    return normalized[: limit - 3].rstrip() + "..."


def _metadata_source_fingerprint(*, metadata_text: str, row: dict[str, Any]) -> str:
    payload = {
        "title": str(row.get("Title") or "").strip(),
        "description": str(row.get("Description") or "").strip(),
        "path": str(row.get("PathOnClient") or "").strip(),
        "file_type": str(row.get("FileType") or "").strip(),
        "checksum": str(row.get("Checksum") or "").strip(),
        "metadata_text": metadata_text,
    }
    digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest()
    return f"sha256:{digest}"


def _parse_signature_present(text: str) -> _ParsedField | None:
    status_match = _search_label_match(
        text,
        labels=("envelope status", "document status", "agreement status", "status"),
    )
    if status_match is not None:
        normalized_status = status_match.raw_value.strip().lower()
        if normalized_status in {"completed", "signed", "executed", "countersigned"}:
            return _ParsedField(True, status_match.source_locator, status_match.raw_snippet)
        if normalized_status in {"sent", "draft", "viewed", "pending"}:
            return _ParsedField(False, status_match.source_locator, status_match.raw_snippet)

    haystack = text.lower()
    for token in _PENDING_SIGNATURE_TOKENS:
        if token in haystack:
            return _ParsedField(False, "metadata:body", token)
    for token in _SIGNED_STATUS_TOKENS:
        if token in haystack:
            return _ParsedField(True, "metadata:body", token)
    return None


def _parse_int_label(text: str, *, labels: tuple[str, ...]) -> _ParsedField | None:
    raw = _search_label_match(text, labels=labels)
    if raw is None:
        return None
    match = re.search(r"(\d+)", raw.raw_value)
    if not match:
        return None
    try:
        return _ParsedField(int(match.group(1)), raw.source_locator, raw.raw_snippet)
    except Exception:
        return None


def _parse_currency_label(text: str, *, labels: tuple[str, ...]) -> _ParsedField | None:
    raw = _search_label_match(text, labels=labels)
    if raw is None:
        return None
    value = _coerce_float(raw.raw_value)
    if value is None:
        return None
    return _ParsedField(value, raw.source_locator, raw.raw_snippet)


def _parse_term_months(text: str) -> _ParsedField | None:
    raw = _search_label_match(text, labels=("term months", "term", "subscription term"))
    if raw is not None:
        match = re.search(r"(\d+)", raw.raw_value)
        if match:
            try:
                return _ParsedField(int(match.group(1)), raw.source_locator, raw.raw_snippet)
            except Exception:
                return None
    fallback = re.search(r"\b(\d+)\s*(?:months|month|mos|mo)\b", text, flags=re.IGNORECASE)
    if not fallback:
        return None
    try:
        return _ParsedField(int(fallback.group(1)), "metadata:body", fallback.group(0))
    except Exception:
        return None


def _parse_text_label(text: str, *, labels: tuple[str, ...]) -> _ParsedField | None:
    raw = _search_label_match(text, labels=labels)
    if raw is None:
        return None
    cleaned = str(raw.raw_value).strip().strip(";,.")
    if not cleaned:
        return None
    return _ParsedField(cleaned, raw.source_locator, raw.raw_snippet)


def _parse_date_label(text: str, *, labels: tuple[str, ...]) -> _ParsedField | None:
    raw = _search_label_match(text, labels=labels)
    if raw is None:
        return None
    normalized = _normalize_date(raw.raw_value)
    if normalized is None:
        return None
    return _ParsedField(normalized, raw.source_locator, raw.raw_snippet)


def _parse_date_range(text: str, *, label: str) -> tuple[_ParsedField | None, _ParsedField | None]:
    pattern = re.compile(
        rf"{re.escape(label)}\s*[:=]\s*([A-Za-z0-9,/-]+?)\s*(?:to|-)\s*([A-Za-z0-9,/-]+)",
        flags=re.IGNORECASE,
    )
    match = pattern.search(text)
    if not match:
        return None, None
    start = _normalize_date(match.group(1))
    end = _normalize_date(match.group(2))
    raw_snippet = match.group(0)
    locator = f"label:{label}"
    start_field = _ParsedField(start, locator, raw_snippet) if start is not None else None
    end_field = _ParsedField(end, locator, raw_snippet) if end is not None else None
    return start_field, end_field


def _search_label_match(text: str, *, labels: tuple[str, ...]) -> _LabeledMatch | None:
    for label in labels:
        pattern = re.compile(
            rf"{re.escape(label)}\s*[:=]\s*([^\n\r;]+)",
            flags=re.IGNORECASE,
        )
        match = pattern.search(text)
        if match:
            value = str(match.group(1) or "").strip()
            if value:
                return _LabeledMatch(
                    label=label,
                    raw_value=value,
                    raw_snippet=str(match.group(0) or "").strip(),
                    source_locator=f"label:{label}",
                )
    return None


def _metadata_field_confidence(
    *,
    canonical_key: CloseDocumentCanonicalFieldKey,
    provider_hint: CloseDocumentProviderHint,
    source_locator: str | None,
) -> float:
    confidence = 0.50
    if provider_hint in _SUPPORTED_METADATA_PROVIDERS:
        confidence += 0.08
    if str(source_locator or "").startswith("label:"):
        confidence += 0.08
    if canonical_key in {"signature_present", "signer_count"}:
        confidence += 0.04
    if canonical_key in _COMPARABLE_FIELD_KEYS:
        confidence += 0.03
    return max(0.0, min(0.78, confidence))


def _normalize_date(value: Any) -> str | None:
    text = str(value or "").strip()
    if not text:
        return None
    if re.fullmatch(r"\d{4}-\d{2}-\d{2}", text):
        return text
    iso_match = re.match(r"^(\d{4}-\d{2}-\d{2})[T ]", text)
    if iso_match:
        return iso_match.group(1)
    for fmt in _DATE_FORMATS:
        try:
            return datetime.strptime(text, fmt).date().isoformat()
        except ValueError:
            continue
    embedded = re.search(r"(\d{4}-\d{2}-\d{2}|\d{1,2}/\d{1,2}/\d{4}|\d{1,2}-\d{1,2}-\d{4})", text)
    if embedded:
        return _normalize_date(embedded.group(1))
    month_name = re.search(r"([A-Za-z]{3,9}\s+\d{1,2},\s+\d{4})", text)
    if month_name:
        return _normalize_date(month_name.group(1))
    return None


def _coerce_float(value: Any) -> float | None:
    if value in (None, ""):
        return None
    if isinstance(value, bool):
        return None
    if isinstance(value, (int, float)):
        return float(value)
    cleaned = re.sub(r"[^0-9.\-]", "", str(value))
    if not cleaned or cleaned in {"-", ".", "-."}:
        return None
    try:
        return float(cleaned)
    except Exception:
        return None


def _coerce_int(value: Any) -> int | None:
    if value in (None, ""):
        return None
    if isinstance(value, bool):
        return None
    if isinstance(value, int):
        return value
    if isinstance(value, float):
        return int(value)
    match = re.search(r"-?\d+", str(value))
    if not match:
        return None
    try:
        return int(match.group(0))
    except Exception:
        return None


def _normalize_field_value(
    *,
    canonical_key: CloseDocumentCanonicalFieldKey,
    value: Any,
) -> Any:
    if canonical_key == "amount":
        return _coerce_float(value)
    if canonical_key in {"term_months", "signer_count"}:
        return _coerce_int(value)
    if canonical_key in {"service_start_date", "service_end_date", "effective_date", "executed_at"}:
        return _normalize_date(value)
    if canonical_key == "signature_present":
        if isinstance(value, bool):
            return value
        lowered = str(value or "").strip().lower()
        if lowered in {"true", "yes", "signed", "completed", "executed"}:
            return True
        if lowered in {"false", "no", "pending", "unsigned", "sent"}:
            return False
        return None
    if value in (None, ""):
        return None
    return str(value).strip().lower()


def _display_value(*, canonical_key: CloseDocumentCanonicalFieldKey, value: Any) -> str:
    if value in (None, ""):
        return "empty"
    if canonical_key == "amount":
        amount = _coerce_float(value)
        return f"{amount:.2f}" if amount is not None else str(value)
    if canonical_key in {"term_months", "signer_count"}:
        integer = _coerce_int(value)
        return str(integer) if integer is not None else str(value)
    if canonical_key in {"service_start_date", "service_end_date", "effective_date", "executed_at"}:
        return _normalize_date(value) or str(value)
    return str(value)


def _document_signal_summary(*, canonical_key: CloseDocumentCanonicalFieldKey, value: Any) -> str:
    labels = {
        "signature_present": "signature status",
        "signer_count": "signer count",
        "executed_at": "execution date",
        "effective_date": "effective date",
        "legal_entity": "legal entity",
        "product_summary": "product summary",
    }
    label = labels.get(canonical_key, canonical_key.replace("_", " "))
    return f"Document signal for {label}: {_display_value(canonical_key=canonical_key, value=value)}."


def _field_value(fields: list[EvidenceFieldV1], canonical_key: CloseDocumentCanonicalFieldKey) -> Any:
    for field in fields:
        if field.canonical_key == canonical_key:
            return field.extracted_value
    return None


def _evidence_id(
    *,
    method: CloseDocumentExtractionMethod,
    document_id: str,
    version_id: str | None,
) -> str:
    suffix = version_id or "latest"
    return f"doc-evidence-{method}-{document_id}-{suffix}"


def _evidence_summary(
    *,
    title: str,
    provider_hint: CloseDocumentProviderHint,
    extraction_method: CloseDocumentExtractionMethod,
    signature_present: bool | None,
    amount: float | None,
    term_months: int | None,
    service_start_date: str | None,
    service_end_date: str | None,
) -> str:
    parts = [title]
    if provider_hint != "unknown":
        parts.append(provider_hint)
    parts.append(_method_label(extraction_method))
    if signature_present is True:
        parts.append("signed")
    elif signature_present is False:
        parts.append("unsigned")
    if amount is not None:
        parts.append(f"amount {amount:.2f}")
    if term_months is not None:
        parts.append(f"term {term_months} months")
    if service_start_date and service_end_date:
        parts.append(f"{service_start_date} to {service_end_date}")
    return " | ".join(parts)


def _method_label(method: CloseDocumentExtractionMethod) -> str:
    mapping = {
        "provider_api": "provider API",
        "document_vision": "document vision",
        "salesforce_contentversion_metadata": "metadata parse",
    }
    return mapping.get(method, method.replace("_", " "))


def _sort_issues(issues: list[CloseDocumentExtractionIssueV1]) -> list[CloseDocumentExtractionIssueV1]:
    classification_rank = {"block_close": 0, "approval_required": 1, "informational": 2}
    severity_rank = {"blocking": 0, "warning": 1, "info": 2}
    return sorted(
        issues,
        key=lambda issue: (
            classification_rank.get(issue.classification, 9),
            severity_rank.get(issue.severity, 9),
            str(issue.code or ""),
        ),
    )


def _load_document_vision_config(state_dir: Path | None) -> AIProviderConfig | None:
    if state_dir is None:
        return None
    config = AIProviderStore(state_dir).load_config()
    if config is None or not config.enabled:
        return None
    return config


def load_document_vision_runtime_capability_profile(
    state_dir: str | Path | None,
):
    resolved_state_dir = Path(state_dir) if state_dir not in (None, "") else None
    config = _load_document_vision_config(resolved_state_dir)
    if config is None:
        return None
    return build_model_capability_profile(
        config,
        connection_id="document",
        supports_structured_output=True,
        supports_document_inputs=True,
        capability_source="declared",
    )


def _document_vision_runtime_available(state_dir: Path | None) -> bool:
    profile = load_document_vision_runtime_capability_profile(state_dir)
    if profile is None:
        return False
    requirement = workflow_model_requirement("document_reconciliation")
    if not model_satisfies_workflow(profile, requirement):
        return False
    return True


__all__ = [
    "AdobeSignApiProvider",
    "CloseDocumentCanonicalFieldKey",
    "CloseDocumentEvidenceV1",
    "CloseDocumentExtractionIssueV1",
    "CloseDocumentExtractionMethod",
    "CloseDocumentExtractionResultV1",
    "CloseDocumentExtractionStatus",
    "AdobeAgreementProvider",
    "CloseDocumentFieldComparisonOutcome",
    "CloseDocumentFieldComparisonV1",
    "DocuSignApiProvider",
    "DocuSignManagedPackageProvider",
    "PandaDocApiProvider",
    "PandaDocDocumentProvider",
    "DocumentEvidenceProvider",
    "EvidenceFieldV1",
    "build_document_evidence_providers",
    "extract_structured_close_document_evidence",
    "load_document_vision_runtime_capability_profile",
    "preflight_close_document_inventory",
]
