# SPDX-License-Identifier: GPL-2.0-only
#
# llamachat - a small native chat client for a local llama.cpp router
# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
"""Dialog for entering what presets.ini cannot answer about a model."""

import math

from PySide6.QtWidgets import (
    QCheckBox,
    QDialog,
    QDialogButtonBox,
    QFormLayout,
    QLabel,
    QLineEdit,
    QVBoxLayout,
)

from .models import ModelInfo


def _number(text: str, cast):
    """Field text to a number, treating blank and garbage alike as unknown.

    This is the third writer of a price, after providers._number and
    models._get, and it needs their non-finite guard for the same reason:
    "nan" and "inf" survive float() and would be stored, then rendered as a
    "$nan" cost. is_priced() screens them downstream, so the readout stays
    honest either way, but a stored nan is a value the dialog would show
    back to the user on reopen.
    """
    text = (text or "").strip()
    if not text:
        return None
    try:
        value = cast(text)
    except ValueError:
        return None
    if cast is float and not math.isfinite(value):
        return None
    return value


def to_info(ctx_text, vision, in_text, out_text, vision_prefill=None):
    """Build a ModelInfo from the dialog's raw field values.

    Vision is the one tri-state field, and a binary checkbox cannot hold
    three states on its own. The checkbox carries the user's intent (checked
    means "yes"), and vision_prefill carries what was known before the
    dialog opened: an unchecked box over an unknown prefill stays unknown
    rather than writing False, which would shadow a provider-level
    vision=True through models.resolve's pick(). An unchecked box over a
    known prefill, True or False, is a real "no".
    """
    if vision:
        resolved_vision = True
    elif vision_prefill is None:
        resolved_vision = None
    else:
        resolved_vision = False
    return ModelInfo(
        ctx_size=_number(ctx_text, int),
        vision=resolved_vision,
        price_in=_number(in_text, float),
        price_out=_number(out_text, float),
    )


def to_fields(info: ModelInfo) -> tuple[str, bool, str, str]:
    """The inverse, for prefilling. Unknown becomes an empty field."""
    return (
        "" if info.ctx_size is None else str(info.ctx_size),
        bool(info.vision),
        "" if info.price_in is None else str(info.price_in),
        "" if info.price_out is None else str(info.price_out),
    )


class ModelDialog(QDialog):
    """Context size, vision and prices for one model.

    Prefilled from the provider's defaults, so the common case is checking
    the numbers rather than typing them.
    """

    def __init__(self, model_id: str, info: ModelInfo, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Model settings")
        self.model_id = model_id
        self._vision_prefill = info.vision

        ctx, vision, price_in, price_out = to_fields(info)
        self.ctx = QLineEdit(ctx)
        self.ctx.setPlaceholderText("unknown")
        self.vision = QCheckBox("Accepts images")
        self.vision.setChecked(vision)
        self.price_in = QLineEdit(price_in)
        self.price_in.setPlaceholderText("unpriced")
        self.price_out = QLineEdit(price_out)
        self.price_out.setPlaceholderText("unpriced")

        layout = QVBoxLayout(self)
        heading = QLabel(f"<b>{model_id}</b>")
        heading.setTextInteractionFlags(heading.textInteractionFlags())
        layout.addWidget(heading)

        form = QFormLayout()
        form.addRow("Context size (tokens)", self.ctx)
        form.addRow("", self.vision)
        form.addRow("Input price (per 1M tokens)", self.price_in)
        form.addRow("Output price (per 1M tokens)", self.price_out)
        layout.addLayout(form)

        note = QLabel(
            "Leave prices empty if you do not want a cost estimate.\n"
            "Context size drives the meter and the attachment budget."
        )
        note.setWordWrap(True)
        layout.addWidget(note)

        buttons = QDialogButtonBox(
            QDialogButtonBox.Save | QDialogButtonBox.Cancel
        )
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)

    def info(self) -> ModelInfo:
        """What the user entered."""
        return to_info(
            self.ctx.text(),
            self.vision.isChecked(),
            self.price_in.text(),
            self.price_out.text(),
            vision_prefill=self._vision_prefill,
        )