"""
Annotate a screenshot with rectangles, arrows, text labels, circles, and highlights.

Usage:
    python annotate.py --input screenshot.png --output annotated.png --annotations annotations.json

Annotations JSON format — an array of objects, each with a "type" field:

    rect:      {"type": "rect", "x": 100, "y": 200, "width": 300, "height": 50, "color": "red", "thickness": 3}
    arrow:     {"type": "arrow", "from_x": 100, "from_y": 100, "to_x": 300, "to_y": 200, "color": "red", "thickness": 3}
    text:      {"type": "text", "x": 100, "y": 150, "text": "Bug here!", "color": "red", "size": 20}
    circle:    {"type": "circle", "cx": 200, "cy": 200, "radius": 50, "color": "yellow", "thickness": 2}
    highlight: {"type": "highlight", "x": 100, "y": 200, "width": 300, "height": 50, "color": "yellow", "opacity": 0.3}
"""
import argparse
import json
import math
import os
import sys

try:
    from PIL import Image, ImageDraw, ImageFont
except ImportError:
    print("Error: Pillow is not installed. Run the setup script first.", file=sys.stderr)
    sys.exit(1)


# Named color mapping
COLOR_MAP = {
    "red": (255, 0, 0),
    "green": (0, 180, 0),
    "blue": (0, 0, 255),
    "yellow": (255, 255, 0),
    "orange": (255, 165, 0),
    "cyan": (0, 255, 255),
    "magenta": (255, 0, 255),
    "white": (255, 255, 255),
    "black": (0, 0, 0),
    "pink": (255, 105, 180),
    "purple": (128, 0, 128),
}


def parse_color(color_str):
    """Parse a color name or hex code into an (R, G, B) tuple."""
    if not color_str:
        return (255, 0, 0)  # default red

    color_str = color_str.strip().lower()
    if color_str in COLOR_MAP:
        return COLOR_MAP[color_str]

    # Hex code
    if color_str.startswith("#"):
        hex_str = color_str.lstrip("#")
        if len(hex_str) == 6:
            return tuple(int(hex_str[i:i+2], 16) for i in (0, 2, 4))
        elif len(hex_str) == 3:
            return tuple(int(c*2, 16) for c in hex_str)

    print(f"Warning: Unrecognized color '{color_str}', using red.", file=sys.stderr)
    return (255, 0, 0)


def draw_rect(draw, ann):
    """Draw a rectangle outline."""
    x, y = ann["x"], ann["y"]
    w, h = ann["width"], ann["height"]
    color = parse_color(ann.get("color"))
    thickness = ann.get("thickness", 2)
    draw.rectangle([x, y, x + w, y + h], outline=color, width=thickness)


def draw_arrow(draw, ann):
    """Draw a line with an arrowhead."""
    fx, fy = ann["from_x"], ann["from_y"]
    tx, ty = ann["to_x"], ann["to_y"]
    color = parse_color(ann.get("color"))
    thickness = ann.get("thickness", 2)

    # Main line
    draw.line([(fx, fy), (tx, ty)], fill=color, width=thickness)

    # Arrowhead
    angle = math.atan2(ty - fy, tx - fx)
    arrow_len = max(15, thickness * 5)
    arrow_angle = math.radians(25)

    x1 = tx - arrow_len * math.cos(angle - arrow_angle)
    y1 = ty - arrow_len * math.sin(angle - arrow_angle)
    x2 = tx - arrow_len * math.cos(angle + arrow_angle)
    y2 = ty - arrow_len * math.sin(angle + arrow_angle)

    draw.polygon([(tx, ty), (x1, y1), (x2, y2)], fill=color)


def draw_text(img, draw, ann):
    """Draw a text label."""
    x, y = ann["x"], ann["y"]
    text = ann["text"]
    color = parse_color(ann.get("color"))
    size = ann.get("size", 16)

    # Try to use a TrueType font; fall back to default
    font = None
    try:
        # Common Windows font paths
        for font_path in [
            "C:/Windows/Fonts/arial.ttf",
            "C:/Windows/Fonts/segoeui.ttf",
            "C:/Windows/Fonts/calibri.ttf",
        ]:
            if os.path.isfile(font_path):
                font = ImageFont.truetype(font_path, size)
                break
    except Exception:
        pass

    if font is None:
        font = ImageFont.load_default()

    # Draw with a slight dark outline for readability
    for dx in [-1, 0, 1]:
        for dy in [-1, 0, 1]:
            if dx == 0 and dy == 0:
                continue
            draw.text((x + dx, y + dy), text, fill=(0, 0, 0), font=font)
    draw.text((x, y), text, fill=color, font=font)


def draw_circle(draw, ann):
    """Draw a circle outline."""
    cx, cy = ann["cx"], ann["cy"]
    r = ann["radius"]
    color = parse_color(ann.get("color"))
    thickness = ann.get("thickness", 2)
    draw.ellipse([cx - r, cy - r, cx + r, cy + r], outline=color, width=thickness)


def draw_highlight(img, ann):
    """Draw a semi-transparent filled rectangle overlay."""
    x, y = ann["x"], ann["y"]
    w, h = ann["width"], ann["height"]
    color = parse_color(ann.get("color"))
    opacity = ann.get("opacity", 0.3)

    overlay = Image.new("RGBA", img.size, (0, 0, 0, 0))
    overlay_draw = ImageDraw.Draw(overlay)
    alpha = int(255 * opacity)
    overlay_draw.rectangle([x, y, x + w, y + h], fill=(*color, alpha))

    # Composite
    if img.mode != "RGBA":
        img = img.convert("RGBA")
    composited = Image.alpha_composite(img, overlay)
    return composited


def annotate(image_path, annotations, output_path):
    """Apply annotations to an image and save the result."""
    img = Image.open(image_path).convert("RGBA")
    draw = ImageDraw.Draw(img)

    for ann in annotations:
        ann_type = ann.get("type", "")

        if ann_type == "rect":
            draw_rect(draw, ann)
        elif ann_type == "arrow":
            draw_arrow(draw, ann)
        elif ann_type == "text":
            draw_text(img, draw, ann)
        elif ann_type == "circle":
            draw_circle(draw, ann)
        elif ann_type == "highlight":
            img = draw_highlight(img, ann)
            draw = ImageDraw.Draw(img)  # Re-create draw after composite
        else:
            print(f"Warning: Unknown annotation type '{ann_type}', skipping.", file=sys.stderr)

    # Save as PNG (convert back to RGB if needed)
    if output_path.lower().endswith(".jpg") or output_path.lower().endswith(".jpeg"):
        img = img.convert("RGB")
    img.save(output_path)
    print(json.dumps({"status": "ok", "output": output_path}))


def main():
    parser = argparse.ArgumentParser(description="Annotate a screenshot.")
    parser.add_argument("--input", required=True, help="Input image file path")
    parser.add_argument("--output", required=True, help="Output annotated image file path")
    parser.add_argument("--annotations", required=True, help="JSON file with annotation definitions")
    args = parser.parse_args()

    if not os.path.isfile(args.input):
        print(f"Error: Input file not found: {args.input}", file=sys.stderr)
        sys.exit(1)

    if not os.path.isfile(args.annotations):
        print(f"Error: Annotations file not found: {args.annotations}", file=sys.stderr)
        sys.exit(1)

    with open(args.annotations, "r", encoding="utf-8") as f:
        annotations = json.load(f)

    if not isinstance(annotations, list):
        print("Error: Annotations file must contain a JSON array.", file=sys.stderr)
        sys.exit(1)

    annotate(args.input, annotations, args.output)


if __name__ == "__main__":
    main()
