#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Save figure as bundle (ZIP format)."""

import hashlib
import json
import tempfile
import warnings
import zipfile
from pathlib import Path
from typing import Optional, Union

from ._extract import (
    extract_data_from_record,
    extract_spec_from_record,
    extract_style_from_record,
)
from ._paths import DATA_FILENAME, EXPORTS_DIR, SPEC_FILENAME, STYLE_FILENAME


def save_bundle(
    fig,
    path: Union[str, Path],
    dpi: Optional[int] = None,
    image_formats: Optional[list] = None,
    save_hitmap: bool = True,
    verbose: bool = True,
) -> Path:
    """Save figure as a layered bundle (ZIP format).

    Bundle structure inside ZIP:
        spec.json      # WHAT (semantic specification)
        style.json     # HOW (appearance settings)
        data.csv       # DATA (immutable source data)
        exports/
            figure.png
            figure_hitmap.png

    Parameters
    ----------
    fig : RecordingFigure
        The figure to save.
    path : str or Path
        Output path (.zip will be added if not present).
    dpi : int, optional
        DPI for exports (default from style or 300).
    image_formats : list, optional
        Image formats to export (default: ['png']).
    save_hitmap : bool
        Whether to save hitmap for GUI editing (default: True).
    verbose : bool
        Whether to print status (default: True).

    Returns
    -------
    Path
        Path to saved ZIP bundle.
    """
    from .._wrappers import RecordingFigure

    if not isinstance(fig, RecordingFigure):
        raise TypeError(
            "Expected RecordingFigure. Use fr.subplots() to create "
            "a recording-enabled figure."
        )

    # Ensure .zip extension
    path = Path(path)
    if path.suffix.lower() != ".zip":
        path = path.with_suffix(".zip")

    # Get DPI
    if dpi is None:
        from .._api._save import get_save_dpi

        dpi = get_save_dpi()

    # Create temporary directory for bundle contents
    with tempfile.TemporaryDirectory() as tmpdir:
        tmpdir = Path(tmpdir)
        exports_dir = tmpdir / EXPORTS_DIR
        exports_dir.mkdir()

        # Extract and save spec
        spec = extract_spec_from_record(fig.record)

        # Extract and save data
        df = extract_data_from_record(fig.record)
        if not df.empty:
            # Add data hash for integrity tracking
            data_hash = hashlib.sha256(df.to_csv().encode()).hexdigest()[:16]
            spec["data_hash"] = data_hash
            df.to_csv(tmpdir / DATA_FILENAME, index=False)

        # Save spec (after adding data_hash)
        with open(tmpdir / SPEC_FILENAME, "w") as f:
            json.dump(spec, f, indent=2, default=str)

        # Extract and save style
        style = extract_style_from_record(fig.record)
        with open(tmpdir / STYLE_FILENAME, "w") as f:
            json.dump(style, f, indent=2, default=str)

        # Export images
        image_formats = image_formats or ["png"]
        for fmt in image_formats:
            export_path = exports_dir / f"figure.{fmt}"
            fig.fig.savefig(export_path, dpi=dpi)

        # Save hitmap
        if save_hitmap:
            try:
                from .._editor._hitmap import generate_hitmap

                hitmap_img, _ = generate_hitmap(fig, dpi=min(dpi, 150))
                hitmap_path = exports_dir / "figure_hitmap.png"
                hitmap_img.save(hitmap_path)
            except Exception as e:
                if verbose:
                    warnings.warn(f"Hitmap generation failed: {e}")

        # Create ZIP file with root directory matching zip filename
        path.parent.mkdir(parents=True, exist_ok=True)
        root_dir = path.stem  # e.g., "figure" from "figure.zip"
        with zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED) as zf:
            for file in tmpdir.rglob("*"):
                if file.is_file():
                    arcname = Path(root_dir) / file.relative_to(tmpdir)
                    zf.write(file, arcname)

    if verbose:
        print(f"Saved bundle: {path}")
        print(f"  (extracts to: {path.stem}/)")
        with zipfile.ZipFile(path, "r") as zf:
            for info in zf.infolist():
                if not info.is_dir():
                    print(f"  {info.filename}: {info.file_size} bytes")

    return path
