from __future__ import annotations

from dataclasses import dataclass


@dataclass(frozen=True)
class IniKey:
    """Pair of (section, option) identifying a value inside an INI file."""

    section: str
    option: str


def get_ini_value(content: str, *, section: str, option: str) -> str | None:
    """Return the value for an INI option in a section (best-effort).

    - Ignores comments and blank lines
    - Matches `option=value` or `option = value`
    - Returns None if section/option not found
    """
    current_section: str | None = None
    target_section = section.strip()
    target_option = option.strip()

    for raw in content.splitlines():
        line = raw.strip()
        if not line or line.startswith(("#", ";")):
            continue
        if line.startswith("[") and line.endswith("]"):
            current_section = line[1:-1].strip()
            continue
        if current_section != target_section:
            continue
        if "=" not in line:
            continue
        left, right = line.split("=", 1)
        if left.strip() == target_option:
            return right.strip()
    return None


def set_ini_value(content: str, *, section: str, option: str, value: str) -> str:
    """Set or add an INI option inside a section.

    This is a small, formatting-preserving editor:
    - Keeps existing comments/ordering as-is
    - Replaces the first matching `option = ...` or `option=...` in the target section
    - Appends the option at the end of the section if missing
    - Creates the section if missing
    """
    lines = content.splitlines(True)  # keep line endings
    sec_header = f"[{section}]"

    # Locate section bounds.
    sec_start = None
    for i, line in enumerate(lines):
        if line.strip() == sec_header:
            sec_start = i
            break

    if sec_start is None:
        # Append a new section at the end.
        if lines and not lines[-1].endswith("\n"):
            lines[-1] = lines[-1] + "\n"
        if lines and lines[-1].strip() != "":
            lines.append("\n")
        lines.append(f"{sec_header}\n")
        lines.append(f"{option}={value}\n")
        return "".join(lines)

    # Find section end.
    sec_end = len(lines)
    for j in range(sec_start + 1, len(lines)):
        s = lines[j].strip()
        if s.startswith("[") and s.endswith("]"):
            sec_end = j
            break

    # Replace option if exists.
    opt_prefix = option.strip()
    for k in range(sec_start + 1, sec_end):
        raw = lines[k]
        stripped = raw.lstrip()
        if not stripped or stripped.startswith(("#", ";")):
            continue
        if stripped.startswith(opt_prefix) and ("=" in stripped):
            left = stripped.split("=", 1)[0].strip()
            if left == opt_prefix:
                newline = "\n" if raw.endswith("\n") else ""
                indent = raw[: len(raw) - len(stripped)]
                lines[k] = f"{indent}{option}={value}{newline}"
                return "".join(lines)

    # Not found: append before section end (keep a trailing newline).
    insert_at = sec_end
    if insert_at > 0 and not lines[insert_at - 1].endswith("\n"):
        lines[insert_at - 1] = lines[insert_at - 1] + "\n"
    lines.insert(insert_at, f"{option}={value}\n")
    return "".join(lines)

