"""
Core minification logic for minson.
"""

import json
import os
from typing import Union


def minify(data: Union[str, dict, list]) -> str:
    """
    Minify JSON to reduce token count for LLM API calls.

    Accepts:
        - A Python dict or list
        - A JSON string
        - A file path to a .json file

    Returns:
        A minified JSON string with all whitespace removed.

    Examples:
        >>> from minson import minify
        >>> minify({"name": "Alice", "age": 30})
        '{"name":"Alice","age":30}'

        >>> minify('{ "name": "Alice",  "age": 30 }')
        '{"name":"Alice","age":30}'

        >>> minify("/path/to/data.json")
        '{"name":"Alice","age":30}'

    Raises:
        ValueError: If the input is not valid JSON or a valid file path.
        FileNotFoundError: If a file path is given but the file doesn't exist.
        TypeError: If the input type is not supported.
    """
    if isinstance(data, (dict, list)):
        return _dumps(data)

    if isinstance(data, str):
        # Check if it looks like a file path
        if data.endswith(".json") or os.path.exists(data):
            return _from_file(data)
        # Otherwise treat as a raw JSON string
        return _from_string(data)

    raise TypeError(
        f"Unsupported type: {type(data).__name__}. "
        "Expected a dict, list, JSON string, or file path."
    )


def minify_file(path: str, output_path: str = None) -> str:
    """
    Minify a JSON file and optionally write the result to a new file.

    Args:
        path: Path to the input .json file.
        output_path: Optional path to write the minified output.
                     If omitted, the minified string is just returned.

    Returns:
        The minified JSON string.

    Example:
        >>> minify_file("data.json", "data.min.json")
    """
    result = _from_file(path)
    if output_path:
        with open(output_path, "w") as f:
            f.write(result)
    return result


def token_savings(data: Union[str, dict, list]) -> dict:
    """
    Estimate token savings from minifying the given JSON.

    Returns a dict with:
        - original_chars: character count before minification
        - minified_chars: character count after minification
        - chars_saved: characters removed
        - estimated_pct_saved: rough % reduction

    Note: Token count ≈ chars / 4 (rough estimate for English/JSON text).

    Example:
        >>> token_savings({"name": "Alice", "age": 30})
        {'original_chars': 30, 'minified_chars': 24, 'chars_saved': 6, 'estimated_pct_saved': 20.0}
    """
    if isinstance(data, (dict, list)):
        original = json.dumps(data, indent=2)
    elif isinstance(data, str):
        if data.endswith(".json") or os.path.exists(data):
            with open(data, "r") as f:
                original = f.read()
        else:
            original = data
    else:
        raise TypeError(f"Unsupported type: {type(data).__name__}")

    minified = minify(data)
    original_chars = len(original)
    minified_chars = len(minified)
    chars_saved = original_chars - minified_chars
    pct_saved = round((chars_saved / original_chars) * 100, 1) if original_chars else 0

    return {
        "original_chars": original_chars,
        "minified_chars": minified_chars,
        "chars_saved": chars_saved,
        "estimated_pct_saved": pct_saved,
        "estimated_tokens_saved": round(chars_saved / 4),
    }


# ── Internal helpers ──────────────────────────────────────────────────────────

def _dumps(obj) -> str:
    return json.dumps(obj, separators=(",", ":"))


def _from_string(s: str) -> str:
    try:
        parsed = json.loads(s)
    except json.JSONDecodeError as e:
        raise ValueError(f"Invalid JSON string: {e}") from e
    return _dumps(parsed)


def _from_file(path: str) -> str:
    if not os.path.exists(path):
        raise FileNotFoundError(f"File not found: {path}")
    with open(path, "r") as f:
        try:
            parsed = json.load(f)
        except json.JSONDecodeError as e:
            raise ValueError(f"Invalid JSON in file '{path}': {e}") from e
    return _dumps(parsed)