"""Resolve tokenized slide layout recipes to EMU placeholder positions.

Each slide layout type is defined as a dict of named regions, where each
region has grid coordinates (col, row, col_span, row_span).  The resolver
converts these grid coordinates to EMU positions via SlideGrid.cell_rect().

The resolved output is consumed by both the supertheme (PPTX) and ODF
emitters.  Token values come from the ``layout.slide.regions`` token group
and can be overridden per DTCG layer.

Typical flow::

    tokens["layout"]["slide"]["regions"]   # from generator or layer override
              ↓
    resolve_layout_recipes(regions, grid)  # this module
              ↓
    dict[layout_type, dict[(ph_type, ph_idx), (x, y, cx, cy)]]
              ↓
    supertheme._apply_layout_recipe()      # patches PPTX XML
    odf.ImpressTemplateEmitter             # patches ODF XML
"""

from __future__ import annotations

from collections.abc import Iterator
from typing import Any

from tokenmoulds.design.page_geometry import SlideGrid


# ---------------------------------------------------------------------------
# Placeholder name → PPTX (ph_type, ph_idx) mapping
# ---------------------------------------------------------------------------

#: Maps token region names to PPTX ``<p:ph type="..." idx="..."/>`` values.
#: ph_type or ph_idx is None when the PPTX attribute is absent.
_INCIDENTAL_PH: dict[str, tuple[str | None, str | None]] = {
    "date": ("dt", "10"),
    "footer": ("ftr", "11"),
    "slide_num": ("sldNum", "12"),
}

PPTX_PLACEHOLDER_MAP: dict[str, dict[str, tuple[str | None, str | None]]] = {
    "title_slide": {
        "title": ("ctrTitle", None),
        "subtitle": ("subTitle", "1"),
        **_INCIDENTAL_PH,
    },
    "title_content": {
        "title": ("title", None),
        "body": (None, "1"),
        **_INCIDENTAL_PH,
    },
    "section_header": {
        "title": ("title", None),
        "body": ("body", "1"),
        **_INCIDENTAL_PH,
    },
    "two_content": {
        "title": ("title", None),
        "body_left": (None, "1"),
        "body_right": (None, "2"),
        **_INCIDENTAL_PH,
    },
    "comparison": {
        "title": ("title", None),
        "label_left": ("body", "1"),
        "content_left": (None, "2"),
        "label_right": ("body", "3"),
        "content_right": (None, "4"),
        **_INCIDENTAL_PH,
    },
    "title_only": {
        "title": ("title", None),
        **_INCIDENTAL_PH,
    },
    "blank": {
        **_INCIDENTAL_PH,
    },
    "content_caption": {
        "title": ("title", None),
        "content": (None, "1"),
        "caption": ("body", "2"),
        **_INCIDENTAL_PH,
    },
    "picture_caption": {
        "title": ("title", None),
        "picture": ("pic", "1"),
        "caption": ("body", "2"),
        **_INCIDENTAL_PH,
    },
    "vertical_text": {
        "title": ("title", None),
        "body": (None, "1"),
        **_INCIDENTAL_PH,
    },
    "vertical_title": {
        "title": ("title", None),
        "body": (None, "1"),
        **_INCIDENTAL_PH,
    },
}

#: Maps token layout type names to PPTX layout type attribute values.
LAYOUT_TYPE_TO_PPTX: dict[str, str] = {
    "title_slide": "title",
    "title_content": "obj",
    "section_header": "secHead",
    "two_content": "twoObj",
    "comparison": "twoTxTwoObj",
    "title_only": "titleOnly",
    "blank": "blank",
    "content_caption": "objTx",
    "picture_caption": "picTx",
    "vertical_text": "vertTx",
    "vertical_title": "vertTitleAndTx",
}

#: Reverse mapping: PPTX type → token layout type.
PPTX_TO_LAYOUT_TYPE: dict[str, str] = {v: k for k, v in LAYOUT_TYPE_TO_PPTX.items()}

#: Maps token region names to ODF ``presentation:object`` values.
#: Used by the Impress emitter to resolve placeholder classes.
ODF_PLACEHOLDER_MAP: dict[str, str] = {
    "title": "title",
    "subtitle": "subtitle",
    "body": "outline",
    "body_left": "outline",
    "body_right": "outline",
    "content": "object",
    "caption": "notes",
    "picture": "object",
    "label_left": "text",
    "label_right": "text",
    "content_left": "object",
    "content_right": "object",
}

#: Maps token layout type names to ODF presentation-page-layout names.
LAYOUT_TYPE_TO_ODF: dict[str, tuple[str, str]] = {
    "title_slide": ("AL1T", "Title Slide"),
    "title_content": ("AL2T", "Title, Content"),
    "section_header": ("AL3T", "Section Header"),
    "two_content": ("AL4T", "Two Content"),
    "comparison": ("AL5T", "Comparison"),
    "title_only": ("AL6T", "Title Only"),
    "blank": ("AL0T", "Blank"),
    "content_caption": ("AL7T", "Content with Caption"),
    "picture_caption": ("AL8T", "Picture with Caption"),
    "vertical_text": ("AL9T", "Vertical Text"),
    "vertical_title": ("AL10T", "Vertical Title and Text"),
}


def compute_default_slide_regions(
    grid_or_baseline: SlideGrid | float,
    *,
    brand_tone: float = 0.5,
) -> dict[str, Any]:
    """Compute grid-coordinate layout recipes for all 11 standard layouts.

    Args:
        grid_or_baseline: A ``SlideGrid`` to compute for, or a baseline EMU
            value (in which case a default 16:9 reference grid is created).
        brand_tone: Brand tone (0.0 = conservative, 0.5 = balanced,
            1.0 = expressive).  Modulates zone boundaries per the spec:
            title height, gutter, footer size, and title-slide positioning.

    Each region is a dict of ``{col, row, col_span, row_span}`` in grid
    units.  These are concrete values computed from the grid dimensions
    — layers can override any value.
    """
    if isinstance(grid_or_baseline, SlideGrid):
        grid = grid_or_baseline
    else:
        from tokenmoulds.design.page_geometry import create_slide_grid

        grid = create_slide_grid(9144000, 5143500, int(grid_or_baseline))

    t = max(0.0, min(1.0, brand_tone))
    C, R = grid.columns, grid.rows

    # --- Shared zone boundaries (brand-tone-modulated) ---
    # Conservative(0): min(7, R//4)  Balanced(0.5): min(6, R//4)  Expressive(1): min(5, R//4)
    title_rows = min(int(7 - 2 * t), R // 4)
    # Conservative(0): 2  Balanced(0.5): 1  Expressive(1): 1
    gutter = 2 if t < 0.25 else 1
    # Conservative(0): 2  Balanced(0.5): 2  Expressive(1): 1
    footer_rows = 1 if t > 0.75 else 2
    body_start = title_rows + gutter
    body_rows = R - body_start
    # Body excluding footer zone
    body_content_rows = body_rows - footer_rows - 1
    footer_start = R - footer_rows

    half_cols = C // 2
    two_thirds = C * 2 // 3
    one_third = C - two_thirds

    def _r(col: int, row: int, cs: int, rs: int) -> dict[str, int]:
        return {"col": col, "row": row, "col_span": cs, "row_span": rs}

    # Incidental placeholders (date / footer / slide number)
    third = C // 3
    incidentals = {
        "date": _r(0, footer_start, third, footer_rows),
        "footer": _r(third, footer_start, third, footer_rows),
        "slide_num": _r(2 * third, footer_start, C - 2 * third, footer_rows),
    }

    # --- Title Slide (brand-tone-modulated) ---
    # Conservative(0): centered R/2-span/2  Balanced(0.5): golden R*0.38  Expressive(1): upper R/4
    _ctr_frac = 0.5 - 0.25 * t  # 0.5 → 0.38 → 0.25
    ctr_start = max(2, int(R * _ctr_frac - R * (1 / 3 + t / 6) / 2))
    # Conservative(0): R*1/3  Balanced(0.5): R*2/5  Expressive(1): R*1/2
    ctr_span = max(4, int(R * (1 / 3 + t / 6)))
    sub_start = ctr_start + ctr_span + 1
    sub_span = max(4, R // 6)

    # --- Section Header (brand-tone-modulated) ---
    # Conservative(0): R/3  Balanced(0.5): R/4  Expressive(1): R/5
    _sec_frac = 1 / 3 - t / 15  # 0.333 → 0.267 → 0.200
    sec_start = max(2, int(R * _sec_frac))
    sec_span = R * 9 // 20
    desc_start = sec_start + sec_span
    desc_span = max(4, R // 5)

    # --- Comparison: labels + content ---
    label_rows = min(3, body_rows // 6)
    content_gap = 1
    content_start = body_start + label_rows + content_gap
    content_rows = body_rows - label_rows - content_gap

    return {
        "title_slide": {
            "title": _r(0, ctr_start, C, ctr_span),
            "subtitle": _r(0, sub_start, C, sub_span),
            **incidentals,
        },
        "title_content": {
            "title": _r(0, 0, C, title_rows),
            "body": _r(0, body_start, C, body_content_rows),
            **incidentals,
        },
        "section_header": {
            "title": _r(0, sec_start, C, sec_span),
            "body": _r(0, desc_start, C, desc_span),
            **incidentals,
        },
        "two_content": {
            "title": _r(0, 0, C, title_rows),
            "body_left": _r(0, body_start, half_cols, body_content_rows),
            "body_right": _r(half_cols, body_start, C - half_cols, body_content_rows),
            **incidentals,
        },
        "comparison": {
            "title": _r(0, 0, C, title_rows),
            "label_left": _r(0, body_start, half_cols, label_rows),
            "content_left": _r(0, content_start, half_cols, content_rows),
            "label_right": _r(half_cols, body_start, C - half_cols, label_rows),
            "content_right": _r(half_cols, content_start, C - half_cols, content_rows),
            **incidentals,
        },
        "title_only": {
            "title": _r(0, 0, C, title_rows),
            **incidentals,
        },
        "blank": {
            **incidentals,
        },
        "content_caption": {
            "title": _r(0, 0, C, title_rows),
            "content": _r(0, body_start, two_thirds, body_content_rows),
            "caption": _r(two_thirds, body_start, one_third, body_content_rows),
            **incidentals,
        },
        "picture_caption": {
            "title": _r(0, 0, C, title_rows),
            "picture": _r(0, body_start, two_thirds, body_content_rows),
            "caption": _r(two_thirds, body_start, one_third, body_content_rows),
            **incidentals,
        },
        "vertical_text": {
            "title": _r(0, 0, C, title_rows),
            "body": _r(0, body_start, C, body_content_rows),
            **incidentals,
        },
        "vertical_title": {
            "title": _r(C - one_third, 0, one_third, R - footer_rows - 1),
            "body": _r(0, 0, two_thirds, R - footer_rows - 1),
            **incidentals,
        },
    }


#: Incidental region names excluded from ODF layout resolution.
_INCIDENTAL_REGIONS: frozenset[str] = frozenset({"date", "footer", "slide_num"})


def _iter_content_regions(
    regions: dict[str, dict[str, Any]],
    *,
    include_incidentals: bool = True,
) -> Iterator[tuple[str, int, int, int, int]]:
    """Yield ``(region_name, col, row, col_span, row_span)`` for valid regions."""
    for name, data in regions.items():
        if name.startswith("$"):
            continue
        if not include_incidentals and name in _INCIDENTAL_REGIONS:
            continue
        if not isinstance(data, dict) or "col" not in data:
            continue
        yield (
            name,
            int(data["col"]),
            int(data["row"]),
            int(data["col_span"]),
            int(data["row_span"]),
        )


def resolve_layout_recipe(
    layout_type: str,
    regions: dict[str, dict[str, Any]],
    grid: SlideGrid,
) -> dict[tuple[str | None, str | None], tuple[int, int, int, int]]:
    """Resolve a single layout's region tokens to PPTX placeholder positions.

    Args:
        layout_type: Token layout type name (e.g. ``"title_slide"``).
        regions: Dict of region name → ``{col, row, col_span, row_span}``.
        grid: The slide grid for converting coordinates to EMU.

    Returns:
        Mapping of ``(ph_type, ph_idx)`` → ``(x, y, cx, cy)`` in EMU,
        suitable for patching PPTX slide layout XML.
    """
    ph_map = PPTX_PLACEHOLDER_MAP.get(layout_type, {})
    result: dict[tuple[str | None, str | None], tuple[int, int, int, int]] = {}

    for region_name, col, row, cs, rs in _iter_content_regions(regions):
        if region_name not in ph_map:
            continue
        result[ph_map[region_name]] = grid.cell_rect(col, row, cs, rs)

    return result


def resolve_all_layout_recipes(
    slide_regions: dict[str, Any],
    grid: SlideGrid,
) -> dict[str, dict[tuple[str | None, str | None], tuple[int, int, int, int]]]:
    """Resolve all layout recipes from the token tree.

    Args:
        slide_regions: The ``layout.slide.regions`` token subtree, keyed
            by layout type name.
        grid: The slide grid for the target aspect ratio.

    Returns:
        Mapping of token layout type → placeholder positions dict.
    """
    result: dict[
        str, dict[tuple[str | None, str | None], tuple[int, int, int, int]]
    ] = {}

    for layout_type in PPTX_PLACEHOLDER_MAP:
        regions = slide_regions.get(layout_type)
        if regions is None or not isinstance(regions, dict):
            continue
        recipe = resolve_layout_recipe(layout_type, regions, grid)
        if recipe:
            result[layout_type] = recipe

    return result


def resolve_for_pptx_layout_type(
    pptx_type: str,
    slide_regions: dict[str, Any],
    grid: SlideGrid,
) -> dict[tuple[str | None, str | None], tuple[int, int, int, int]]:
    """Resolve layout recipe for a PPTX layout type string.

    This is the drop-in replacement for ``_compute_layout_positions()``
    in supertheme.py.

    Args:
        pptx_type: PPTX layout type attribute (e.g. ``"title"``, ``"obj"``).
        slide_regions: The ``layout.slide.regions`` token subtree.
        grid: The slide grid for the target aspect ratio.

    Returns:
        Mapping of ``(ph_type, ph_idx)`` → ``(x, y, cx, cy)`` in EMU.
        Returns empty dict if the layout type has no recipe.
    """
    layout_type = PPTX_TO_LAYOUT_TYPE.get(pptx_type)
    if layout_type is None:
        return {}

    regions = slide_regions.get(layout_type)
    if regions is None or not isinstance(regions, dict):
        return {}

    return resolve_layout_recipe(layout_type, regions, grid)


def resolve_layout_by_name(
    layout_type: str,
    regions: dict[str, dict[str, Any]],
    grid: SlideGrid,
) -> dict[str, tuple[int, int, int, int]]:
    """Resolve a layout's regions to EMU positions keyed by region name.

    Unlike ``resolve_layout_recipe()`` which keys by PPTX (ph_type, ph_idx),
    this returns positions keyed by the token region name (e.g. ``"title"``,
    ``"body"``).  Used by ODF emitters and the IR design bridge.

    Incidental regions (date, footer, slide_num) are excluded since ODF
    handles these differently.
    """
    return {
        name: grid.cell_rect(col, row, cs, rs)
        for name, col, row, cs, rs in _iter_content_regions(
            regions, include_incidentals=False
        )
    }
