#!/usr/bin/env python3
"""
Build a migration manifest from Flexible Survival's Inform story file.

The tool is intentionally source-path agnostic. Point it at the current
story.ni location and, optionally, the root that contains the included Inform
source files.
"""

from __future__ import annotations

import argparse
import json
import re
from collections import Counter
from pathlib import Path


INCLUDE_RE = re.compile(r"^Include\s+(.+?)\s+by\s+(.+?)\.", re.IGNORECASE)
HEADING_RE = re.compile(r"^(Book|Part)\s+(\d+)\s*-\s*(.+)$", re.IGNORECASE)

WOLF_TERMS = (
    "wolf",
    "wolves",
    "werewolf",
    "husky",
    "canine",
    "wolftaur",
    "lupine",
)


def normalize(text: str) -> str:
    return re.sub(r"[^a-z0-9]+", "", text.lower())


def slugify(text: str) -> str:
    slug = re.sub(r"[^a-z0-9]+", "_", text.lower()).strip("_")
    return slug or "unnamed"


def classify_content_type(current_part: str, include_name: str) -> tuple[str, str | None]:
    part = current_part.lower()
    name = include_name.lower()

    if "locations" in part:
        return "location", "game/scripts/Content/Locations/{writer}/{name}.rpy"

    if "quests & events" in part:
        if "quest" in name or "quests" in name:
            return "quest", "game/scripts/Content/Quests/{writer}/{name}.rpy"
        return "event", "game/scripts/Content/Events/{writer}/{name}.rpy"

    if "monsters/infections" in part:
        infection_terms = (
            "infection",
            "infections",
            "forms",
            "form",
            "latex",
        )
        if any(term in name for term in infection_terms):
            return "infection", "game/scripts/Content/Infections/{writer}/{name}.rpy"
        return "enemy", "game/scripts/Content/Enemies/{writer}/{name}.rpy"

    if "npcs" in part:
        return "npc", "game/scripts/Content/NPCs/{writer}/{name}.rpy"

    if "companions and allies" in part:
        return "companion", "game/scripts/Content/Companions/{writer}/{name}.rpy"

    return "core", None


def is_wolf_related(include_name: str) -> bool:
    text = include_name.lower()
    return any(term in text for term in WOLF_TERMS)


def build_source_index(source_root: Path | None) -> dict[str, list[str]]:
    if source_root is None or not source_root.exists():
        return {}

    index: dict[str, list[str]] = {}
    for path in source_root.rglob("*"):
        if not path.is_file():
            continue
        key = normalize(path.stem)
        index.setdefault(key, []).append(path.as_posix())
    return index


def resolve_source_file(include_name: str, source_index: dict[str, list[str]]) -> list[str]:
    if not source_index:
        return []
    return source_index.get(normalize(include_name), [])


def render_target_path(template: str | None, writer: str, include_name: str) -> str | None:
    if template is None:
        return None
    return template.format(writer=writer, name=include_name)


def parse_story_file(story_file: Path, source_root: Path | None) -> list[dict[str, object]]:
    source_index = build_source_index(source_root)
    entries: list[dict[str, object]] = []

    current_book = ""
    current_part = ""

    for line_number, raw_line in enumerate(story_file.read_text(encoding="utf-8").splitlines(), start=1):
        line = raw_line.strip()
        if not line:
            continue

        heading_match = HEADING_RE.match(line)
        if heading_match:
            level, _, title = heading_match.groups()
            if level.lower() == "book":
                current_book = title.strip()
                current_part = ""
            else:
                current_part = title.strip()
            continue

        include_match = INCLUDE_RE.match(line)
        if not include_match:
            continue

        include_name = include_match.group(1).strip()
        writer_name = include_match.group(2).strip()

        content_type, target_template = classify_content_type(current_part, include_name)
        writer_slug = slugify(writer_name)
        include_slug = slugify(include_name)
        source_matches = resolve_source_file(include_name, source_index)

        entries.append(
            {
                "include_name": include_name,
                "writer_name": writer_name,
                "writer_slug": writer_slug,
                "include_slug": include_slug,
                "book": current_book,
                "part": current_part,
                "content_type": content_type,
                "wolf_related": is_wolf_related(include_name),
                "target_path": render_target_path(target_template, writer_slug, include_slug),
                "source_matches": source_matches,
                "source_found": bool(source_matches),
                "needs_manual_target": target_template is None,
                "story_line": line_number,
            }
        )

    return entries


def sort_entries(entries: list[dict[str, object]], wolf_first: bool) -> list[dict[str, object]]:
    def sort_key(entry: dict[str, object]) -> tuple[int, str, str]:
        wolf_rank = 0 if entry["wolf_related"] else 1
        return (
            wolf_rank if wolf_first else 0,
            str(entry["content_type"]),
            str(entry["include_name"]).lower(),
        )

    return sorted(entries, key=sort_key)


def write_json(entries: list[dict[str, object]], output_path: Path) -> None:
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_text(json.dumps(entries, indent=2), encoding="utf-8")


def write_markdown(
    entries: list[dict[str, object]],
    output_path: Path,
    story_file: Path,
    source_root: Path | None,
    wolf_first: bool,
) -> None:
    output_path.parent.mkdir(parents=True, exist_ok=True)

    counts = Counter(str(entry["content_type"]) for entry in entries)
    wolf_entries = [entry for entry in entries if entry["wolf_related"]]

    lines: list[str] = []
    lines.append("# Inform Source Manifest")
    lines.append("")
    lines.append(f"- Story file: `{story_file.as_posix()}`")
    lines.append(f"- Source root: `{source_root.as_posix()}`" if source_root else "- Source root: not set")
    lines.append(f"- Wolf-first ordering: `{'on' if wolf_first else 'off'}`")
    lines.append("")
    lines.append("## Counts")
    lines.append("")
    for content_type in sorted(counts):
        lines.append(f"- `{content_type}`: {counts[content_type]}")
    lines.append("")

    lines.append("## Wolf Queue")
    lines.append("")
    if wolf_entries:
        for entry in wolf_entries:
            target = entry["target_path"] or "manual target"
            lines.append(
                f"- `{entry['content_type']}`: {entry['include_name']} by {entry['writer_name']} -> `{target}`"
            )
    else:
        lines.append("- No wolf-related includes were detected with the current keyword rules.")
    lines.append("")

    lines.append("## Full Manifest")
    lines.append("")
    lines.append("| Type | Include | Writer | Part | Target | Source |")
    lines.append("|---|---|---|---|---|---|")
    for entry in entries:
        target = entry["target_path"] or "manual target"
        source = ", ".join(entry["source_matches"][:2]) if entry["source_matches"] else "not resolved"
        lines.append(
            f"| {entry['content_type']} | {entry['include_name']} | {entry['writer_name']} | {entry['part']} | `{target}` | `{source}` |"
        )
    lines.append("")

    output_path.write_text("\n".join(lines), encoding="utf-8")


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="Build a Flexible Survival Inform migration manifest.")
    parser.add_argument("--story-file", required=True, help="Path to story.ni or equivalent entry file.")
    parser.add_argument(
        "--source-root",
        help="Optional root directory that contains the included Inform source files.",
    )
    parser.add_argument(
        "--output-json",
        default="docs/inform_source_manifest.json",
        help="Where to write the JSON manifest.",
    )
    parser.add_argument(
        "--output-md",
        default="docs/inform_source_manifest.md",
        help="Where to write the Markdown manifest.",
    )
    parser.add_argument(
        "--wolf-first",
        action="store_true",
        help="Sort wolf-related content ahead of the rest.",
    )
    return parser


def main() -> int:
    parser = build_parser()
    args = parser.parse_args()

    story_file = Path(args.story_file)
    if not story_file.exists():
        parser.error(f"Story file does not exist: {story_file}")

    source_root = Path(args.source_root) if args.source_root else None
    entries = parse_story_file(story_file, source_root)
    entries = sort_entries(entries, wolf_first=args.wolf_first)

    write_json(entries, Path(args.output_json))
    write_markdown(entries, Path(args.output_md), story_file, source_root, args.wolf_first)
    print(f"Wrote {len(entries)} manifest entries.")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
