"""Generative Design — Stage 3a (#1965): каталог типовых домов (house-type catalog).

Контракт Stage 3a: вместо max-FAR жадной раскладки (Stage 1b) пользователь выбирает
ТИПОВЫЕ дома и говорит, сколько секций каждого типа поставить. Этот модуль — справочник
таких типов: для каждого ``section_type`` он несёт габариты пятна секции (ширина × глубина,
метры), дефолтную этажность и подходящий класс жилья.

ИСТОЧНИК: это РАЗУМНЫЙ ДЕФОЛТНЫЙ каталог (sane-default), а не выгрузка из БД. Габариты —
типовые размеры секций массового жилья РФ (панель/монолит/башня/малоэтажка), округлённые
до реалистичных значений. Каталог намеренно захардкожен в коде на Stage 3a: миграции БД
сейчас нет (см. эпик #1953). Когда понадобится редактируемый застройщиком каталог — его
ПРОДВИГАЮТ в БД-таблицу с тем же контрактом (``section_type`` → footprint/floors/class),
а этот модуль станет seed'ом/фолбэком. До тех пор — single source of truth по типам.

Детерминированно, без LLM / внешних API / БД.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import Literal

logger = logging.getLogger(__name__)

# Класс жилья — то же Literal-множество, что у ConceptInput / compute_teap (single
# source of truth по допустимым значениям; рассинхрон тут = ошибка типов в mypy-strict).
HousingClass = Literal["econom", "comfort", "business"]


@dataclass(frozen=True)
class HouseType:
    """Один типовой дом каталога (тип секции МКД / малоэтажки).

    section_type   — стабильный машинный КЛЮЧ типа (латиница, snake_case); это и есть
        значение, которое фронт кладёт в ``BuildingProgramItem.section_type``.
    label_ru       — человекочитаемый русский лейбл для UI (Stage 3b его показывает).
    footprint_w_m  — ширина пятна секции, метры.
    footprint_d_m  — глубина пятна секции, метры.
    default_floors — дефолтная этажность типа (UI подставляет, пользователь может менять
        в пределах контракта BuildingProgramItem [1, 40]).
    housing_class  — подходящий класс жилья (драйвит ТЭП/финмодель ниже по конвейеру).
    """

    section_type: str
    label_ru: str
    footprint_w_m: float
    footprint_d_m: float
    default_floors: int
    housing_class: HousingClass

    @property
    def footprint_sqm(self) -> float:
        """Площадь пятна секции, кв.м (ширина × глубина)."""
        return self.footprint_w_m * self.footprint_d_m


# ── Каталог типовых домов (sane-default, см. модульный docstring про источник) ──────
# Покрываем основные форматы массового жилья РФ:
#   * панель-эконом  — длинная неглубокая секция, средняя этажность, эконом-класс;
#   * монолит-комфорт— чуть шире/глубже, комфорт-класс, типовая «свечка» 14 этажей;
#   * башня-бизнес   — компактное квадратное пятно, высотная (точечная) застройка;
#   * малоэтажка-комфорт — широкая невысокая секция (3 этажа), низкоплотная застройка;
#   * таунхаус       — узкое неглубокое пятно блокированной застройки, 3 этажа.
# Габариты — реалистичные типовые размеры; этажность — характерная для формата.
HOUSE_TYPES: tuple[HouseType, ...] = (
    HouseType(
        section_type="panel_econom",
        label_ru="Панельная секция (эконом)",
        footprint_w_m=24.0,
        footprint_d_m=15.0,
        default_floors=9,
        housing_class="econom",
    ),
    HouseType(
        section_type="monolith_comfort",
        label_ru="Монолитная секция (комфорт)",
        footprint_w_m=21.0,
        footprint_d_m=18.0,
        default_floors=14,
        housing_class="comfort",
    ),
    HouseType(
        section_type="tower_business",
        label_ru="Башня (бизнес)",
        footprint_w_m=18.0,
        footprint_d_m=18.0,
        default_floors=25,
        housing_class="business",
    ),
    HouseType(
        section_type="lowrise_comfort",
        label_ru="Малоэтажная секция (комфорт)",
        footprint_w_m=30.0,
        footprint_d_m=14.0,
        default_floors=3,
        housing_class="comfort",
    ),
    HouseType(
        section_type="townhouse",
        label_ru="Таунхаус",
        footprint_w_m=12.0,
        footprint_d_m=10.0,
        default_floors=3,
        housing_class="comfort",
    ),
)

# Индекс по ключу для O(1)-лукапа. Построен один раз при импорте; ключи уникальны
# (assert ниже ловит дубликат типа на старте, а не молча затирает запись).
_BY_KEY: dict[str, HouseType] = {ht.section_type: ht for ht in HOUSE_TYPES}
assert len(_BY_KEY) == len(HOUSE_TYPES), "duplicate section_type key in HOUSE_TYPES"


def get_house_type(section_type: str) -> HouseType:
    """Найти тип дома по ключу ``section_type``. Бросает :class:`KeyError`, если нет.

    Вызывающий слой (placement) обязан валидировать ключи программы заранее (см.
    :func:`available_section_types`) — неизвестный ключ здесь это программная ошибка,
    а не пользовательский ввод, поэтому KeyError, а не тихий None.
    """
    try:
        return _BY_KEY[section_type]
    except KeyError as exc:
        raise KeyError(
            f"unknown house type {section_type!r}; " f"available: {', '.join(sorted(_BY_KEY))}"
        ) from exc


def available_section_types() -> frozenset[str]:
    """Множество допустимых ключей ``section_type`` каталога (для валидации программы)."""
    return frozenset(_BY_KEY)


__all__ = [
    "HOUSE_TYPES",
    "HouseType",
    "HousingClass",
    "available_section_types",
    "get_house_type",
]
