"""Crop position selection for poster format (1:1 square crop)."""

import base64
import json
import sys
import time
from typing import Tuple

CROP_POSITIONS = ["left", "center-left", "center", "center-right", "right"]

MAX_RETRIES = 2
RETRY_BASE_DELAY = 2  # seconds


def apply_crop_position(
    image_width: int,
    image_height: int,
    position: str,
) -> Tuple[int, int, int, int]:
    """
    Calculate the bounding box for a 1:1 square crop of the given image.

    The crop height equals *image_height* (full height), giving a square of
    side *image_height*.  The horizontal position within the frame is
    controlled by *position*.

    Parameters
    ----------
    image_width:
        Width of the source image in pixels.
    image_height:
        Height of the source image in pixels.
    position:
        One of ``left``, ``center-left``, ``center``, ``center-right``,
        ``right``.

    Returns
    -------
    (left, upper, right, lower) bounding box suitable for ``PIL.Image.crop()``.

    Raises
    ------
    ValueError
        If *position* is not one of the five allowed values.
    """
    if position not in CROP_POSITIONS:
        raise ValueError(
            f"Invalid crop position '{position}'. "
            f"Must be one of: {', '.join(CROP_POSITIONS)}"
        )

    crop_size = image_height  # square side = full height
    max_offset = image_width - crop_size

    # Five evenly-spaced positions from 0 to max_offset
    step_index = CROP_POSITIONS.index(position)
    x_offset = (step_index * max_offset) // (len(CROP_POSITIONS) - 1)

    return (x_offset, 0, x_offset + crop_size, image_height)


def select_crop_with_ai(
    image_path: str,
    api_key: str,
    model: str,
) -> Tuple[str, str]:
    """
    Ask Claude Vision to suggest the best 1:1 crop position for a poster.

    Parameters
    ----------
    image_path:
        Path to the high-res frame image.
    api_key:
        Anthropic API key.
    model:
        Claude model name.

    Returns
    -------
    (crop_position, reasoning)
        *crop_position* is one of the five allowed values.
        *reasoning* is the AI explanation.

    Raises
    ------
    RuntimeError
        If the AI call fails after all retries or returns an invalid response.
    """
    try:
        import anthropic
    except ImportError:
        raise RuntimeError(
            "The 'anthropic' package is required for AI crop selection. "
            "Install it with: pip install anthropic"
        )

    with open(image_path, "rb") as fh:
        image_data = base64.standard_b64encode(fh.read()).decode("utf-8")

    prompt = (
        "You are analysing an image to determine the best 1:1 square crop position "
        "for a portrait-format poster. The image is wider than it is tall. "
        "A square crop covering the full height will be taken at one of five "
        "horizontal positions: left, center-left, center, center-right, right. "
        "Choose the position that best captures the main subject or the most "
        "visually interesting area.\n\n"
        "Respond ONLY with a valid JSON object on a single line, with no additional text:\n"
        '{"crop_position": "<one of: left, center-left, center, center-right, right>", '
        '"reasoning": "<brief explanation>"}'
    )

    client = anthropic.Anthropic(api_key=api_key)

    last_error: Exception | None = None
    for attempt in range(MAX_RETRIES):
        try:
            message = client.messages.create(
                model=model,
                max_tokens=256,
                messages=[
                    {
                        "role": "user",
                        "content": [
                            {
                                "type": "image",
                                "source": {
                                    "type": "base64",
                                    "media_type": "image/jpeg",
                                    "data": image_data,
                                },
                            },
                            {
                                "type": "text",
                                "text": prompt,
                            },
                        ],
                    }
                ],
            )

            raw = message.content[0].text.strip()
            # Strip markdown code fences if present
            if raw.startswith("```"):
                raw = raw.split("```")[1]
                if raw.startswith("json"):
                    raw = raw[4:]
                raw = raw.strip()

            data = json.loads(raw)
            crop_position = str(data["crop_position"])
            reasoning = str(data["reasoning"])

            if crop_position not in CROP_POSITIONS:
                raise ValueError(
                    f"AI returned invalid crop position: {crop_position!r}"
                )

            return crop_position, reasoning

        except Exception as exc:  # noqa: BLE001
            last_error = exc
            if attempt < MAX_RETRIES - 1:
                delay = RETRY_BASE_DELAY * (2 ** attempt)
                print(
                    f"   ⚠️  AI crop attempt {attempt + 1} failed: {exc}. "
                    f"Retrying in {delay}s…",
                    file=sys.stderr,
                )
                time.sleep(delay)

    raise RuntimeError(
        f"AI crop selection failed after {MAX_RETRIES} attempts: {last_error}"
    )
