"""Parse GNU `ls -la` permission strings and listing lines."""

from __future__ import annotations

import re

_ISO_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}$")


def modified_from_ls_parts(parts: list[str]) -> str:
    """Build display timestamp from ``ls`` fields (without filename)."""
    if len(parts) < 6:
        return ""
    if len(parts) >= 7 and _ISO_DATE.match(parts[5]):
        time_part = parts[6].split(".", 1)[0] if parts[6] else ""
        return f"{parts[5]} {time_part}".strip()
    if len(parts) >= 9:
        return " ".join(parts[5:8])
    return " ".join(parts[5:])


def parse_ls_la_line(line: str) -> dict | None:
    """Parse one ``ls -la`` line; returns None for total/skip lines.

    Handles two timestamp formats produced by different ``ls`` implementations:
    - Standard (3-token date):  ``perms nlinks owner group size Mon DD HH:MM name``  → 9+ fields
    - GNU long-iso (2-token):   ``perms nlinks owner group size YYYY-MM-DD HH:MM name`` → 8+ fields

    With ``--time-style=long-iso`` the date collapses from 3 tokens to 2, so
    the total token count drops from 9 to 8.  Directories and regular files were
    silently dropped (only symlinks survived because their names contain
    ``" -> target"`` which pushed the count back to 9).
    """
    stripped = line.strip()
    if not stripped or stripped.startswith("total "):
        return None

    # Split into at most 9 pieces so filenames with spaces are kept intact.
    parts = stripped.split(None, 8)
    if len(parts) < 8:
        return None

    perms = parts[0]
    is_dir = perms.startswith("d")
    is_link = perms.startswith("l")

    # Detect format by checking whether field[5] is an ISO date (YYYY-MM-DD).
    # long-iso: [perms nlinks owner group size DATE TIME name]  → name at index 7
    # standard: [perms nlinks owner group size MON  DAY  TIME name] → name at index 8
    if _ISO_DATE.match(parts[5]):
        # long-iso format — name is at index 7 (or rest of string)
        if len(parts) < 8:
            return None
        name = parts[7]
        modified = f"{parts[5]} {parts[6].split('.', 1)[0]}"
    else:
        # standard format — need at least 9 tokens for the name at index 8
        if len(parts) < 9:
            return None
        name = parts[8]
        modified = modified_from_ls_parts(parts)

    link_target = None
    if is_link and " -> " in name:
        name, link_target = name.split(" -> ", 1)
    if name in (".", ".."):
        return None

    return {
        "name": name,
        "is_dir": is_dir,
        "is_link": is_link,
        "link_target": link_target,
        "size": parts[4],
        "perms": perms,
        "owner": parts[2],
        "group": parts[3] if len(parts) > 3 else "",
        "modified": modified,
    }


def perms_to_mode_octal(perms: str) -> str | None:
    """Convert ``drwxr-xr-x`` / ``-rw-r--r--`` to ``755`` / ``644``."""
    if not perms or len(perms) < 10:
        return None
    bits = perms[1:10]
    if len(bits) != 9:
        return None

    def _tri(chunk: str) -> int:
        value = 0
        if chunk[0] == "r":
            value += 4
        if chunk[1] == "w":
            value += 2
        if chunk[2] in "xXsStT":
            value += 1
        return value

    return f"{_tri(bits[0:3])}{_tri(bits[3:6])}{_tri(bits[6:9])}"


def format_owner_label(owner: str, group: str | None = None) -> str:
    """Primary list label: ``www`` or ``www:nogroup`` when group differs."""
    o = (owner or "").strip()
    g = (group or "").strip()
    if not o:
        return "—"
    if not g or g == o:
        return o
    return f"{o}:{g}"
