# -*- coding: utf-8 -*-
"""Predict how Kodi wraps text in Estuary fonts, to trim text to whole lines.

Kodi's textbox/label never ellipsize multi-line text and overflow their
height, so text that must fit N lines is trimmed here. Widths come from PIL
with the Estuary font file, scaled by factors calibrated against real Kodi
renders (scripts/dialog_harness, 2026-09-29): Kodi regular text measured
0.91-0.97x of PIL, bold ("*_title", synthetic emboldening) 1.07-1.11x. The
factors below are the conservative end, so predicted lines are never shorter
than Kodi's — trimmed text always fits.
"""

import xbmc
import xbmcvfs

_FONT_FILE = 'special://xbmc/addons/skin.estuary/fonts/NotoSans-Regular.ttf'

# Estuary font name -> (size, width factor vs PIL, first line height, next line height) in 1080p skin px
FONTS = {
    'font27': (27, 0.975, 40, 36.7),
    'font32': (32, 0.975, 47, 43.4),
    'font45_title': (45, 1.12, 66, 61.7),
}
_SAFETY_PX = 4
_cache = {}


def _pil_font(name):
    if name not in _cache:
        try:
            from PIL import ImageFont
            _cache[name] = ImageFont.truetype(xbmcvfs.translatePath(_FONT_FILE), FONTS[name][0])
        except Exception as exc:  # no PIL/FreeType or no Estuary: fall back to an average width
            xbmc.log('[plugin.video.milionar] text_fit: PIL font unavailable ({})'.format(exc), xbmc.LOGWARNING)
            _cache[name] = None
    return _cache[name]


def text_width(text, font_name):
    size, factor = FONTS[font_name][0], FONTS[font_name][1]
    font = _pil_font(font_name)
    return (_pil_width(font, text) if font else len(text) * size * 0.56) * factor


def _pil_width(font, text):
    """Pixel width of text: getlength (Pillow 8+), getsize (older Pillow bundled with Kodi on Windows)."""
    if hasattr(font, 'getlength'):
        return font.getlength(text)
    return font.getsize(text)[0]


def wrap(text, font_name, width):
    """Lines as Kodi would break them (word wrap, over-long words split by characters)."""
    limit = width - _SAFETY_PX
    lines = []
    for paragraph in (text or '').replace('[CR]', '\n').split('\n'):
        line = ''
        for word in paragraph.split():
            candidate = word if not line else line + ' ' + word
            if text_width(candidate, font_name) <= limit:
                line = candidate
                continue
            if line:
                lines.append(line)
            while text_width(word, font_name) > limit:  # split a word longer than the line
                cut = len(word)
                while cut > 1 and text_width(word[:cut], font_name) > limit:
                    cut -= 1
                lines.append(word[:cut])
                word = word[cut:]
            line = word
        lines.append(line)
    return lines


def line_count(text, font_name, width):
    return len(wrap(text, font_name, width)) if (text or '').strip() else 0


def block_height(lines, font_name):
    """Height of an auto-height textbox with `lines` lines (skin px, rounded up)."""
    if lines <= 0:
        return 0
    first, step = FONTS[font_name][2], FONTS[font_name][3]
    return int(first + step * (lines - 1) + 0.999)


def lines_for_height(height, font_name):
    first, step = FONTS[font_name][2], FONTS[font_name][3]
    if height < first:
        return 0
    return 1 + int((height - first) // step)


def fit(text, font_name, width, max_lines, ellipsis='…'):
    """Trim text at a word boundary so it wraps to at most max_lines, ending with ellipsis."""
    text = ' '.join((text or '').split())
    if max_lines <= 0:
        return ''
    lines = wrap(text, font_name, width)
    if len(lines) <= max_lines:
        return text
    kept = lines[:max_lines]
    last = kept[-1]
    limit = width - _SAFETY_PX
    while last and text_width(last.rstrip(' .,;:–-') + ellipsis, font_name) > limit:
        last = last.rsplit(' ', 1)[0] if ' ' in last else last[:-1]
    kept[-1] = last.rstrip(' .,;:–-') + ellipsis
    return ' '.join(kept)
