"""Query builder for FoundryGraph-backed company list builder."""

from __future__ import annotations

import asyncio
import json
import logging
import os
from typing import Any, Dict, Iterable, List, Tuple, TYPE_CHECKING

# Lazy import google.cloud.bigquery to avoid GCP credential discovery hang at startup
if TYPE_CHECKING:
    from google.cloud import bigquery as bq_typing

_bigquery_module = None
logger = logging.getLogger(__name__)

def _get_bigquery():
    """Lazily import BigQuery module to avoid startup hang."""
    global _bigquery_module
    if _bigquery_module is None:
        from google.cloud import bigquery
        _bigquery_module = bigquery
    return _bigquery_module


class CompanyQueryBuilder:
    """Build parameterized BigQuery queries for company list search/export."""

    PROJECT_ID = os.getenv("FM_FOUNDRYGRAPH_PROJECT_ID") or os.getenv(
        "GOOGLE_CLOUD_PROJECT"
    )
    TABLE = (
        f"{PROJECT_ID}.foundrygraph_gold.companies_gold"
        if PROJECT_ID
        else "foundrygraph_gold.companies_gold"
    )

    SORTABLE_FIELDS = {"name", "employees", "revenue_band", "hq_country", "quality_score"}
    FILTERABLE_FIELDS = {
        "industry",
        "country",
        "state",
        "employee_band",
        "employee_min",
        "employee_max",
        "revenue_band",
        "revenue_min",
        "revenue_max",
        "has_domain",
        "is_parent",
        "is_subsidiary",
    }
    MAX_COLUMNS = 40

    # Base exportable columns; actual export list is computed dynamically from schema.
    EXPORTABLE_COLUMNS_BASE: Dict[str, str] = {
        "name": "label",
        "domain": "domain",
        "website": "website",
        "quality_score": "quality_score",
        "fg_id": "fg_id",
        "wikidata_id": "wikidata_id",
        "parent_org": "parent_org",
        "has_domain": "domain IS NOT NULL",
        "is_parent": "parent_org IS NULL",
        "is_subsidiary": "parent_org IS NOT NULL",
    }

    DEFAULT_COLUMNS: List[str] = [
        "name",
        "domain",
        "website",
        "quality_score",
    ]

    COLUMN_PRESETS: List[Dict[str, Any]] = [
        {
            "key": "basic",
            "label": "Core Data",
            "columns": [
                "name",
                "domain",
                "website",
                "fg_id",
                "quality_score",
            ],
        },
        {
            "key": "firmographics",
            "label": "Firmographics",
            "columns": [
                "name",
                "domain",
                "industry",
                "employees",
                "employee_band",
                "revenue",
                "revenue_band",
                "hq_country",
                "hq_state",
            ],
        },
        {
            "key": "social",
            "label": "Social",
            "columns": [
                "name",
                "domain",
                "website",
                "linkedin_url",
                "quality_score",
            ],
        },
        {
            "key": "with_wikidata",
            "label": "With Wikidata ID",
            "columns": ["name", "domain", "website", "wikidata_id", "quality_score"],
        },
        {
            "key": "with_hierarchy",
            "label": "With Parent Org",
            "columns": [
                "name",
                "domain",
                "website",
                "parent_org",
                "is_parent",
                "quality_score",
            ],
        },
    ]

    # Mapping from LinkedIn industries to Wikidata QIDs for filter matching
    # Each LinkedIn industry maps to one or more Wikidata entity IDs
    # Updated with actual QIDs found in FoundryGraph data for better matching
    LINKEDIN_TO_WIKIDATA: Dict[str, List[str]] = {
        "accounting": ["Q4116214", "Q82142"],
        "airlines/aviation": ["Q46970", "Q8205", "Q1757562"],  # Q1757562 = airline (most common)
        "alternative dispute resolution": ["Q495015"],
        "alternative medicine": ["Q188504"],
        "animation": ["Q11425", "Q969040"],  # + creative industries
        "apparel & fashion": ["Q3661311", "Q12684", "Q11828862"],  # + clothing industry
        "architecture & planning": ["Q12271", "Q891723"],
        "arts & crafts": ["Q27066", "Q178885", "Q969040"],  # + creative industries
        "automotive": ["Q190117", "Q11423"],
        "aviation & aerospace": ["Q8205", "Q46970", "Q41176", "Q3477363", "Q1757562"],  # + aerospace, airline
        "banking": ["Q132624", "Q806718", "Q57774188"],  # + financial sector
        "biotechnology": ["Q7191", "Q9027"],
        "broadcast media": ["Q11033", "Q15265344", "Q11030"],  # + journalism
        "building materials": ["Q206615"],
        "business supplies & equipment": ["Q4830453"],
        "capital markets": ["Q107437571", "Q43015", "Q57774188"],  # + financial sector
        "chemicals": ["Q173725", "Q11344", "Q207652"],  # + chemical industry
        "civic & social organization": ["Q2092297", "Q43229", "Q130370871"],  # + business associations
        "civil engineering": ["Q77590"],
        "commercial real estate": ["Q1192650", "Q10494"],
        "computer & network security": ["Q3510521", "Q17144"],
        "computer games": ["Q7889", "Q941594"],  # video game industry (high freq)
        "computer hardware": ["Q3966", "Q11457"],
        "computer networking": ["Q1301371", "Q7942"],
        "computer software": ["Q7397", "Q326478", "Q880371"],  # + software industry (high freq)
        "construction": ["Q385378", "Q13405"],
        "consumer electronics": ["Q5169399"],
        "consumer goods": ["Q868404", "Q5169401"],
        "consumer services": ["Q3533467"],
        "cosmetics": ["Q668953", "Q131207"],
        "dairy": ["Q130019", "Q42757", "Q540912"],  # + food industry
        "defense & space": ["Q232405", "Q41176", "Q3477363"],  # + aerospace industry
        "design": ["Q82604", "Q969040"],  # + creative industries
        "education management": ["Q8434", "Q1344"],
        "e-learning": ["Q1068473", "Q192291"],
        "electrical & electronic manufacturing": ["Q8148", "Q125637", "Q187939"],  # + industrial manufacturing
        "entertainment": ["Q1363919", "Q173799", "Q969040"],  # + creative industries
        "environmental services": ["Q2934", "Q191022", "Q130370849"],  # + environment
        "events services": ["Q1656682", "Q200764"],
        "executive office": ["Q327333", "Q22687"],
        "facilities services": ["Q1402009"],
        "farming": ["Q11451", "Q271669"],
        "financial services": ["Q837171", "Q43015", "Q57774188", "Q806718"],  # + financial sector, institution
        "fine art": ["Q36649", "Q838948", "Q969040"],  # + creative industries
        "fishery": ["Q14373", "Q180538"],
        "food & beverages": ["Q28225653", "Q8194", "Q540912", "Q11644505"],  # + food industry, brewing
        "food production": ["Q28225653", "Q107414", "Q540912"],  # + food industry
        "fundraising": ["Q1247753", "Q1021488"],  # + community foundation
        "furniture": ["Q14745", "Q1184963"],
        "gambling & casinos": ["Q11416", "Q111042"],
        "glass, ceramics & concrete": ["Q11469", "Q45621"],
        "government administration": ["Q7188", "Q327333", "Q112166113"],  # + public administration
        "government relations": ["Q1170714", "Q7188"],
        "graphic design": ["Q185925", "Q969040"],  # + creative industries
        "health, wellness & fitness": ["Q12147", "Q309", "Q31207", "Q15067276"],  # + health care
        "higher education": ["Q38723", "Q3918", "Q136822", "Q112166132"],  # + higher education QIDs
        "hospital & health care": ["Q16917", "Q12147", "Q31207", "Q15067276", "Q130370834"],  # + hospitals
        "hospitality": ["Q274582", "Q170173", "Q1495452"],  # + hospitality industry
        "human resources": ["Q1056396", "Q1423816"],
        "import & export": ["Q181036", "Q178659"],
        "individual & family services": ["Q1423816", "Q5283295"],
        "industrial automation": ["Q184199", "Q184148", "Q187939"],  # + industrial manufacturing
        "information services": ["Q11661", "Q5628757"],
        "information technology & services": ["Q11661", "Q326478", "Q880371"],  # + software industry
        "insurance": ["Q43183", "Q210272"],
        "international affairs": ["Q166542"],
        "international trade & development": ["Q178803", "Q131123"],
        "internet": ["Q75", "Q6881511", "Q484847"],  # + e-commerce
        "investment banking": ["Q40140", "Q806718", "Q57774188"],  # + financial sector
        "investment management": ["Q212247", "Q37172", "Q57774188"],  # + financial sector
        "judiciary": ["Q684245", "Q7163"],
        "law enforcement": ["Q44554"],
        "law practice": ["Q38025", "Q185434"],
        "legal services": ["Q38025", "Q185434"],
        "legislative office": ["Q11204", "Q7188"],
        "leisure, travel & tourism": ["Q11348", "Q858", "Q49389"],  # + tourism
        "libraries": ["Q7075"],
        "logistics & supply chain": ["Q177777", "Q207452"],
        "luxury goods & jewelry": ["Q205663", "Q161439"],
        "machinery": ["Q11019", "Q178061", "Q187939"],  # + industrial manufacturing
        "management consulting": ["Q1163715", "Q265621"],
        "maritime": ["Q155930", "Q184098"],
        "market research": ["Q913764", "Q42240"],  # + research
        "marketing & advertising": ["Q39809", "Q39703"],
        "mechanical or industrial engineering": ["Q101333", "Q11023", "Q187939"],  # + industrial manufacturing
        "media production": ["Q11033", "Q1361580", "Q969040"],  # + creative industries
        "medical devices": ["Q131194", "Q272769", "Q15067276"],  # + health care industry
        "medical practice": ["Q11190", "Q12147", "Q31207"],  # + health care
        "mental health care": ["Q189533", "Q1856049", "Q31207"],  # + health care
        "military": ["Q8473"],
        "mining & metals": ["Q44497", "Q11426"],
        "motion pictures & film": ["Q11424", "Q691008", "Q1415395", "Q969040"],  # + film industry, creative
        "museums & institutions": ["Q33506"],
        "music": ["Q638", "Q859247", "Q746359", "Q969040"],  # + music industry, creative
        "nanotechnology": ["Q7155"],
        "newspapers": ["Q11032", "Q192283", "Q11030"],  # + journalism
        "non-profit organization management": ["Q163740", "Q3533467", "Q1021488"],  # + community foundation
        "oil & energy": ["Q862571", "Q2151621"],  # + energy industry
        "online media": ["Q11033", "Q6881511", "Q484847"],  # + e-commerce
        "outsourcing/offshoring": ["Q487344", "Q1138957"],
        "package/freight delivery": ["Q319604", "Q182838"],
        "packaging & containers": ["Q170065", "Q207540"],
        "paper & forest products": ["Q187541", "Q189445"],
        "performing arts": ["Q184485", "Q969040"],  # + creative industries
        "pharmaceuticals": ["Q507443", "Q35535"],
        "philanthropy": ["Q170475", "Q1021488"],  # + community foundation
        "photography": ["Q11633", "Q969040"],  # + creative industries
        "plastics": ["Q11474", "Q899792"],
        "political organization": ["Q7278", "Q7163"],
        "primary/secondary education": ["Q9826", "Q149566"],
        "printing": ["Q3490", "Q328865", "Q3972943"],  # + publishing
        "professional training & coaching": ["Q1207553", "Q906127"],
        "program development": ["Q8425", "Q7397", "Q880371"],  # + software industry
        "public policy": ["Q7454"],
        "public relations & communications": ["Q373546", "Q197548"],
        "public safety": ["Q294414"],
        "publishing": ["Q3331189", "Q328865", "Q3972943"],  # + publishing
        "railroad manufacture": ["Q160169", "Q3565868"],  # + rail transport
        "ranching": ["Q1391954", "Q188869"],
        "real estate": ["Q10494", "Q1192650"],
        "recreational facilities & services": ["Q184872", "Q11410", "Q124022875"],  # + sporting activities
        "religious institutions": ["Q9174", "Q130370869"],  # + religious congregations
        "renewables & environment": ["Q12748", "Q2934", "Q130370849", "Q2151621"],  # + environment, energy
        "research": ["Q1321845", "Q735", "Q42240"],  # + research
        "restaurants": ["Q11707", "Q28225653", "Q540912"],  # + food industry
        "retail": ["Q126793", "Q27584"],  # retail industry (high freq)
        "security & investigations": ["Q3037724", "Q5616678"],
        "semiconductors": ["Q11461", "Q192858"],
        "shipbuilding": ["Q474200"],
        "sporting goods": ["Q200538", "Q124022875"],  # + sporting activities
        "sports": ["Q349", "Q1323943", "Q124022875"],  # + sporting activities (highest freq!)
        "staffing & recruiting": ["Q1423816", "Q431603"],
        "supermarkets": ["Q180846", "Q126793"],  # + retail industry
        "telecommunications": ["Q1788545", "Q418", "Q2401742"],  # + telecommunications
        "textiles": ["Q28823", "Q573649", "Q11828862"],  # + clothing industry
        "think tanks": ["Q179527"],
        "tobacco": ["Q907703", "Q1566"],
        "translation & localization": ["Q7553", "Q15275719"],
        "transportation/trucking/railroad": ["Q7590", "Q160169", "Q3565868", "Q178512"],  # + rail, public transport
        "utilities": ["Q173376", "Q35127", "Q2151621"],  # + energy industry
        "venture capital & private equity": ["Q3295867", "Q219538", "Q57774188"],  # + financial sector
        "veterinary": ["Q170201"],
        "warehousing": ["Q1362236", "Q17022376"],
        "wholesale": ["Q27584", "Q181036", "Q126793"],  # + retail industry
        "wine & spirits": ["Q282", "Q1269", "Q11644505"],  # + brewing industry
        "wireless": ["Q192691", "Q1788545", "Q2401742"],  # + telecommunications
        "writing & editing": ["Q36180", "Q1093910", "Q3972943"],  # + publishing
    }

    # LinkedIn standard industries for filter UI
    CURATED_INDUSTRIES: List[str] = [
        "accounting",
        "airlines/aviation",
        "alternative dispute resolution",
        "alternative medicine",
        "animation",
        "apparel & fashion",
        "architecture & planning",
        "arts & crafts",
        "automotive",
        "aviation & aerospace",
        "banking",
        "biotechnology",
        "broadcast media",
        "building materials",
        "business supplies & equipment",
        "capital markets",
        "chemicals",
        "civic & social organization",
        "civil engineering",
        "commercial real estate",
        "computer & network security",
        "computer games",
        "computer hardware",
        "computer networking",
        "computer software",
        "construction",
        "consumer electronics",
        "consumer goods",
        "consumer services",
        "cosmetics",
        "dairy",
        "defense & space",
        "design",
        "education management",
        "e-learning",
        "electrical & electronic manufacturing",
        "entertainment",
        "environmental services",
        "events services",
        "executive office",
        "facilities services",
        "farming",
        "financial services",
        "fine art",
        "fishery",
        "food & beverages",
        "food production",
        "fundraising",
        "furniture",
        "gambling & casinos",
        "glass, ceramics & concrete",
        "government administration",
        "government relations",
        "graphic design",
        "health, wellness & fitness",
        "higher education",
        "hospital & health care",
        "hospitality",
        "human resources",
        "import & export",
        "individual & family services",
        "industrial automation",
        "information services",
        "information technology & services",
        "insurance",
        "international affairs",
        "international trade & development",
        "internet",
        "investment banking",
        "investment management",
        "judiciary",
        "law enforcement",
        "law practice",
        "legal services",
        "legislative office",
        "leisure, travel & tourism",
        "libraries",
        "logistics & supply chain",
        "luxury goods & jewelry",
        "machinery",
        "management consulting",
        "maritime",
        "market research",
        "marketing & advertising",
        "mechanical or industrial engineering",
        "media production",
        "medical devices",
        "medical practice",
        "mental health care",
        "military",
        "mining & metals",
        "motion pictures & film",
        "museums & institutions",
        "music",
        "nanotechnology",
        "newspapers",
        "non-profit organization management",
        "oil & energy",
        "online media",
        "outsourcing/offshoring",
        "package/freight delivery",
        "packaging & containers",
        "paper & forest products",
        "performing arts",
        "pharmaceuticals",
        "philanthropy",
        "photography",
        "plastics",
        "political organization",
        "primary/secondary education",
        "printing",
        "professional training & coaching",
        "program development",
        "public policy",
        "public relations & communications",
        "public safety",
        "publishing",
        "railroad manufacture",
        "ranching",
        "real estate",
        "recreational facilities & services",
        "religious institutions",
        "renewables & environment",
        "research",
        "restaurants",
        "retail",
        "security & investigations",
        "semiconductors",
        "shipbuilding",
        "sporting goods",
        "sports",
        "staffing & recruiting",
        "supermarkets",
        "telecommunications",
        "textiles",
        "think tanks",
        "tobacco",
        "translation & localization",
        "transportation/trucking/railroad",
        "utilities",
        "venture capital & private equity",
        "veterinary",
        "warehousing",
        "wholesale",
        "wine & spirits",
        "wireless",
        "writing & editing",
    ]

    # Mapping from country names to Wikidata QIDs
    # Common countries used in B2B data - maps to Wikidata entity IDs
    COUNTRY_TO_WIKIDATA: Dict[str, str] = {
        "united states": "Q30",
        "us": "Q30",
        "usa": "Q30",
        "united kingdom": "Q145",
        "uk": "Q145",
        "great britain": "Q145",
        "canada": "Q16",
        "australia": "Q408",
        "germany": "Q183",
        "france": "Q142",
        "japan": "Q17",
        "china": "Q148",
        "india": "Q668",
        "brazil": "Q155",
        "mexico": "Q96",
        "spain": "Q29",
        "italy": "Q38",
        "netherlands": "Q55",
        "switzerland": "Q39",
        "sweden": "Q34",
        "norway": "Q20",
        "denmark": "Q35",
        "finland": "Q33",
        "belgium": "Q31",
        "austria": "Q40",
        "ireland": "Q27",
        "poland": "Q36",
        "russia": "Q159",
        "south korea": "Q884",
        "korea": "Q884",
        "singapore": "Q334",
        "hong kong": "Q8646",
        "taiwan": "Q865",
        "israel": "Q801",
        "united arab emirates": "Q878",
        "uae": "Q878",
        "saudi arabia": "Q851",
        "south africa": "Q258",
        "new zealand": "Q664",
        "argentina": "Q414",
        "chile": "Q298",
        "colombia": "Q739",
        "indonesia": "Q252",
        "malaysia": "Q833",
        "thailand": "Q869",
        "vietnam": "Q881",
        "philippines": "Q928",
        "pakistan": "Q843",
        "turkey": "Q43",
        "egypt": "Q79",
        "nigeria": "Q1033",
        "kenya": "Q114",
        "portugal": "Q45",
        "greece": "Q41",
        "czech republic": "Q213",
        "czechia": "Q213",
        "romania": "Q218",
        "hungary": "Q28",
        "ukraine": "Q212",
        "luxembourg": "Q32",
        "iceland": "Q189",
    }

    # Curated list of countries for the UI dropdown
    CURATED_COUNTRIES: List[str] = [
        "united states",
        "united kingdom",
        "canada",
        "australia",
        "germany",
        "france",
        "japan",
        "china",
        "india",
        "brazil",
        "mexico",
        "spain",
        "italy",
        "netherlands",
        "switzerland",
        "sweden",
        "norway",
        "denmark",
        "finland",
        "belgium",
        "austria",
        "ireland",
        "poland",
        "russia",
        "south korea",
        "singapore",
        "hong kong",
        "taiwan",
        "israel",
        "united arab emirates",
        "saudi arabia",
        "south africa",
        "new zealand",
        "argentina",
        "chile",
        "colombia",
        "indonesia",
        "malaysia",
        "thailand",
        "vietnam",
        "philippines",
        "pakistan",
        "turkey",
        "egypt",
        "nigeria",
        "kenya",
        "portugal",
        "greece",
        "czech republic",
        "romania",
        "hungary",
        "ukraine",
        "luxembourg",
        "iceland",
    ]

    def __init__(self) -> None:
        bq = _get_bigquery()
        self._client = bq.Client(project=self.PROJECT_ID) if self.PROJECT_ID else bq.Client()
        self._table_columns_cache: set[str] | None = None
        self._table_schema_cache: dict[str, Any] | None = None
        self._exportable_columns_cache: dict[str, str] | None = None

    @property
    def client(self) -> "bq_typing.Client":
        return self._client

    def _table_schema(self) -> dict[str, Any]:
        if self._table_schema_cache is not None:
            return self._table_schema_cache
        try:
            table = self.client.get_table(self.TABLE)
            schema = {field.name: field for field in table.schema}
            self._table_schema_cache = schema
            if self._table_columns_cache is None:
                self._table_columns_cache = set(schema)
            self._exportable_columns_cache = None
        except Exception as exc:
            logger.warning("Failed to fetch BigQuery schema for %s: %s", self.TABLE, exc)
            self._table_schema_cache = {}
            if self._table_columns_cache is None:
                self._table_columns_cache = set()
            self._exportable_columns_cache = None
        return self._table_schema_cache

    def _table_columns(self) -> set[str]:
        if self._table_columns_cache is not None:
            return self._table_columns_cache
        schema = self._table_schema()
        self._table_columns_cache = set(schema)
        self._exportable_columns_cache = None
        return self._table_columns_cache

    def _has_column(self, name: str) -> bool:
        return name in self._table_columns()

    @staticmethod
    def _first_available(columns: set[str], *names: str) -> str | None:
        for name in names:
            if name in columns:
                return name
        return None

    def _industry_field(self) -> Dict[str, Any] | None:
        schema = self._table_schema()
        for name in ("industries", "industry"):
            field = schema.get(name)
            if field is None:
                continue
            field_type = (getattr(field, "field_type", "") or "").upper()
            mode = (getattr(field, "mode", "") or "NULLABLE").upper()
            subfields = [sub.name for sub in getattr(field, "fields", []) or []]
            return {
                "name": name,
                "field_type": field_type,
                "mode": mode,
                "subfields": subfields,
            }
        return None

    @staticmethod
    def _pick_label_field(subfields: Iterable[str]) -> str | None:
        normalized = {name.lower(): name for name in subfields}
        for candidate in ("label", "name", "industry", "value", "id"):
            if candidate in normalized:
                return normalized[candidate]
        return None

    @staticmethod
    def _json_label_expr(json_ref: str) -> str:
        return (
            "COALESCE("
            f"SAFE.JSON_VALUE({json_ref}, '$.label'), "
            f"SAFE.JSON_VALUE({json_ref}, '$.name'), "
            f"SAFE.JSON_VALUE({json_ref}, '$.industry'), "
            f"SAFE.JSON_VALUE({json_ref}, '$.value'), "
            f"SAFE.JSON_VALUE({json_ref}, '$.id'), "
            f"SAFE.JSON_VALUE({json_ref}, '$')"
            ")"
        )

    def _industry_labels_array_expr(
        self,
        column_ref: str,
        field_type: str,
        mode: str,
        subfields: Iterable[str],
    ) -> str:
        field_type = (field_type or "").upper()
        mode = (mode or "NULLABLE").upper()

        if field_type == "JSON":
            if mode == "REPEATED":
                label_expr = self._json_label_expr("ind")
                return f"""
                    ARRAY(
                        SELECT label FROM (
                            SELECT {label_expr} AS label
                            FROM UNNEST({column_ref}) AS ind
                        )
                        WHERE label IS NOT NULL AND label != ''
                    )
                """
            label_expr = self._json_label_expr("industry")
            direct_expr = self._json_label_expr(column_ref)
            return f"""
                ARRAY(
                    SELECT label FROM (
                        SELECT {label_expr} AS label
                        FROM UNNEST(SAFE.JSON_QUERY_ARRAY({column_ref}, '$')) AS industry
                        UNION ALL
                        SELECT {direct_expr} AS label
                    )
                    WHERE label IS NOT NULL AND label != ''
                )
            """

        if mode == "REPEATED":
            if field_type in ("RECORD", "STRUCT"):
                label_field = self._pick_label_field(subfields)
                if label_field:
                    return f"""
                        ARRAY(
                            SELECT label FROM (
                                SELECT ind.{label_field} AS label
                                FROM UNNEST({column_ref}) AS ind
                            )
                            WHERE label IS NOT NULL AND label != ''
                        )
                    """
                return f"""
                    ARRAY(
                        SELECT label FROM (
                            SELECT TO_JSON_STRING(ind) AS label
                            FROM UNNEST({column_ref}) AS ind
                        )
                        WHERE label IS NOT NULL AND label != ''
                    )
                """
            if field_type == "STRING":
                return f"""
                    ARRAY(
                        SELECT label FROM (
                            SELECT NULLIF(TRIM(ind), '') AS label
                            FROM UNNEST({column_ref}) AS ind
                        )
                        WHERE label IS NOT NULL AND label != ''
                    )
                """
            return f"""
                ARRAY(
                    SELECT label FROM (
                        SELECT TO_JSON_STRING(ind) AS label
                        FROM UNNEST({column_ref}) AS ind
                    )
                    WHERE label IS NOT NULL AND label != ''
                )
            """

        if field_type in ("RECORD", "STRUCT"):
            label_field = self._pick_label_field(subfields)
            if label_field:
                return f"""
                    ARRAY(
                        SELECT label FROM (
                            SELECT {column_ref}.{label_field} AS label
                        )
                        WHERE label IS NOT NULL AND label != ''
                    )
                """
            return f"""
                ARRAY(
                    SELECT label FROM (
                        SELECT TO_JSON_STRING({column_ref}) AS label
                    )
                    WHERE label IS NOT NULL AND label != ''
                )
            """

        if field_type == "STRING":
            return f"""
                ARRAY(
                    SELECT label FROM (
                        SELECT NULLIF(TRIM({column_ref}), '') AS label
                    )
                    WHERE label IS NOT NULL AND label != ''
                )
            """

        return "ARRAY<STRING>[]"

    def _country_expr(self) -> str | None:
        columns = self._table_columns()
        column = self._first_available(columns, "hq_country", "country_code", "country")
        if column:
            return f"LOWER({column})"
        if "headquarters" in columns:
            return (
                "LOWER(COALESCE(JSON_VALUE(headquarters, '$.country'), "
                "JSON_VALUE(headquarters, '$[0].country')))"
            )
        return None

    def _country_value_expr(self) -> str | None:
        columns = self._table_columns()
        column = self._first_available(columns, "hq_country", "country_code", "country")
        if column:
            return column
        if "headquarters" in columns:
            return (
                "COALESCE(JSON_VALUE(headquarters, '$.country'), "
                "JSON_VALUE(headquarters, '$[0].country'))"
            )
        return None

    def _state_expr(self) -> str | None:
        columns = self._table_columns()
        column = self._first_available(columns, "hq_state", "state")
        if column:
            return f"LOWER({column})"
        if "headquarters" in columns:
            return (
                "LOWER(COALESCE(JSON_VALUE(headquarters, '$.state'), "
                "JSON_VALUE(headquarters, '$[0].state')))"
            )
        return None

    def _state_value_expr(self) -> str | None:
        columns = self._table_columns()
        column = self._first_available(columns, "hq_state", "state")
        if column:
            return column
        if "headquarters" in columns:
            return (
                "COALESCE(JSON_VALUE(headquarters, '$.state'), "
                "JSON_VALUE(headquarters, '$[0].state'))"
            )
        return None

    def _employee_count_expr(self) -> str | None:
        columns = self._table_columns()
        if "employees" in columns:
            # Cast via FLOAT64 first to handle decimal values like 1920127.95
            return (
                "SAFE_CAST(TRUNC(SAFE_CAST(COALESCE(JSON_VALUE(employees, '$.count'), "
                "JSON_VALUE(employees, '$.value')) AS FLOAT64)) AS INT64)"
            )
        if "employee_count" in columns:
            return "SAFE_CAST(TRUNC(SAFE_CAST(employee_count AS FLOAT64)) AS INT64)"
        return None

    @staticmethod
    def _employee_band_expr_from(count_expr: str) -> str:
        return f"""
            CASE
                WHEN {count_expr} IS NULL THEN NULL
                WHEN {count_expr} < 50 THEN '1-49'
                WHEN {count_expr} < 250 THEN '50-249'
                WHEN {count_expr} < 1000 THEN '250-999'
                WHEN {count_expr} < 5000 THEN '1000-4999'
                ELSE '5000+'
            END
        """

    def _employee_band_expr(self) -> str | None:
        count_expr = self._employee_count_expr()
        if not count_expr:
            return None
        return self._employee_band_expr_from(count_expr)

    def _revenue_value_expr(self) -> str | None:
        columns = self._table_columns()
        if "revenue" in columns:
            # Cast via FLOAT64 first to handle decimal values like 1920127.95
            return (
                "SAFE_CAST(TRUNC(SAFE_CAST(COALESCE(JSON_VALUE(revenue, '$.amount'), "
                "JSON_VALUE(revenue, '$.value')) AS FLOAT64)) AS INT64)"
            )
        if "revenue_amount" in columns:
            return "SAFE_CAST(TRUNC(SAFE_CAST(revenue_amount AS FLOAT64)) AS INT64)"
        return None

    @staticmethod
    def _revenue_band_expr_from(value_expr: str) -> str:
        return f"""
            CASE
                WHEN {value_expr} IS NULL THEN NULL
                WHEN {value_expr} < 1000000 THEN '<$1M'
                WHEN {value_expr} < 10000000 THEN '$1M-$10M'
                WHEN {value_expr} < 50000000 THEN '$10M-$50M'
                WHEN {value_expr} < 100000000 THEN '$50M-$100M'
                WHEN {value_expr} < 500000000 THEN '$100M-$500M'
                WHEN {value_expr} < 1000000000 THEN '$500M-$1B'
                ELSE '$1B+'
            END
        """

    def _revenue_band_expr(self) -> str | None:
        value_expr = self._revenue_value_expr()
        if not value_expr:
            return None
        return self._revenue_band_expr_from(value_expr)

    def _linkedin_expr(self) -> str | None:
        columns = self._table_columns()
        return self._first_available(
            columns,
            "linkedin_url",
            "linkedin",
            "linkedin_company_url",
            "linkedin_company",
        )

    def _industry_export_expr(self) -> str | None:
        industry_field = self._industry_field()
        if not industry_field:
            return None
        field_name = industry_field["name"]
        field_type = industry_field["field_type"]
        mode = industry_field["mode"]
        subfields = industry_field["subfields"]

        if field_type == "STRING" and mode != "REPEATED":
            return field_name

        label_array_expr = self._industry_labels_array_expr(
            f"{field_name}", field_type, mode, subfields
        ).strip()
        if label_array_expr == "ARRAY<STRING>[]":
            return field_name
        return f"ARRAY_TO_STRING({label_array_expr}, '; ')"

    def exportable_columns(self) -> Dict[str, str]:
        if self._exportable_columns_cache is not None:
            return self._exportable_columns_cache

        columns = self._table_columns()
        exportable: Dict[str, str] = {}

        def _add(key: str, expr: str | None) -> None:
            if expr:
                exportable[key] = expr

        name_col = self._first_available(columns, "label", "name")
        _add("name", name_col)
        if "domain" in columns:
            _add("domain", "domain")
            _add("has_domain", "domain IS NOT NULL")
        if "website" in columns:
            _add("website", "website")
        if "quality_score" in columns:
            _add("quality_score", "quality_score")
        if "fg_id" in columns:
            _add("fg_id", "fg_id")
        if "wikidata_id" in columns:
            _add("wikidata_id", "wikidata_id")
        if "parent_org" in columns:
            _add("parent_org", "parent_org")
            _add("is_parent", "parent_org IS NULL")
            _add("is_subsidiary", "parent_org IS NOT NULL")

        _add("hq_country", self._country_value_expr())
        _add("hq_state", self._state_value_expr())
        _add("employees", self._employee_count_expr())
        _add("employee_band", self._employee_band_expr())
        _add("revenue", self._revenue_value_expr())
        _add("revenue_band", self._revenue_band_expr())
        _add("industry", self._industry_export_expr())
        _add("linkedin_url", self._linkedin_expr())

        if not exportable:
            exportable = dict(self.EXPORTABLE_COLUMNS_BASE)

        self._exportable_columns_cache = exportable
        return exportable

    def available_columns(self) -> List[str]:
        return list(self.exportable_columns().keys())

    def column_presets(self) -> List[Dict[str, Any]]:
        exportable = set(self.exportable_columns().keys())
        presets: List[Dict[str, Any]] = []
        for preset in self.COLUMN_PRESETS:
            cols = [c for c in preset.get("columns", []) if c in exportable]
            if not cols:
                continue
            presets.append({**preset, "columns": cols})
        if not presets and exportable:
            presets.append(
                {
                    "key": "all",
                    "label": "All",
                    "columns": list(exportable)[: self.MAX_COLUMNS],
                }
            )
        return presets

    @staticmethod
    def _coerce(value: Any) -> Any:
        """Normalize BigQuery values for JSON/CSV outputs."""
        if isinstance(value, (list, dict)):
            try:
                return json.dumps(value)
            except Exception:
                return str(value)
        if isinstance(value, (bytes, bytearray)):
            try:
                return value.decode("utf-8")
            except Exception:
                return str(value)
        return value

    def _normalize_columns(self, columns: Iterable[str], max_columns: int) -> List[str]:
        exportable = self.exportable_columns()
        cleaned: List[str] = []
        for col in columns:
            key = (col or "").strip()
            if not key or key not in exportable:
                continue
            if key not in cleaned:
                cleaned.append(key)
            if len(cleaned) >= max_columns:
                break
        if not cleaned:
            defaults = [c for c in self.DEFAULT_COLUMNS if c in exportable]
            cleaned = defaults if defaults else list(exportable.keys())
        return cleaned[: max_columns or self.MAX_COLUMNS]

    def _order_clause(self, sort_by: str | None, sort_dir: str | None) -> str:
        field = (sort_by or "quality_score").strip().lower()
        if field not in self.SORTABLE_FIELDS:
            field = "quality_score"
        sort_expr = self._sort_expression(field)
        if sort_dir:
            direction = "ASC" if sort_dir.lower() == "asc" else "DESC"
        else:
            direction = "ASC" if field == "name" else "DESC"
        return f"ORDER BY {sort_expr} {direction}"

    def _sort_expression(self, field: str) -> str:
        field = (field or "").strip().lower()
        if field == "name":
            return "label"
        if field == "employees":
            count_expr = self._employee_count_expr()
            return count_expr or "quality_score"
        if field == "revenue_band":
            value_expr = self._revenue_value_expr()
            return value_expr or "quality_score"
        if field == "hq_country":
            country_expr = self._country_expr()
            return country_expr or "quality_score"
        if field in self.SORTABLE_FIELDS:
            return field
        return "quality_score"

    def _build_filters(
        self, filters: Dict[str, Any]
    ) -> Tuple[List[str], List[Any]]:  # Returns bigquery.QueryParameter list
        conditions: List[str] = []
        params: List[Any] = []
        bq = _get_bigquery()

        def _as_list(value: Any) -> List[Any]:
            if value is None:
                return []
            if isinstance(value, (list, tuple, set)):
                return list(value)
            return [value]

        def _as_int(value: Any) -> int | None:
            if value is None or value == "":
                return None
            try:
                return int(value)
            except Exception:
                return None

        industries_raw = filters.get("industry")
        if industries_raw is None:
            industries_raw = filters.get("industries")
        industries = [i.strip().lower() for i in _as_list(industries_raw) if i]
        if industries:
            # Translate LinkedIn industries to Wikidata QIDs using the mapping
            wikidata_qids: List[str] = []
            for ind in industries:
                qids = self.LINKEDIN_TO_WIKIDATA.get(ind, [])
                wikidata_qids.extend(qids)
            # Deduplicate while preserving order
            seen: set[str] = set()
            unique_qids: List[str] = []
            for qid in wikidata_qids:
                if qid not in seen:
                    seen.add(qid)
                    unique_qids.append(qid)

            if unique_qids:
                industry_field = self._industry_field()
                if industry_field:
                    field_name = industry_field["name"]
                    field_type = industry_field["field_type"]
                    mode = industry_field["mode"]
                    # Match against industry QIDs ($.id field) instead of labels
                    if field_type == "JSON":
                        if mode == "REPEATED":
                            # Array of JSON objects: industries[].id
                            conditions.append(
                                f"""EXISTS (
                                    SELECT 1
                                    FROM UNNEST({field_name}) AS ind
                                    WHERE SAFE.JSON_VALUE(ind, '$.id') IN UNNEST(@industry_qids)
                                )"""
                            )
                        else:
                            # Single JSON array: JSON_QUERY_ARRAY(industries, '$')[].id
                            conditions.append(
                                f"""EXISTS (
                                    SELECT 1
                                    FROM UNNEST(SAFE.JSON_QUERY_ARRAY({field_name}, '$')) AS ind
                                    WHERE SAFE.JSON_VALUE(ind, '$.id') IN UNNEST(@industry_qids)
                                )"""
                            )
                        params.append(
                            bq.ArrayQueryParameter("industry_qids", "STRING", unique_qids)
                        )
                    elif field_name == "industry_ids" and mode == "REPEATED":
                        # Flat repeated string column of QIDs
                        conditions.append(
                            f"""EXISTS (
                                SELECT 1
                                FROM UNNEST({field_name}) AS qid
                                WHERE qid IN UNNEST(@industry_qids)
                            )"""
                        )
                        params.append(
                            bq.ArrayQueryParameter("industry_qids", "STRING", unique_qids)
                        )
                    else:
                        logger.warning(
                            "Industry filter: unsupported schema %s/%s in %s",
                            field_type, mode, self.TABLE,
                        )
                else:
                    # Fallback: check if industry_ids column exists (flat array of QIDs)
                    if self._has_column("industry_ids"):
                        conditions.append(
                            """EXISTS (
                                SELECT 1
                                FROM UNNEST(industry_ids) AS qid
                                WHERE qid IN UNNEST(@industry_qids)
                            )"""
                        )
                        params.append(
                            bq.ArrayQueryParameter("industry_qids", "STRING", unique_qids)
                        )
                    else:
                        logger.warning(
                            "Industry filter requested but no industry column found in %s",
                            self.TABLE,
                        )
            else:
                logger.warning(
                    "Industry filter: no Wikidata QIDs mapped for %s",
                    industries,
                )

        countries_input = [c.strip().lower() for c in _as_list(filters.get("country")) if c]
        if countries_input:
            # Translate country names to Wikidata QIDs using the mapping
            country_qids: List[str] = []
            for country in countries_input:
                qid = self.COUNTRY_TO_WIKIDATA.get(country)
                if qid and qid not in country_qids:
                    country_qids.append(qid)

            if country_qids:
                # Match against countries JSON array's $.id field
                columns = self._table_columns()
                if "countries" in columns:
                    conditions.append(
                        """EXISTS (
                            SELECT 1
                            FROM UNNEST(SAFE.JSON_QUERY_ARRAY(countries, '$')) AS c
                            WHERE SAFE.JSON_VALUE(c, '$.id') IN UNNEST(@country_qids)
                        )"""
                    )
                    params.append(
                        bq.ArrayQueryParameter("country_qids", "STRING", country_qids)
                    )
                else:
                    # Fallback to hq_country if countries array doesn't exist
                    country_expr = self._country_expr()
                    if country_expr:
                        conditions.append(f"{country_expr} IN UNNEST(@countries)")
                        params.append(
                            bq.ArrayQueryParameter("countries", "STRING", countries_input)
                        )
                    else:
                        logger.warning(
                            "Country filter requested but no country column found in %s",
                            self.TABLE,
                        )
            else:
                logger.warning(
                    "Country filter: no Wikidata QIDs mapped for %s",
                    countries_input,
                )

        states = [s.strip().lower() for s in _as_list(filters.get("state")) if s]
        if states:
            state_expr = self._state_expr()
            if state_expr:
                conditions.append(f"{state_expr} IN UNNEST(@states)")
                params.append(bq.ArrayQueryParameter("states", "STRING", states))
            else:
                logger.warning(
                    "State filter requested but no state column found in %s",
                    self.TABLE,
                )

        emp_min = _as_int(filters.get("employee_min"))
        emp_max = _as_int(filters.get("employee_max"))
        # Range filters take precedence over band filters to avoid conflicting constraints.
        if emp_min is None and emp_max is None:
            emp_bands = [b.strip().lower() for b in _as_list(filters.get("employee_band")) if b]
            if emp_bands:
                employee_band_expr = self._employee_band_expr()
                if employee_band_expr:
                    conditions.append(
                        f"LOWER({employee_band_expr}) IN UNNEST(@emp_bands)"
                    )
                    params.append(
                        bq.ArrayQueryParameter("emp_bands", "STRING", emp_bands)
                    )
                else:
                    logger.warning(
                        "Employee band filter requested but no employees column found in %s",
                        self.TABLE,
                    )
        if emp_min is not None or emp_max is not None:
            employee_count_expr = self._employee_count_expr()
            if employee_count_expr:
                if emp_min is not None:
                    conditions.append(f"{employee_count_expr} >= @employee_min")
                    params.append(
                        bq.ScalarQueryParameter("employee_min", "INT64", emp_min)
                    )
                if emp_max is not None:
                    conditions.append(f"{employee_count_expr} <= @employee_max")
                    params.append(
                        bq.ScalarQueryParameter("employee_max", "INT64", emp_max)
                    )
            else:
                logger.warning(
                    "Employee range filter requested but no employees column found in %s",
                    self.TABLE,
                )

        rev_min = _as_int(filters.get("revenue_min"))
        rev_max = _as_int(filters.get("revenue_max"))
        # Range filters take precedence over band filters to avoid conflicting constraints.
        if rev_min is None and rev_max is None:
            rev_bands = [r.strip().lower() for r in _as_list(filters.get("revenue_band")) if r]
            if rev_bands:
                revenue_band_expr = self._revenue_band_expr()
                if revenue_band_expr:
                    conditions.append(
                        f"LOWER({revenue_band_expr}) IN UNNEST(@revenue_bands)"
                    )
                    params.append(
                        bq.ArrayQueryParameter("revenue_bands", "STRING", rev_bands)
                    )
                else:
                    logger.warning(
                        "Revenue band filter requested but no revenue column found in %s",
                        self.TABLE,
                    )
        if rev_min is not None or rev_max is not None:
            revenue_value_expr = self._revenue_value_expr()
            if revenue_value_expr:
                if rev_min is not None:
                    conditions.append(f"{revenue_value_expr} >= @revenue_min")
                    params.append(
                        bq.ScalarQueryParameter("revenue_min", "INT64", rev_min)
                    )
                if rev_max is not None:
                    conditions.append(f"{revenue_value_expr} <= @revenue_max")
                    params.append(
                        bq.ScalarQueryParameter("revenue_max", "INT64", rev_max)
                    )
            else:
                logger.warning(
                    "Revenue range filter requested but no revenue column found in %s",
                    self.TABLE,
                )

        has_domain = filters.get("has_domain")
        if has_domain is True:
            if self._has_column("domain"):
                conditions.append("domain IS NOT NULL")
            else:
                logger.warning(
                    "Has-domain filter requested but domain column missing in %s",
                    self.TABLE,
                )
        elif has_domain is False:
            if self._has_column("domain"):
                conditions.append("domain IS NULL")
            else:
                logger.warning(
                    "Has-domain filter requested but domain column missing in %s",
                    self.TABLE,
                )

        is_parent = filters.get("is_parent")
        if is_parent is True:
            if self._has_column("parent_org"):
                conditions.append("parent_org IS NULL")
            else:
                logger.warning(
                    "Parent filter requested but parent_org column missing in %s",
                    self.TABLE,
                )
        elif is_parent is False:
            if self._has_column("parent_org"):
                conditions.append("parent_org IS NOT NULL")
            else:
                logger.warning(
                    "Parent filter requested but parent_org column missing in %s",
                    self.TABLE,
                )

        is_subsidiary = filters.get("is_subsidiary")
        if is_subsidiary is True:
            if self._has_column("parent_org"):
                conditions.append("parent_org IS NOT NULL")
            else:
                logger.warning(
                    "Subsidiary filter requested but parent_org column missing in %s",
                    self.TABLE,
                )
        elif is_subsidiary is False:
            if self._has_column("parent_org"):
                conditions.append("parent_org IS NULL")
            else:
                logger.warning(
                    "Subsidiary filter requested but parent_org column missing in %s",
                    self.TABLE,
                )

        return conditions, params

    def build_query(
        self,
        filters: Dict[str, Any],
        columns: Iterable[str] | None,
        limit: int,
        offset: int = 0,
        sort_by: str | None = None,
        sort_dir: str | None = None,
        include_extra_row: bool = False,
        max_columns: int | None = None,
    ) -> Tuple[str, List[Any], List[str]]:  # Returns bigquery.QueryParameter list
        selected_columns = self._normalize_columns(
            columns or self.DEFAULT_COLUMNS, max_columns or self.MAX_COLUMNS
        )
        exportable = self.exportable_columns()
        select_clause = ", ".join(
            f"{exportable[col]} AS {col}" for col in selected_columns
        )

        conditions, params = self._build_filters(filters or {})
        where_clause = f"WHERE {' AND '.join(conditions)}" if conditions else ""
        order_clause = self._order_clause(sort_by, sort_dir)

        query_limit = max(limit or 1, 1)
        if include_extra_row:
            query_limit += 1

        bq = _get_bigquery()
        params.append(bq.ScalarQueryParameter("limit", "INT64", query_limit))
        params.append(bq.ScalarQueryParameter("offset", "INT64", max(offset, 0)))

        query = f"""
        SELECT {select_clause}
        FROM `{self.TABLE}`
        {where_clause}
        {order_clause}
        LIMIT @limit OFFSET @offset
        """
        return query, params, selected_columns

    async def fetch_filter_options(self) -> Dict[str, List[str]]:
        """
        Retrieve distinct filter values with aggressive limits to avoid heavy scans.
        Cached by caller (Redis) for 24h to reduce BigQuery costs.
        """

        country_expr = self._country_expr()
        state_expr = self._state_expr()
        revenue_band_expr = self._revenue_band_expr()
        employee_band_expr = self._employee_band_expr()

        base_fields = [
            f"{country_expr} AS country" if country_expr else "NULL AS country",
            f"{state_expr} AS state" if state_expr else "NULL AS state",
            f"LOWER({revenue_band_expr}) AS revenue_band"
            if revenue_band_expr
            else "NULL AS revenue_band",
            f"LOWER({employee_band_expr}) AS employee_band"
            if employee_band_expr
            else "NULL AS employee_band",
        ]
        base_cte = f"""
        base AS (
            SELECT
                {", ".join(base_fields)}
            FROM `{self.TABLE}`
            WHERE label IS NOT NULL
        )
        """.strip()

        industry_field = self._industry_field()
        industry_cte = ""
        industries_select = "ARRAY<STRING>[] AS industries"
        if industry_field:
            field_name = industry_field["name"]
            field_type = industry_field["field_type"]
            mode = industry_field["mode"]
            subfields = industry_field["subfields"]
            if field_name == "industry" and field_type == "STRING" and mode != "REPEATED":
                industry_cte = f"""
                industry_values AS (
                    SELECT DISTINCT LOWER(industry) AS industry
                    FROM `{self.TABLE}` AS c
                    WHERE industry IS NOT NULL
                )
                """.strip()
                industries_select = """
                ARRAY(
                    SELECT industry FROM (
                        SELECT DISTINCT industry FROM industry_values
                        WHERE industry IS NOT NULL
                        ORDER BY industry
                        LIMIT 200
                    )
                ) AS industries
                """.strip()
            else:
                label_array_expr = self._industry_labels_array_expr(
                    f"c.{field_name}", field_type, mode, subfields
                ).strip()
                if label_array_expr == "ARRAY<STRING>[]":
                    logger.warning(
                        "Industry options requested but no usable industry schema found in %s",
                        self.TABLE,
                    )
                else:
                    industry_cte = f"""
                    industry_values AS (
                        SELECT DISTINCT LOWER(label) AS industry
                        FROM `{self.TABLE}` AS c,
                        UNNEST({label_array_expr}) AS label
                        WHERE label IS NOT NULL
                    )
                    """.strip()
                    industries_select = """
                    ARRAY(
                        SELECT industry FROM (
                            SELECT DISTINCT industry FROM industry_values
                            WHERE industry IS NOT NULL
                            ORDER BY industry
                            LIMIT 200
                        )
                    ) AS industries
                    """.strip()

        with_clauses = [base_cte]
        if industry_cte:
            with_clauses.append(industry_cte)
        with_sql = "WITH " + ",\n".join(with_clauses)

        select_fields = [
            """
            ARRAY(
                SELECT country FROM (
                    SELECT DISTINCT country FROM base
                    WHERE country IS NOT NULL
                    ORDER BY country
                    LIMIT 200
                )
            ) AS countries
            """.strip(),
            """
            ARRAY(
                SELECT state FROM (
                    SELECT DISTINCT state FROM base
                    WHERE state IS NOT NULL
                    ORDER BY state
                    LIMIT 200
                )
            ) AS states
            """.strip(),
            """
            ARRAY(
                SELECT revenue_band FROM (
                    SELECT DISTINCT revenue_band FROM base
                    WHERE revenue_band IS NOT NULL
                    ORDER BY revenue_band
                    LIMIT 50
                )
            ) AS revenue_bands
            """.strip(),
            """
            ARRAY(
                SELECT employee_band FROM (
                    SELECT DISTINCT employee_band FROM base
                    WHERE employee_band IS NOT NULL
                    ORDER BY employee_band
                )
            ) AS employee_bands
            """.strip(),
            industries_select,
        ]

        query = f"""
        {with_sql}
        SELECT
            {", ".join(select_fields)}
        """

        def _run():
            job = self.client.query(query)
            return list(job.result(max_results=1))[0]

        row = await asyncio.to_thread(_run)

        return {
            # All values returned lowercased for consistent client-side matching
            # Use curated lists instead of BQ data (Wikidata stores QIDs, not readable labels)
            "industries": list(self.CURATED_INDUSTRIES),
            "countries": list(self.CURATED_COUNTRIES),
            "states": list(row.get("states") or []),
            "revenue_bands": list(row.get("revenue_bands") or []),
            "employee_bands": list(row.get("employee_bands") or []),
        }

    @staticmethod
    def format_rows(rows: Iterable[Any], columns: List[str]) -> List[Dict[str, Any]]:
        formatted: List[Dict[str, Any]] = []
        for row in rows:
            item: Dict[str, Any] = {}
            for col in columns:
                try:
                    value = row[col]
                except Exception:
                    value = None
                item[col] = CompanyQueryBuilder._coerce(value)
            formatted.append(item)
        return formatted
