from __future__ import annotations

import json
import os
from dataclasses import dataclass
from typing import Any

from fileatlas.schema import ProjectMap, SymbolDef


def _norm_path(p: str) -> str:
    p = (p or "").replace("\\", "/")
    # remove leading ./ for stability
    while p.startswith("./"):
        p = p[2:]
    return p.strip("/")


def load_project_map(path: str) -> ProjectMap:
    with open(path, "r", encoding="utf-8") as f:
        obj = json.load(f)
    if not isinstance(obj, dict):
        raise ValueError("project_map.json must be a JSON object")
    return obj  # type: ignore[return-value]


@dataclass(frozen=True)
class ProjectIndex:
    project: ProjectMap
    all_files: set[str]
    python_files: set[str]
    package_dirs: set[str]
    root: str
    symbols_by_qualname: dict[str, list[SymbolDef]]
    symbols_by_name: dict[str, list[SymbolDef]]
    symbols_by_path: dict[str, list[SymbolDef]]


def build_index(project: ProjectMap) -> ProjectIndex:
    files = project.get("files", {}) if isinstance(project.get("files", {}), dict) else {}
    all_files: set[str] = set()
    python_files: set[str] = set()
    package_dirs: set[str] = set()

    for raw_path, entry in (files or {}).items():
        path = _norm_path(str(raw_path))
        if not path:
            continue
        all_files.add(path)
        try:
            facts = entry.get("facts", {}) if isinstance(entry, dict) else {}
            lang = facts.get("language") if isinstance(facts, dict) else None
        except Exception:
            lang = None
        if str(lang) == "python":
            python_files.add(path)
        if path.endswith("/__init__.py"):
            pkg_dir = path[: -len("/__init__.py")]
            if pkg_dir:
                package_dirs.add(pkg_dir)

    meta = project.get("meta", {}) if isinstance(project.get("meta", {}), dict) else {}
    root = str(meta.get("root") or ".")
    root = os.path.abspath(root)

    symbols = project.get("symbols", {}) if isinstance(project.get("symbols", {}), dict) else {}
    symbols_by_qualname: dict[str, list[SymbolDef]] = {}
    symbols_by_name: dict[str, list[SymbolDef]] = {}
    symbols_by_path: dict[str, list[SymbolDef]] = {}

    for sid, sym in (symbols or {}).items():
        if not isinstance(sym, dict):
            continue
        # ensure symbol_id present
        if "symbol_id" not in sym and sid:
            sym = {**sym, "symbol_id": str(sid)}  # type: ignore[assignment]
        qn = str(sym.get("qualname") or "").strip()
        nm = str(sym.get("name") or "").strip()
        sp = str(sym.get("path") or "").strip()
        if qn:
            symbols_by_qualname.setdefault(qn, []).append(sym)  # type: ignore[arg-type]
        if nm:
            symbols_by_name.setdefault(nm, []).append(sym)  # type: ignore[arg-type]
        if sp:
            symbols_by_path.setdefault(_norm_path(sp), []).append(sym)  # type: ignore[arg-type]

    return ProjectIndex(
        project=project,
        all_files=all_files,
        python_files=python_files,
        package_dirs=package_dirs,
        root=root,
        symbols_by_qualname=symbols_by_qualname,
        symbols_by_name=symbols_by_name,
        symbols_by_path=symbols_by_path,
    )


def get_file_entry(project: ProjectMap, path: str) -> dict[str, Any]:
    files = project.get("files", {}) if isinstance(project.get("files", {}), dict) else {}
    entry = files.get(path) if isinstance(files, dict) else None
    if isinstance(entry, dict):
        return entry
    return {}


def get_file_facts(project: ProjectMap, path: str) -> dict[str, Any]:
    entry = get_file_entry(project, path)
    facts = entry.get("facts", {})
    return facts if isinstance(facts, dict) else {}


def get_file_analysis(project: ProjectMap, path: str) -> dict[str, Any]:
    entry = get_file_entry(project, path)
    analysis = entry.get("analysis", {})
    return analysis if isinstance(analysis, dict) else {}

