from __future__ import annotations

import json
from pathlib import Path
import shutil
from typing import Any


def read(path: str, *, runtime: Any) -> str:
    target = _workspace_path(path, runtime.workspace_root)
    return target.read_text(encoding="utf-8")


def read_json(path: str, *, runtime: Any) -> Any:
    return json.loads(read(path, runtime=runtime))


def write(path: str, content: Any, *, runtime: Any) -> dict[str, Any]:
    target = _workspace_path(path, runtime.workspace_root)
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(str(content), encoding="utf-8")
    return {"path": path, "written": True}


def write_json(
    path: str,
    value: Any,
    indent: int | None = 2,
    sort_keys: bool = False,
    *,
    runtime: Any,
) -> dict[str, Any]:
    content = json.dumps(value, indent=indent, sort_keys=sort_keys)
    return write(path, content, runtime=runtime)


def append(path: str, content: Any, *, runtime: Any) -> dict[str, Any]:
    target = _workspace_path(path, runtime.workspace_root)
    target.parent.mkdir(parents=True, exist_ok=True)
    with target.open("a", encoding="utf-8") as handle:
        handle.write(str(content))
    return {"path": path, "appended": True}


def list_dir(path: str, *, runtime: Any) -> list[str]:
    target = _workspace_path(path, runtime.workspace_root)
    return sorted(entry.name for entry in target.iterdir())


def delete(path: str, *, runtime: Any) -> dict[str, Any]:
    target = _workspace_path(path, runtime.workspace_root)
    target.unlink()
    return {"path": path, "deleted": True}


def exists(path: str, *, runtime: Any) -> bool:
    target = _workspace_path(path, runtime.workspace_root)
    return target.exists()


def stat(path: str, *, runtime: Any) -> dict[str, Any]:
    target = _workspace_path(path, runtime.workspace_root)
    if not target.exists():
        return {"path": path, "exists": False}

    details = target.stat()
    return {
        "path": path,
        "exists": True,
        "is_file": target.is_file(),
        "is_dir": target.is_dir(),
        "size": details.st_size,
        "modified": details.st_mtime,
    }


def mkdir(path: str, *, runtime: Any) -> dict[str, Any]:
    target = _workspace_path(path, runtime.workspace_root)
    target.mkdir(parents=True, exist_ok=True)
    return {"path": path, "created": True}


def copy(
    source: str,
    destination: str,
    overwrite: bool = False,
    *,
    runtime: Any,
) -> dict[str, Any]:
    source_path = _workspace_path(source, runtime.workspace_root)
    destination_path = _workspace_path(destination, runtime.workspace_root)

    if not source_path.is_file():
        raise FileNotFoundError(f"source file does not exist: {source}")
    if destination_path.exists() and not overwrite:
        raise FileExistsError(f"destination already exists: {destination}")

    destination_path.parent.mkdir(parents=True, exist_ok=True)
    shutil.copy2(source_path, destination_path)
    return {"source": source, "destination": destination, "copied": True}


def move(
    source: str,
    destination: str,
    overwrite: bool = False,
    *,
    runtime: Any,
) -> dict[str, Any]:
    source_path = _workspace_path(source, runtime.workspace_root)
    destination_path = _workspace_path(destination, runtime.workspace_root)

    if not source_path.exists():
        raise FileNotFoundError(f"source path does not exist: {source}")
    if destination_path.exists():
        if not overwrite:
            raise FileExistsError(f"destination already exists: {destination}")
        if destination_path.is_dir():
            raise IsADirectoryError(f"destination is a directory: {destination}")
        destination_path.unlink()

    destination_path.parent.mkdir(parents=True, exist_ok=True)
    shutil.move(str(source_path), str(destination_path))
    return {"source": source, "destination": destination, "moved": True}


def glob(path: str, pattern: str = "*", *, runtime: Any) -> list[str]:
    target = _workspace_path(path, runtime.workspace_root)
    matches: list[str] = []
    for entry in target.glob(pattern):
        matches.append(entry.relative_to(runtime.workspace_root).as_posix())
    return sorted(matches)


def _workspace_path(path: str, workspace_root: Path) -> Path:
    candidate = Path(path)
    if not candidate.is_absolute():
        candidate = workspace_root / candidate
    return candidate.resolve()
