from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
from typing import Iterable
import shutil
import pymupdf
from .extractor import extract_all_images
from .reporting import print_report, write_csv, fmt_page_dims
from .recommendations import recommendation_tags_for_record, recommendation_long_for_record


def _page_size_pt(page: pymupdf.Page) -> tuple[float, float]:
    r = page.rect
    return (float(r.width), float(r.height))


def _suspect_tiled_images(extracted: list[dict]) -> bool:
    return len(extracted) > 12


def _build_analysis_metadata(failed_pages: list[int], canceled: bool) -> tuple[str, str]:
    notes: list[str] = []
    status = "complete"

    if failed_pages:
        status = "incomplete"
        pages_txt = ", ".join(str(p) for p in failed_pages)
        notes.append(f"Some pages could not be analyzed: {pages_txt}.")

    if canceled:
        status = "canceled" if not failed_pages else "incomplete"
        notes.append("Analysis was canceled before all pages were processed.")

    return status, " ".join(notes)


def find_pdfs(root: Path, recursive: bool = False) -> Iterable[Path]:
    pattern = "**/*.pdf" if recursive else "*.pdf"
    for p in root.glob(pattern):
        if p.is_file():
            yield p


def analyze_pdf(
    pdf_path: Path,
    out_dir: Path,
    *,
    verbose: bool = False,
    progress: Callable[[int, int], None] | None = None,
    should_cancel: Callable[[], bool] | None = None,
) -> tuple[list[dict], list[int], list[int], bool]:

    if out_dir.exists():
        shutil.rmtree(out_dir)
    out_dir.mkdir(parents=True, exist_ok=True)

    all_rows: list[dict] = []
    suspected_tiling_pages: list[int] = []
    failed_pages: list[int] = []
    canceled = False

    with pymupdf.open(pdf_path.as_posix()) as doc:
        total_pages = len(doc)

        if progress:
            progress(0, max(total_pages, 1))

        for idx in range(1, total_pages + 1):
            if should_cancel and should_cancel():
                canceled = True
                break

            page = doc[idx - 1]
            pw_pt, ph_pt = _page_size_pt(page)
            page_dims_str = fmt_page_dims(pw_pt, ph_pt)

            try:
                extracted = extract_all_images(page, idx, out_dir)
            except Exception as e:
                failed_pages.append(idx)
                if verbose:
                    print(f"[error] Page {idx}: failed to extract images: {e}")
                continue

            suspected_tiling = _suspect_tiled_images(extracted)
            if suspected_tiling:
                suspected_tiling_pages.append(idx)

            occ_rows: list[dict] = []
            for rec in extracted:
                sh, hi, co, cf, cr = rec["metrics"]

                row: dict = {
                    "page": rec["page"],
                    "image_id": rec["image_id"],
                    "dpi_pair": rec["dpi_pair"],
                    "bbox_pt": rec["bbox_pt"],
                    "page_dims": page_dims_str,
                    "pixels": rec.get("pixels"),
                    "saved": rec.get("saved"),
                }

                if sh is not None:
                    row["shadow_clip_pct"] = sh
                if hi is not None:
                    row["highlight_clip_pct"] = hi
                if co is not None:
                    row["contrast_p1p99_pct"] = co
                if cf is not None:
                    row["colorfulness_hs"] = cf
                if cr is not None:
                    row["color_richness_pct"] = cr

                tags_str = recommendation_tags_for_record(row)
                row["recommendations"] = tags_str
                row["recommendations_long"] = recommendation_long_for_record(tags_str)
                row["analysis_status"] = "complete"
                row["analysis_notes"] = ""
                occ_rows.append(row)

            if verbose:
                print_report(idx, pw_pt, ph_pt, occ_rows, suspected_tiling=suspected_tiling)

            all_rows.extend(occ_rows)

            if progress:
                progress(idx, max(total_pages, 1))

    analysis_status, analysis_notes = _build_analysis_metadata(failed_pages, canceled)
    for row in all_rows:
        row["analysis_status"] = analysis_status
        row["analysis_notes"] = analysis_notes

    return all_rows, suspected_tiling_pages, failed_pages, canceled


def run(pdf_path: Path, out_dir: Path, csv_path: Path) -> None:
    rows, suspected, failed_pages, canceled = analyze_pdf(pdf_path, out_dir, verbose=True)
    write_csv(rows, csv_path)

    if suspected:
        pages_txt = ", ".join(str(p) for p in suspected)
        print("\n[warning] Suspected tiled images detected on pages:")
        print(f"          {pages_txt}")
        print("          Detection is heuristic and may contain false positives.")

    if failed_pages:
        pages_txt = ", ".join(str(p) for p in failed_pages)
        print("\n[warning] Some pages could not be analyzed completely:")
        print(f"          {pages_txt}")

    if canceled:
        print("\n[warning] Analysis was canceled before all pages were processed.")
