"""Singapore Math "model method" bar-model SVG generator.

Four model types, all in AGS brand style (CSS vars, dusty-rose border).
The public functions return a self-contained SVG string; the matching
`_*_content` helpers return just the inner XML + bounding box so that
`solution_block.py` can stack several models inside one outer frame.

Types:
  1. part_whole(entities)       — one entity's bar split into labelled parts
  2. comparison(entities)       — multiple bars drawn to relative scale
  3. multiplication(entities)   — bar split into N equal repeated units
  4. unitary(entities, total,   — N equal blank unit-boxes + optional
              unit_value)          known "extra" block + optional total bracket

Entity shape for types 1/2/3:
  {"label": "Sarah",
   "parts": [
     {"value": 8,  "known": True},
     {"value": 10, "known": True, "label": "?"}   # optional override
   ]}

Entity shape for type 4:
  {"label": "Yellow puppets",
   "units": 5,           # number of blank unit-boxes
   "extra": 1,           # number of extra "known" blocks
   "extra_value": 7}     # value shown inside the extra block (per extra)
"""

STYLE = (
    "<style>svg{--crimson:#960000;--gold:#BF9237;--lime:#88C808;"
    "--steel:#4A6488;--cream:#FAF5EE;--ink:#2A2A2A;--border:#D0ACAC;}</style>"
)

# Layout constants (shared)
BH = 40            # bar height
GAP = 16           # vertical gap between entity rows
LBL = 140          # left-label gutter
PAD = 24           # outer padding
MIN_SEG = 40       # minimum segment width for readability
LBL_FS = 16
SEG_FS = 15

# Per-entity colour cycle: (fill, text-on-fill).
# Crimson + cream, then gold + ink, alternating per entity row.
_ENTITY_COLORS = [("var(--crimson)", "var(--cream)"),
                  ("var(--gold)", "var(--ink)")]
_UNKNOWN_FILL = "var(--border)"
_UNKNOWN_TEXT = "var(--ink)"
_EXTRA_FILL = "var(--cream)"
_EXTRA_TEXT = "var(--ink)"


def _svg(inner, w, h, *, border=True):
    parts = [
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{w:.0f}" '
        f'height="{h:.0f}" viewBox="0 0 {w:.0f} {h:.0f}">',
        STYLE,
    ]
    if border:
        parts.append(
            f'<rect x="3" y="3" width="{w-6:.0f}" height="{h-6:.0f}" '
            f'rx="10" fill="none" stroke="var(--border)" stroke-width="3"/>'
        )
    parts.append(inner)
    parts.append('</svg>')
    return '\n'.join(parts)


# ---------- parts-based renderer (types 1, 2, 3) ----------

def _parts_content(entities, *, target_width=860, x0=0, y0=0):
    """Render parts-based entities. Returns (xml, W, H)."""
    if not entities:
        return ('', target_width, PAD * 2)
    max_total = max(sum(p['value'] for p in e['parts']) for e in entities) or 1
    avail = target_width - LBL - PAD * 2
    scale = avail / max_total
    # per-segment widths with MIN_SEG floor
    seg_w = [[max(p['value'] * scale, MIN_SEG) for p in e['parts']]
             for e in entities]
    bar_w = [sum(sw) for sw in seg_w]
    max_bar = max(bar_w)
    W = LBL + max_bar + PAD * 2
    n = len(entities)
    H = PAD * 2 + n * BH + (n - 1) * GAP

    out = []
    y = y0 + PAD
    for i, e in enumerate(entities):
        fill, txt = _ENTITY_COLORS[i % len(_ENTITY_COLORS)]
        out.append(
            f'<text x="{x0 + PAD}" y="{y + BH/2:.1f}" '
            f'font-family="sans-serif" font-size="{LBL_FS}" '
            f'font-weight="bold" fill="var(--ink)" '
            f'dominant-baseline="central">{e.get("label","")}</text>'
        )
        x = x0 + LBL
        for j, p in enumerate(e['parts']):
            sw = seg_w[i][j]
            known = bool(p.get('known', False))
            f_color = fill if known else _UNKNOWN_FILL
            t_color = txt if known else _UNKNOWN_TEXT
            if 'label' in p:
                label = str(p['label'])
            elif known:
                label = str(p.get('value', ''))
            else:
                label = '?'
            out.append(
                f'<rect x="{x:.1f}" y="{y}" width="{sw:.1f}" height="{BH}" '
                f'fill="{f_color}" stroke="var(--ink)" stroke-width="2"/>'
            )
            out.append(
                f'<text x="{x + sw/2:.1f}" y="{y + BH/2:.1f}" '
                f'font-family="sans-serif" font-size="{SEG_FS}" '
                f'font-weight="bold" fill="{t_color}" '
                f'text-anchor="middle" dominant-baseline="central">{label}</text>'
            )
            x += sw
        y += BH + GAP
    return ('\n'.join(out), W, H)


def part_whole(entities, target_width=860):
    xml, W, H = _parts_content(entities, target_width=target_width)
    return _svg(xml, W, H)


def comparison(entities, target_width=860):
    # same renderer — comparison is just two-or-more parts-bars drawn to scale.
    xml, W, H = _parts_content(entities, target_width=target_width)
    return _svg(xml, W, H)


def multiplication(entities, target_width=860):
    # same renderer — multiplication is one bar with N equal repeated parts.
    xml, W, H = _parts_content(entities, target_width=target_width)
    return _svg(xml, W, H)


# ---------- unitary renderer (type 4) ----------

def _unitary_content(entities, *, total=None, unit_value=None,
                     target_width=860, x0=0, y0=0):
    if not entities:
        return ('', target_width, PAD * 2)
    BRACE_W = 110  # right-side gutter reserved for the total bracket
    has_total = total is not None
    max_units = max(e.get('units', 0) + e.get('extra', 0) for e in entities) or 1
    avail = target_width - LBL - PAD * 2 - (BRACE_W if has_total else 0)
    unit_w = max(MIN_SEG, avail / max_units)
    max_bar = max((e.get('units', 0) + e.get('extra', 0)) * unit_w
                  for e in entities)
    W = LBL + max_bar + PAD * 2 + (BRACE_W if has_total else 0)
    n = len(entities)
    H = PAD * 2 + n * BH + (n - 1) * GAP

    out = []
    y = y0 + PAD
    first_top = y
    for i, e in enumerate(entities):
        out.append(
            f'<text x="{x0 + PAD}" y="{y + BH/2:.1f}" '
            f'font-family="sans-serif" font-size="{LBL_FS}" '
            f'font-weight="bold" fill="var(--ink)" '
            f'dominant-baseline="central">{e.get("label","")}</text>'
        )
        x = x0 + LBL
        units = int(e.get('units', 0))
        for _ in range(units):
            label = str(unit_value) if unit_value is not None else '?'
            out.append(
                f'<rect x="{x:.1f}" y="{y}" width="{unit_w:.1f}" height="{BH}" '
                f'fill="{_UNKNOWN_FILL}" stroke="var(--ink)" stroke-width="2"/>'
            )
            out.append(
                f'<text x="{x + unit_w/2:.1f}" y="{y + BH/2:.1f}" '
                f'font-family="sans-serif" font-size="{SEG_FS}" '
                f'font-weight="bold" fill="{_UNKNOWN_TEXT}" '
                f'text-anchor="middle" dominant-baseline="central">{label}</text>'
            )
            x += unit_w
        extra = int(e.get('extra', 0))
        if extra > 0:
            ew = unit_w * extra
            ev = e.get('extra_value')
            etxt = str(ev) if ev is not None else ''
            out.append(
                f'<rect x="{x:.1f}" y="{y}" width="{ew:.1f}" height="{BH}" '
                f'fill="{_EXTRA_FILL}" stroke="var(--ink)" stroke-width="2"/>'
            )
            out.append(
                f'<text x="{x + ew/2:.1f}" y="{y + BH/2:.1f}" '
                f'font-family="sans-serif" font-size="{SEG_FS}" '
                f'font-weight="bold" fill="{_EXTRA_TEXT}" '
                f'text-anchor="middle" dominant-baseline="central">{etxt}</text>'
            )
            x += ew
        y += BH + GAP
    last_bot = y - GAP
    if has_total:
        bx = x0 + LBL + max_bar + 14
        mid = (first_top + last_bot) / 2
        out.append(
            f'<line x1="{bx}" y1="{first_top}" x2="{bx}" y2="{last_bot}" '
            f'stroke="var(--ink)" stroke-width="2"/>'
        )
        out.append(
            f'<line x1="{bx}" y1="{first_top}" x2="{bx-7}" y2="{first_top}" '
            f'stroke="var(--ink)" stroke-width="2"/>'
        )
        out.append(
            f'<line x1="{bx}" y1="{last_bot}" x2="{bx-7}" y2="{last_bot}" '
            f'stroke="var(--ink)" stroke-width="2"/>'
        )
        out.append(
            f'<line x1="{bx}" y1="{mid:.1f}" x2="{bx+8}" y2="{mid:.1f}" '
            f'stroke="var(--ink)" stroke-width="2"/>'
        )
        out.append(
            f'<text x="{bx + 14}" y="{mid:.1f}" font-family="sans-serif" '
            f'font-size="{LBL_FS}" font-weight="bold" fill="var(--crimson)" '
            f'dominant-baseline="central">{total}</text>'
        )
    return ('\n'.join(out), W, H)


def unitary(entities, total=None, unit_value=None, target_width=860):
    xml, W, H = _unitary_content(entities, total=total,
                                 unit_value=unit_value,
                                 target_width=target_width)
    return _svg(xml, W, H)


if __name__ == '__main__':
    import os
    out = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'samples'))
    os.makedirs(out, exist_ok=True)

    # 1. PART-WHOLE — "Sarah has 8 teddy bears. Jenny has 10 more."
    pw = part_whole([
        {"label": "Sarah",
         "parts": [{"value": 8, "known": True}]},
        {"label": "Jenny",
         "parts": [{"value": 8, "known": True},
                   {"value": 10, "known": True}]},
    ])
    with open(os.path.join(out, 'bar_model_part_whole.svg'), 'w',
              encoding='utf-8') as f:
        f.write(pw)
    print('wrote bar_model_part_whole.svg')

    # 3. MULTIPLICATION — "Jimmy has 9. David has 4 times as many."
    mu = multiplication([
        {"label": "Jimmy",
         "parts": [{"value": 9, "known": True}]},
        {"label": "David",
         "parts": [{"value": 9, "known": True}] * 4},
    ])
    with open(os.path.join(out, 'bar_model_multiplication.svg'), 'w',
              encoding='utf-8') as f:
        f.write(mu)
    print('wrote bar_model_multiplication.svg')

    # 2. COMPARISON — invented: "Ali has 12 marbles. Ahmad has 5 more."
    cmp = comparison([
        {"label": "Ali",
         "parts": [{"value": 12, "known": True}]},
        {"label": "Ahmad",
         "parts": [{"value": 12, "known": True},
                   {"value": 5, "known": True}]},
    ])
    with open(os.path.join(out, 'bar_model_comparison.svg'), 'w',
              encoding='utf-8') as f:
        f.write(cmp)
    print('wrote bar_model_comparison.svg')

    # 4. UNITARY — "Purple (6 units) and Yellow (5 units + 7) total 39."
    un_setup = unitary([
        {"label": "Purple puppets", "units": 6, "extra": 0},
        {"label": "Yellow puppets", "units": 5, "extra": 1, "extra_value": 7},
    ], total=39, unit_value=None)
    with open(os.path.join(out, 'bar_model_unitary_setup.svg'), 'w',
              encoding='utf-8') as f:
        f.write(un_setup)
    print('wrote bar_model_unitary_setup.svg')

    un_solved = unitary([
        {"label": "Purple puppets", "units": 6, "extra": 0},
        {"label": "Yellow puppets", "units": 5, "extra": 1, "extra_value": 7},
    ], total=39, unit_value=4)
    with open(os.path.join(out, 'bar_model_unitary_solved.svg'), 'w',
              encoding='utf-8') as f:
        f.write(un_solved)
    print('wrote bar_model_unitary_solved.svg')
