#!/usr/bin/env python3
"""Phase 2: Strip names from definition forms in one-per-file .sx files.

Transforms:
  (defcomp ~name (params) body)  ->  (defcomp (params) body)
  (define name value)            ->  (define value)
  (define (name args) body)      ->  (define (args) body)
  (defpage name :path ...)       ->  (defpage :path ...)
  etc.

Self-named components (where the name is part of the content) are left as-is
if they use a different naming convention than the file path would give.

Usage:
    python3 scripts/strip_names.py --dry-run   # preview
    python3 scripts/strip_names.py              # execute
"""
import os
import sys
import re
import argparse

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from shared.sx.parser import parse_all
from shared.sx.types import Symbol, Keyword

NAMED_DEFS = {"defcomp", "defisland", "defmacro", "defpage",
              "defhandler", "defstyle", "deftype", "defeffect",
              "defrelation", "deftest"}

SKIP_FILES = {"boundary.sx", "_init.sx", "_preamble.sx"}


def strip_name_from_source(source):
    """Strip the name from a definition in raw source text.

    Returns (new_source, old_name, def_type) or None if no change needed.

    Works on raw text to preserve formatting, comments, etc.
    Uses the parser to understand structure, then does targeted text surgery.
    """
    try:
        exprs = parse_all(source)
    except Exception:
        return None

    if not exprs:
        return None

    expr = exprs[0]
    if not isinstance(expr, list) or not expr or not isinstance(expr[0], Symbol):
        return None

    kw = expr[0].name

    if kw in NAMED_DEFS:
        if len(expr) < 2 or not isinstance(expr[1], Symbol):
            return None  # Already unnamed or malformed
        old_name = expr[1].name

        # Find and remove the name symbol from source text.
        # Pattern: (defXXX ~name  or  (defXXX name
        # We need to find the name token after the keyword and remove it.
        pattern = re.compile(
            r'(\(\s*' + re.escape(kw) + r')\s+' + re.escape(old_name) + r'(?=\s)',
            re.DOTALL
        )
        match = pattern.search(source)
        if match:
            new_source = source[:match.end(1)] + source[match.end():]
            return (new_source, old_name, kw)
        return None

    if kw == "define":
        if len(expr) < 2:
            return None
        name_part = expr[1]

        if isinstance(name_part, Symbol):
            old_name = name_part.name
            # (define name value) -> (define value)
            pattern = re.compile(
                r'(\(\s*define)\s+' + re.escape(old_name) + r'(?=\s)',
                re.DOTALL
            )
            match = pattern.search(source)
            if match:
                new_source = source[:match.end(1)] + source[match.end():]
                return (new_source, old_name, "define")

        elif (isinstance(name_part, list) and name_part
              and isinstance(name_part[0], Symbol)):
            old_name = name_part[0].name
            # (define (name args...) body) -> (define (args...) body)
            # Find (define (name and remove just "name "
            pattern = re.compile(
                r'(\(\s*define\s+\()\s*' + re.escape(old_name) + r'\s*',
                re.DOTALL
            )
            match = pattern.search(source)
            if match:
                new_source = source[:match.end(1)] + source[match.end():]
                return (new_source, old_name, "define")

        return None

    return None


def main():
    parser = argparse.ArgumentParser(description="Strip names from definitions")
    parser.add_argument("--dry-run", action="store_true")
    parser.add_argument("--dir", default=None)
    args = parser.parse_args()

    project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    os.chdir(project_root)

    dirs = [args.dir] if args.dir else ["sx/sx", "sx/sxc"]

    stripped = 0
    skipped = 0
    errors = []

    for d in dirs:
        if not os.path.isdir(d):
            continue

        for root, subdirs, files in os.walk(d):
            subdirs[:] = [x for x in subdirs if x not in ('__pycache__', '.cache')]
            for filename in sorted(files):
                if not filename.endswith('.sx') or filename in SKIP_FILES:
                    continue

                filepath = os.path.join(root, filename)

                try:
                    with open(filepath, encoding='utf-8') as f:
                        source = f.read()
                except Exception as e:
                    errors.append((filepath, str(e)))
                    continue

                result = strip_name_from_source(source)
                if result is None:
                    skipped += 1
                    continue

                new_source, old_name, def_type = result

                if args.dry_run:
                    print(f"  {filepath}: strip {def_type} {old_name}")
                    stripped += 1
                    continue

                with open(filepath, 'w', encoding='utf-8') as f:
                    f.write(new_source)
                stripped += 1

    print(f"\n{'Dry run — would strip' if args.dry_run else 'Stripped'} {stripped} names")
    print(f"Skipped {skipped} (already unnamed or no definition)")
    if errors:
        print(f"Errors: {len(errors)}")
        for fp, e in errors:
            print(f"  {fp}: {e}")


if __name__ == "__main__":
    main()
