"""Image preprocessing pipeline for OCR accuracy improvement.

Uses OpenCV for deskew, adaptive binarization, CLAHE contrast enhancement,
and denoising. All operations work on the image file in-place (overwrite).
"""
import cv2
import numpy as np
import sys
import json


def emit_progress(percent, stage):
    print(json.dumps({"progress": percent, "stage": stage}), file=sys.stderr, flush=True)


def deskew(image):
    """Detect and correct rotation/skew using Hough line transform."""
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
    edges = cv2.Canny(gray, 50, 150, apertureSize=3)
    lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=100, minLineLength=100, maxLineGap=10)

    if lines is None:
        return image

    angles = []
    for line in lines:
        x1, y1, x2, y2 = line[0]
        angle = np.degrees(np.arctan2(y2 - y1, x2 - x1))
        if abs(angle) < 45:
            angles.append(angle)

    if not angles:
        return image

    median_angle = np.median(angles)
    if abs(median_angle) < 0.5 or abs(median_angle) > 15:
        return image

    h, w = image.shape[:2]
    center = (w // 2, h // 2)
    matrix = cv2.getRotationMatrix2D(center, median_angle, 1.0)
    rotated = cv2.warpAffine(image, matrix, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
    return rotated


def binarize(image):
    """Adaptive thresholding for high-contrast black/white."""
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
    binary = cv2.adaptiveThreshold(
        gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2
    )
    return cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR)


def enhance_contrast(image):
    """CLAHE contrast enhancement for uneven lighting."""
    lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
    l, a, b = cv2.split(lab)
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
    l = clahe.apply(l)
    enhanced = cv2.merge([l, a, b])
    return cv2.cvtColor(enhanced, cv2.COLOR_LAB2BGR)


def denoise(image):
    """Bilateral filter to remove noise while preserving text edges."""
    return cv2.bilateralFilter(image, 9, 75, 75)


def preprocess(input_path, output_path):
    """Run the full preprocessing pipeline and save result.

    Steps: deskew -> enhance contrast -> denoise -> binarize
    Order matters: binarize last because it strips color info needed by CLAHE.
    """
    image = cv2.imread(input_path)
    if image is None:
        raise ValueError(f"Cannot read image: {input_path}")

    image = deskew(image)
    image = enhance_contrast(image)
    image = denoise(image)
    image = binarize(image)

    cv2.imwrite(output_path, image)
    return output_path
