"""Retained inventory parents, item grid and common-filter geometry.

All numbers live in the external source rule. Coordinates use the existing
physical client CanvasContext once, without window chrome/DPI corrections.
Preparation does not establish current-screen ownership or authorize a sale.
"""
import hashlib
import json
import math

from .paths import resource_dir
from .ui_layout import LayoutError, Rect


def bag_detail_layouts():
    data = json.loads((resource_dir() / "ui-rules/bag-cleanup-geometry.json").read_bytes())
    return tuple(tuple(value) for value in data["detail"]["layouts"])


def _detail_flow(detail, roles, available):
    """Source FlowLayoutGroup: ordered wrapping, centered rows, bottom group."""
    lines = [[]]
    width = 0
    for role in roles:
        caption = detail["labels"][role]
        text_advance = sum(detail["space_advance"] if character == " " else detail["hangul_advance"]
                           for character in caption)
        button_width = max(detail["minimum_width"], detail["content_padding_x"]
            +detail["icon_width"]+(detail["content_gap"]+text_advance
            *detail["font_size"]/detail["units_per_em"]
                if caption else 0))
        if button_width > available:
            raise LayoutError("bag detail button does not fit the source parent")
        next_width = width+(detail["button_gap"] if lines[-1] else 0)+button_width
        if lines[-1] and next_width > available:
            lines.append([])
            width = 0
        width += (detail["button_gap"] if lines[-1] else 0)+button_width
        lines[-1].append((role,button_width))
    return lines


def bag_geometry(context, scene, *, ordered_roles=None):
    raw = (resource_dir() / "ui-rules/bag-cleanup-geometry.json").read_bytes()
    data = json.loads(raw)
    digest = hashlib.sha256(raw).hexdigest()
    if not isinstance(data.get("revision"), str):
        raise LayoutError("bag layout revision required")
    safe, scale = context.safe_rect, context.scale
    landscape = context.orientation == "landscape"
    panel = data["panel"]
    left = safe.right - (panel["right"] + panel["width"]) * scale if landscape else safe.center[0] - panel["width"] * scale / 2
    top = safe.top + panel["landscape_top" if landscape else "portrait_top"] * scale
    rows = {}

    def add(role, rect, *, enabled=True, crop=None, **metadata):
        crop = (rect if crop is None else crop).intersection(safe)
        visible = safe.contains(rect)
        rows[role] = dict(rect=rect, ocr_rect=crop, visible=visible,
            enabled=enabled and visible, metadata=dict(scene=scene,
                revision=data["revision"], rule_sha256=digest,
                source=data["provenance"], coordinate_space="physical-client",
                runtime_click_approved=False, **metadata))

    def local(role, x, y, width, height, **metadata):
        add(role, Rect(left+x*scale, top+y*scale, width*scale, height*scale), **metadata)

    if scene == "bag.cleanup":
        hud = data["hud"]
        size = hud["size"] * scale
        prefix = "landscape_" if landscape else "portrait_"
        add("bag.open", Rect(safe.right-hud[prefix+"right"]*scale-size,
            safe.bottom-hud[prefix+"bottom"]*scale-size, size, size))
        tabs = data["main_tabs"]
        add("bag.items", Rect(left+(panel["width"]-tabs["width"])*scale/2,
            safe.bottom-(tabs["bottom"]+tabs["height"])*scale,
            tabs["width"]*scale, tabs["height"]*scale))
        header = data["header"]
        local("bag.search", header["left"], header["top"]+(header["height"]-header["search_width"])/2,
            header["search_width"], header["search_width"])
        categories = data["categories"]
        widths = [max(categories["min_text_width"], len(label)*categories["hangul_advance"]
            *categories["font_size"]/categories["units_per_em"])+categories["padding"]
            +(categories["dropdown_extra"] if i in categories["dropdown_indexes"] else 0)
            for i, label in enumerate(categories["labels"])]
        index = categories["material_index"]
        start = header["left"]+header["search_width"]+header["gap"]
        from .source_bag_scroll import category_scroll_reset
        scroll_width = panel["width"]-2*header["left"]-header["search_width"]-header["sort_width"]-2*header["gap"]
        scroll_viewport = Rect(left+start*scale, top+header["top"]*scale,
            scroll_width*scale, header["height"]*scale)
        pointer, scroll_metadata = category_scroll_reset(scroll_viewport, widths,
            categories["gap"], scale, data["category_scroll"])
        add("bag.categories.reset", pointer, **scroll_metadata)
        material_left = start+sum(widths[:index])+index*categories["gap"]
        local("bag.materials", material_left, header["top"]+(header["height"]-categories["height"])/2,
            widths[index], categories["height"], category_id="Item_Ingredient",
            initial_category="Item_All", category_label_widths=widths)
        close = data["close"]
        add("bag.close", Rect(safe.right-(close["right"]+close["size"])*scale,
            safe.top+close["top"]*scale, close["size"]*scale, close["size"]*scale))
        # Header is observation only. Its actual localized title is not assumed.
        add("bag.header", Rect(safe.left, safe.top, safe.width, panel["landscape_top"]*scale), enabled=False)
        grid = data["grid"]
        viewport = Rect(left+grid["left"]*scale, top+grid["top"]*scale,
            (panel["width"]-grid["left"]-grid["right"])*scale,
            max(0, safe.bottom-grid["bottom"]*scale-(top+grid["top"]*scale)))
        add("bag.result.roi", viewport, enabled=False)
        stride_x = grid["cell_width"]+grid["gap_x"]
        stride_y = grid["cell_height"]+grid["gap_y"]
        available = panel["width"]-grid["left"]-grid["right"]-grid["padding_left"]-grid["padding_right"]
        columns = max(0, math.floor((available+grid["gap_x"])/stride_x))
        count_rows = max(0, math.floor((viewport.height/scale+grid["gap_y"])/stride_y))
        for row in range(count_rows):
            for column in range(columns):
                x = viewport.left+(grid["padding_left"]+column*stride_x)*scale
                y = viewport.top+row*stride_y*scale
                cell = Rect(x, y, grid["cell_width"]*scale, grid["cell_height"]*scale)
                label = Rect(x+grid["label_left"]*scale, y+grid["label_top"]*scale,
                    grid["label_width"]*scale, grid["label_height"]*scale)
                add(f"bag.result.cell.{row*columns+column}", cell, crop=label,
                    label_roi=context.viewport.rect_to_normalized(label), row=row, column=column,
                    item_name_only=True, source_item_prefab=data["provenance"]["inventory_item"])
        f = data["filter"]
        filter_left = safe.right-(f["right"]+f["width"])*scale if landscape else safe.center[0]-f["width"]*scale/2
        button_width = (f["buttons_width"]-f["button_gap"])/2
        buttons_left = filter_left+(f["width"]-f["buttons_width"])*scale/2
        y = safe.bottom-(f["bottom"]+f["button_height"])*scale
        for role, offset in (("bag.reset", 0), ("bag.apply", button_width+f["button_gap"])):
            add(role, Rect(buttons_left+offset*scale, y, button_width*scale, f["button_height"]*scale),
                search_focus_key=data["search_focus_key"], search_focus_evidence=data["search_focus_evidence"])
        if landscape:
            x = f["padding_x"]+f["flow_padding_x"]+f["search_padding_x"]+f["text_input_left"]
            y = f["top"]+f["padding_top"]+f["search_title_height"]+f["flow_gap"]+(f["search_height"]-f["text_input_height"])/2
            width = f["width"]-2*(f["padding_x"]+f["flow_padding_x"]+f["search_padding_x"])-f["text_input_left"]
            add("bag.name", Rect(filter_left+x*scale, safe.top+y*scale, width*scale, f["text_input_height"]*scale))
    elif scene == "bag.sale":
        sale = data["sale"]
        sale_left = safe.right-(sale["right"]+sale["panel_width"])*scale if landscape else safe.center[0]-sale["panel_width"]*scale/2
        sale_top = safe.bottom-sale["panel_height"]*scale
        content_width = sale["panel_width"]-2*sale["buttons_margin_x"]
        input_width = content_width-sale["spinner_width_delta"]-sale["input_width_delta"]
        input_y = sale["contents_top"]+sale["title_height"]+sale["content_gap"]+(sale["spinner_height"]-sale["input_height"])/2
        add("bag.quantity", Rect(sale_left+(sale["panel_width"]-input_width)*scale/2,
            sale_top+input_y*scale, input_width*scale, sale["input_height"]*scale),
            source_field="TextFieldSlot -604643723112722992; spinner/InputField -6087022360572682939")
        add("bag.sale.title", Rect(sale_left+sale["buttons_margin_x"]*scale,
            sale_top+sale["contents_top"]*scale, content_width*scale, sale["title_height"]*scale),
            enabled=False, string_table_id="UI_DetailCardUI_Popup_QuantitySelect_00001")
        width = (sale["panel_width"]-2*sale["buttons_margin_x"]-sale["button_gap"])/2
        add("bag.confirm", Rect(sale_left+(sale["buttons_margin_x"]+width+sale["button_gap"])*scale,
            safe.bottom-(sale["bottom"]+sale["button_height"])*scale,
            width*scale, sale["button_height"]*scale))
    elif scene == "bag.detail":
        detail = data["detail"]
        layouts = [tuple(value) for value in detail["layouts"]]
        delta = detail["landscape_width_delta" if landscape else "portrait_width_delta"]
        available = safe.width/scale-delta
        selected = tuple(ordered_roles or ())
        if selected not in layouts:
            raise LayoutError("bag detail layout has no retained source binding")
        lines = _detail_flow(detail,selected,available)
        for index, line in enumerate(lines):
            total = sum(width for _,width in line)+detail["button_gap"]*(len(line)-1)
            cursor = safe.center[0]-total*scale/2
            row_top = safe.bottom-(detail["padding_bottom"]+detail["button_height"]
                +(len(lines)-index-1)*(detail["button_height"]+detail["button_gap"]))*scale
            for role, width in line:
                rect = Rect(cursor,row_top,width*scale,detail["button_height"]*scale)
                name = "bag."+role.lower()
                label = detail["labels"][role]
                add(name,rect,expected_label=label,caption=label,icon_only=not bool(label),
                    ordered_roles=selected,button_type=role,row=index,row_count=len(lines),
                    source_label_key=detail["label_keys"].get(role),
                    source_prefab="9afc338ee9fbce4265352bb2",price_fields_visible=False)
                cursor += (width+detail["button_gap"])*scale
    elif scene == "bag.warning":
        warning = data["warning"]
        width = (warning["width"]-2*warning["padding_x"]-warning["button_gap"])/2
        add("bag.warning.confirm", Rect(safe.center[0]+warning["button_gap"]*scale/2,
            safe.bottom-(warning["bottom"]+warning["padding_bottom"]+warning["button_height"])*scale,
            width*scale, warning["button_height"]*scale), native_binding=warning["source"])
    else:
        raise LayoutError("unknown bag scene: " + scene)
    return rows
