# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
# Licensed under AGPL-3.0-or-later - see LICENSE file
# Part of SuperLocalMemory v3.4.22 — LLD-05 §6

"""Copilot adapter — writes ``.github/copilot-instructions.md``.

LLD-05 §6. Verified (verification-2026-04-17.md claim 5): plain markdown,
no frontmatter, soft 2 KB / hard 4 KB cap. Adapter is INACTIVE when the
project has no ``.github/`` directory — we do not create it ourselves.

Hard rules covered here:
  - A1 / A2 / A3 / A7: via ``adapter_base.atomic_write``.
  - A4: soft 2 KB + hard 4 KB cap enforcement.
"""

from __future__ import annotations

import logging
import os
from pathlib import Path

from superlocalmemory.core.security_primitives import (
    PathTraversalError,
    safe_resolve,
)
from superlocalmemory.hooks.adapter_base import (
    COPILOT_SOFT_BYTES,
    HARD_BYTES_CAP,
    WriteResult,
    atomic_write,
    record_disable,
    truncate_to_cap,
)
from superlocalmemory.hooks.context_payload import (
    ContextPayload,
    RecallFn,
    build_payload,
    format_entities,
    format_topics,
    truncate_payload_for_cap,
)

logger = logging.getLogger(__name__)

NAME = "copilot_project"
TARGET_REL = ".github/copilot-instructions.md"

_BODY_TEMPLATE = (
    "# SLM Active Brain Context (auto-generated)\n\n"
    "_Regenerated by SuperLocalMemory every 15 min. Version {version}._\n\n"
    "## User preferences\n{topics}\n\n"
    "## Entities\n{entities}\n\n"
    "## Never do\n"
    "- Do not modify files under `.slm/`\n"
    "- Do not commit `*.slm-cache.db`\n"
)


def render_copilot(payload: ContextPayload) -> bytes:
    return _BODY_TEMPLATE.format(
        version=payload.version,
        topics=format_topics(payload),
        entities=format_entities(payload),
    ).encode("utf-8")


class CopilotAdapter:
    """Project-scope Copilot adapter."""

    def __init__(
        self,
        *,
        base_dir: Path,
        sync_log_db: Path,
        recall_fn: RecallFn,
        profile_id: str = "default",
        soft_cap: int = COPILOT_SOFT_BYTES,
        hard_cap: int = HARD_BYTES_CAP,
    ) -> None:
        self._base_dir = Path(base_dir)
        self._sync_log_db = Path(sync_log_db)
        self._recall_fn = recall_fn
        self._profile_id = profile_id
        self._soft_cap = soft_cap
        self._hard_cap = hard_cap
        self.name = NAME
        self._inactive_until_retry = False

    @property
    def target_path(self) -> Path:
        return safe_resolve(self._base_dir, TARGET_REL)

    def is_active(self) -> bool:
        if os.environ.get("SLM_COPILOT_DISABLED") == "1":
            return False
        if self._inactive_until_retry:
            return False
        if os.environ.get("SLM_COPILOT_FORCE") == "1":
            return True
        # Active iff `.github/` directory already exists in the project.
        github_dir = self._base_dir / ".github"
        return github_dir.is_dir()

    def sync(self) -> bool:
        try:
            resolved = self.target_path
        except PathTraversalError:
            logger.warning("copilot: path-traversal refused")
            self._inactive_until_retry = True
            return False

        payload = build_payload(
            self._profile_id, "project", self._base_dir,
            recall_fn=self._recall_fn,
        )

        # Prefer the soft cap; fall back to hard cap.
        rendered = truncate_payload_for_cap(
            payload, hard_cap=self._soft_cap, render=render_copilot,
        )
        if len(rendered) > self._hard_cap:
            rendered = truncate_payload_for_cap(
                payload, hard_cap=self._hard_cap, render=render_copilot,
            )
            rendered = truncate_to_cap(rendered, cap=self._hard_cap)

        result: WriteResult = atomic_write(
            resolved, rendered,
            adapter_name=self.name,
            profile_id=self._profile_id,
            sync_log_db=self._sync_log_db,
        )
        return result.wrote

    def disable(self) -> None:
        try:
            resolved = self.target_path
        except PathTraversalError:
            return
        if resolved.exists():
            try:
                resolved.unlink()
            except OSError:  # pragma: no cover
                pass
        record_disable(
            resolved,
            adapter_name=self.name,
            profile_id=self._profile_id,
            sync_log_db=self._sync_log_db,
        )
        self._inactive_until_retry = True


__all__ = ("CopilotAdapter", "NAME", "TARGET_REL", "render_copilot")
