"""Canonical lead fields available to the rule engine and the template engine.

Rules are written against stable camelCase field names (``jobTitle``) so that the
UI, the extension and stored rules all speak the same language, independent of
the snake_case column names used by SQLAlchemy.
"""

from __future__ import annotations

from typing import Any

#: canonical field name -> ORM attribute name
FIELD_MAP: dict[str, str] = {
    "firstName": "first_name",
    "lastName": "last_name",
    "fullName": "full_name",
    "jobTitle": "job_title",
    "company": "company",
    "industry": "industry",
    "location": "location",
    "companySize": "company_size",
    "headline": "headline",
    "about": "about",
    "profileUrl": "profile_url",
    "website": "website",
    "keywords": "keywords",
    "tags": "tags",
    "score": "score",
    "status": "status",
    "connectionStatus": "connection_status",
    "messageStatus": "message_status",
    "replyStatus": "reply_status",
    "source": "source",
}

#: fields that hold numbers, used by greater_than / less_than / between
NUMERIC_FIELDS = {"companySize", "score"}

#: fields that hold lists of strings
LIST_FIELDS = {"keywords", "tags"}

#: reverse lookup so snake_case input is accepted too
_REVERSE_MAP = {v: k for k, v in FIELD_MAP.items()}

RULE_FIELDS: list[str] = list(FIELD_MAP.keys())


def normalize_field(field: str) -> str:
    """Accept ``job_title`` or ``jobTitle`` and always return the canonical name."""
    if field in FIELD_MAP:
        return field
    if field in _REVERSE_MAP:
        return _REVERSE_MAP[field]
    return field


def lead_to_context(lead: Any) -> dict[str, Any]:
    """Build the plain-dict evaluation context for a Lead ORM object or a dict."""
    context: dict[str, Any] = {}
    for canonical, attribute in FIELD_MAP.items():
        if isinstance(lead, dict):
            value = lead.get(canonical, lead.get(attribute))
        else:
            value = getattr(lead, attribute, None)
        context[canonical] = value

    # Derive fullName when only the name parts are supplied.
    if not context.get("fullName"):
        parts = [context.get("firstName"), context.get("lastName")]
        joined = " ".join(part for part in parts if part)
        context["fullName"] = joined or None
    return context
