"""Measuring beaker / cylinder SVG generator for AGS Math instruments.

generate(capacity, reading, unit, size) -> str  (self-contained SVG)

  - Glass/cream body (rounded-bottom cylinder) with an ink outline.
  - Steel-blue graduation ticks up the right side with numeric labels;
    long majors, short minors.
  - Crimson liquid fill from the bottom up to the `reading` level, with a
    flat liquid surface.
  - D0ACAC rounded border frame; CSS-var style block.

  capacity : full scale of the beaker (top graduation).
  reading  : liquid level (0..capacity); clamped to range.
  unit     : "ml" or "l" (label only; affects tick number formatting a touch).
"""

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


def _fmt(v):
    if abs(v - round(v)) < 1e-9:
        return str(int(round(v)))
    return ('%g' % v)


def _nice_step(capacity):
    """Pick a graduation step giving roughly 5-10 majors."""
    targets = [1, 2, 2.5, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000]
    raw = capacity / 8.0
    for t in targets:
        if t >= raw:
            return t
    return capacity / 8.0


def draw_beaker(parts, bx, bw, top, bottom, capacity, reading, unit,
                clip_id="beakerClip", label=None, label_y=None):
    """Draw ONE beaker (cream body, steel graduations, crimson fill, unit
    label) into `parts` within the column [bx, bx+bw] / [top, bottom].

    Each call defines its own clipPath (`clip_id` must be unique per beaker
    in a shared SVG). Optionally draws `label` centred at `label_y`.
    Returns nothing; appends SVG fragments to `parts`.
    """
    capacity = float(capacity)
    reading = max(0.0, min(capacity, float(reading)))
    frac = 0.0 if capacity <= 0 else reading / capacity
    body_h = bottom - top
    rx_b = bw * 0.18

    body_path = (
        f'M {bx:.2f} {top:.2f} '
        f'L {bx:.2f} {bottom-rx_b:.2f} '
        f'Q {bx:.2f} {bottom:.2f} {bx+rx_b:.2f} {bottom:.2f} '
        f'L {bx+bw-rx_b:.2f} {bottom:.2f} '
        f'Q {bx+bw:.2f} {bottom:.2f} {bx+bw:.2f} {bottom-rx_b:.2f} '
        f'L {bx+bw:.2f} {top:.2f}'
    )

    # Clip path for the liquid (rounded bottom corners)
    parts.append(
        '<defs>'
        f'<clipPath id="{clip_id}">'
        f'<path d="{body_path} Z"/>'
        f'</clipPath>'
        '</defs>'
    )

    # Glass/cream body fill
    parts.append(f'<path d="{body_path}" fill="var(--cream)" stroke="none"/>')

    # Crimson liquid (clipped to the body shape)
    liquid_top = bottom - frac * body_h
    parts.append(
        f'<rect x="{bx:.2f}" y="{liquid_top:.2f}" width="{bw:.2f}" '
        f'height="{bottom-liquid_top:.2f}" fill="var(--crimson)" '
        f'clip-path="url(#{clip_id})"/>'
    )
    # Liquid surface line
    parts.append(
        f'<line x1="{bx:.2f}" y1="{liquid_top:.2f}" x2="{bx+bw:.2f}" '
        f'y2="{liquid_top:.2f}" stroke="var(--ink)" stroke-width="1.5" '
        f'clip-path="url(#{clip_id})"/>'
    )

    # Ink outline of the body
    parts.append(
        f'<path d="{body_path}" fill="none" stroke="var(--ink)" '
        f'stroke-width="3" stroke-linejoin="round"/>'
    )
    # Rim lip (slight flare)
    parts.append(
        f'<line x1="{bx-6:.2f}" y1="{top:.2f}" x2="{bx+bw+6:.2f}" y2="{top:.2f}" '
        f'stroke="var(--ink)" stroke-width="3" stroke-linecap="round"/>'
    )

    # Steel-blue graduation ticks up the right side, labels to the right
    step = _nice_step(capacity)
    n = int(round(capacity / step)) if step else 0
    tick_x = bx + bw
    for i in range(n + 1):
        val = i * step
        y = bottom - (val / capacity) * body_h if capacity else bottom
        is_major = (i % 2 == 0) or n <= 6
        tlen = 14 if is_major else 8
        parts.append(
            f'<line x1="{tick_x-tlen:.2f}" y1="{y:.2f}" x2="{tick_x:.2f}" '
            f'y2="{y:.2f}" stroke="var(--steel)" stroke-width="2"/>'
        )
        if is_major:
            parts.append(
                f'<text x="{tick_x+6:.2f}" y="{y:.2f}" font-family="sans-serif" '
                f'font-size="12" fill="var(--steel)" text-anchor="start" '
                f'dominant-baseline="central">{_fmt(val)}</text>'
            )

    # Unit label below the beaker
    if label_y is None:
        label_y = bottom + 22
    parts.append(
        f'<text x="{bx+bw/2:.2f}" y="{label_y:.2f}" font-family="sans-serif" '
        f'font-size="16" font-weight="bold" fill="var(--ink)" '
        f'text-anchor="middle">{unit}</text>'
    )

    # Optional caption label beneath the unit
    if label is not None and label != "":
        parts.append(
            f'<text x="{bx+bw/2:.2f}" y="{label_y+20:.2f}" font-family="sans-serif" '
            f'font-size="15" font-weight="bold" fill="var(--ink)" '
            f'text-anchor="middle">{label}</text>'
        )


def generate(capacity=500, reading=300, unit="ml", size=320):
    width = size
    height = size
    bx = width * 0.30          # left edge of body
    bw = width * 0.40          # body width
    top = height * 0.16        # top of graduated region
    bottom = height * 0.86     # inside bottom of body

    parts = []
    parts.append(
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" '
        f'viewBox="0 0 {width} {height}">'
    )
    parts.append(STYLE)

    # Border frame
    parts.append(
        f'<rect x="3" y="3" width="{width-6}" height="{height-6}" rx="18" ry="18" '
        f'fill="none" stroke="var(--border)" stroke-width="4"/>'
    )

    draw_beaker(parts, bx, bw, top, bottom, capacity, reading, unit,
                clip_id="beakerClip", label_y=height * 0.95)

    parts.append('</svg>')
    return '\n'.join(parts)


if __name__ == '__main__':
    import os
    out = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'samples'))
    os.makedirs(out, exist_ok=True)
    samples = [
        dict(capacity=500, reading=300, unit="ml"),
        dict(capacity=1, reading=0.75, unit="l"),
        dict(capacity=250, reading=125, unit="ml"),
    ]
    for i, kw in enumerate(samples, 1):
        p = os.path.join(out, f'beaker_{i}.svg')
        with open(p, 'w', encoding='utf-8') as f:
            f.write(generate(**kw))
        print('wrote', p)
