"""Shared path utilities for workspace containment checks."""

from __future__ import annotations

from pathlib import Path

from fastapi import HTTPException
from pydantic import BaseModel


class PathError(BaseModel):
    type: str = "PATH_ERROR"
    message: str
    path: str


def join_under(root: Path, child: Path) -> Path:
    """Resolve *child* relative to *root* and verify it stays inside *root*.

    Strips any leading anchor (``/``, drive letter, etc.) from *child* so that
    absolute paths are treated as workspace-relative.  Raises HTTP 400 if the
    resolved path escapes the workspace.
    """
    root = Path(root).resolve()
    child = Path(child).relative_to(Path(child).anchor or ".")

    final = (root / child).resolve()

    if root not in final.parents and final != root:
        raise HTTPException(
            status_code=400,
            detail=PathError(type="E024", message="Trying to access file outside of workspace", path=str(child)).model_dump(),
        )

    return final


def to_relative_path(root: Path, child: Path) -> str:
    """Return *child* as a workspace-relative path string, with containment enforced.

    A leading ``/`` (or any anchor) in *child* is always stripped and the
    remainder joined under *root*, so the value is never used as an absolute
    filesystem path.  The returned string has no leading separator.

    Use this for all client-supplied paths to produce the ``file_path`` value
    stored in and filtered against the search index.
    """
    final = join_under(root, child)
    return str(final.relative_to(Path(root).resolve()))
