#!/usr/bin/python3
# /// script
# requires-python: ">=3.10"
# dependencies: []
# ///
# SPDX-FileCopyrightText: 2026 Andrew Oberstar <andrew@ajoberstar.org>
# SPDX-License-Identifier: CC0-1.0
"""Scan book covers directly into the quixote inbox.

Usage:
    quixote-scan --media-dir /var/lib/quixote/media
    quixote-scan --media-dir /var/lib/quixote/media --width 148 --height 210

The script picks the next free batch slot (e.g. 0101.pnm) based on existing
files in inbox/ and item directories in staging/. Scanimage writes each page
to inbox/ as it is scanned so the importer can process covers live.

Scan area options (--left, --top, --width, --height) are passed directly to
scanimage's -l, -t, -x and -y options. Their unit is determined by the
scanner backend (millimeters for pixma). Check `scanimage --help` for the
unit your backend uses.
"""

import argparse
import os
import subprocess
import sys
from pathlib import Path


def max_scan_number(inbox_dir: Path, staging_dir: Path) -> int:
    max_scan = 0

    if inbox_dir.exists():
        for f in inbox_dir.glob("*.pnm"):
            try:
                max_scan = max(max_scan, int(f.stem))
            except ValueError:
                pass

    if staging_dir.exists():
        for d in staging_dir.glob("item-*"):
            try:
                item_num = int(d.name.split("-")[1])
                max_scan = max(max_scan, item_num * 2)
            except (ValueError, IndexError):
                pass

    return max_scan


def next_batch_start(max_scan: int) -> int:
    return (max_scan // 100) * 100 + 101


def main() -> int:
    p = argparse.ArgumentParser(description="Scan book covers into the quixote inbox.")
    p.add_argument(
        "--media-dir",
        type=Path,
        required=True,
        help="Directory containing inbox/ and staging/",
    )
    p.add_argument("--device", type=str, help="sane device name")
    p.add_argument("--resolution", type=int, default=600)
    p.add_argument("--left", type=float, default=0, help="left scan offset in backend units")
    p.add_argument("--top", type=float, default=0, help="top scan offset in backend units")
    p.add_argument("--width", type=float, help="scan width in backend units")
    p.add_argument("--height", type=float, help="scan height in backend units")
    args = p.parse_args()

    # Allow group to control
    os.umask(0o002)

    inbox_dir = args.media_dir / "inbox"
    staging_dir = args.media_dir / "staging"
    inbox_dir.mkdir(parents=True, exist_ok=True)

    start = next_batch_start(max_scan_number(inbox_dir, staging_dir))
    print(f"Starting scan at {start:04d}.pnm")

    scan_args = [
        "scanimage",
        f"--device={args.device}",
        f"--batch={inbox_dir / '%04d.pnm'}",
        f"--batch-start={start}",
        "--batch-prompt",
        "--format=pnm",
        "--mode", "Color",
        f"--resolution={args.resolution}",
        f"-l", str(args.left),
        f"-t", str(args.top),
    ]
    if args.width is not None:
        scan_args.extend(["-x", str(args.width)])
    if args.height is not None:
        scan_args.extend(["-y", str(args.height)])

    subprocess.run(scan_args, check=True)

    return 0


if __name__ == "__main__":
    sys.exit(main())
