from __future__ import annotations

from dataclasses import dataclass

from ..schema import SymbolDef
from ..utils import estimate_tokens


@dataclass
class Chunk:
    symbol_id: str
    path: str
    start_line: int
    end_line: int
    approx_tokens: int
    text: str


@dataclass
class ChunkSlicer:
    """
    Slice a file by symbol boundaries (class/function/method spans).
    This is designed for large-file handling and "evidence retrieval".
    """

    max_tokens_per_chunk: int = 8000

    def slice_by_symbols(self, *, path: str, code: str, symbols: list[SymbolDef]) -> list[Chunk]:
        if not symbols:
            est = estimate_tokens(code)
            if est.approx_tokens <= self.max_tokens_per_chunk:
                return [
                    Chunk(
                        symbol_id=f"{path}::file::{path}::1:1",
                        path=path,
                        start_line=1,
                        end_line=max(1, code.count("\n") + 1),
                        approx_tokens=est.approx_tokens,
                        text=code,
                    )
                ]
            # fallback: coarse split by lines
            return self._coarse_line_split(path=path, code=code)

        lines = code.splitlines(keepends=True)
        chunks: list[Chunk] = []

        for sym in symbols:
            sp = sym.get("span")
            sid = sym.get("symbol_id", "")
            if not sp or not sid:
                continue
            start = max(1, int(sp["start_line"]))
            end = max(start, int(sp["end_line"]))
            # 1-based lines -> 0-based indices
            text = "".join(lines[start - 1 : end])
            est = estimate_tokens(text)
            if est.approx_tokens <= self.max_tokens_per_chunk:
                chunks.append(
                    Chunk(
                        symbol_id=sid,
                        path=path,
                        start_line=start,
                        end_line=end,
                        approx_tokens=est.approx_tokens,
                        text=text,
                    )
                )
            else:
                # still too big: coarse split within the symbol span
                chunks.extend(self._coarse_line_split(path=path, code=text, start_line_offset=start))

        return chunks

    def _coarse_line_split(
        self, *, path: str, code: str, start_line_offset: int = 1
    ) -> list[Chunk]:
        lines = code.splitlines(keepends=True)
        # heuristic: assume ~4 chars/token, then tokens_per_line from median line length
        # We keep it simple: aim for ~max_tokens_per_chunk by chars.
        max_chars = self.max_tokens_per_chunk * 4
        chunks: list[Chunk] = []
        buf: list[str] = []
        buf_chars = 0
        chunk_start = start_line_offset
        line_no = start_line_offset

        def flush(end_line: int) -> None:
            nonlocal buf, buf_chars, chunk_start
            if not buf:
                return
            text = "".join(buf)
            est = estimate_tokens(text)
            chunks.append(
                Chunk(
                    symbol_id=f"{path}::coarse::{chunk_start}:{end_line}",
                    path=path,
                    start_line=chunk_start,
                    end_line=end_line,
                    approx_tokens=est.approx_tokens,
                    text=text,
                )
            )
            buf = []
            buf_chars = 0
            chunk_start = end_line + 1

        for ln in lines:
            if buf_chars + len(ln) > max_chars and buf:
                flush(line_no - 1)
            buf.append(ln)
            buf_chars += len(ln)
            line_no += 1

        flush(line_no - 1)
        return chunks

