"""Plugin scaffolder.

Generates a new plugin skeleton from the templates under
shopdb/plugins/templates/. The generated plugin satisfies the framework
contract out of the box; tests/test_plugin_scaffold.py is the canary
that this stays true as the contract evolves.
"""

import re
from pathlib import Path
from string import Template
from typing import Optional


TEMPLATE_ROOT = Path(__file__).parent / 'templates'

VALID_NAME_PATTERN = re.compile(r'^[a-z][a-z0-9]*$')

RESERVED_NAMES = {
    'plugin', 'plugins', 'shopdb', 'core', 'api', 'tests', 'templates',
    'schemas', 'models', 'frontend', 'docs', 'migrations', 'scripts',
}


class ScaffoldError(Exception):
    """Raised when scaffolding cannot proceed."""


def validate_name(name: str) -> None:
    """Validate plugin name against CONTRIBUTING.md and reserved list.

    Raises ScaffoldError on any violation.
    """
    if not name:
        raise ScaffoldError('Plugin name is required')

    if not VALID_NAME_PATTERN.match(name):
        raise ScaffoldError(
            f'Plugin name "{name}" must be lowercase letters and digits only, '
            f'starting with a letter (no hyphens, underscores, or special chars). '
            f'See CONTRIBUTING.md for the naming convention.'
        )

    if name in RESERVED_NAMES:
        raise ScaffoldError(
            f'Plugin name "{name}" is reserved. Pick a different name.'
        )


def pascal_case(name: str) -> str:
    """Convert lowercase plugin name to PascalCase class name.

    'cameras' -> 'Cameras'. The convention assumes single-word lowercase
    plugin names per the naming rules in CONTRIBUTING.md, so this is
    just title-casing.
    """
    return name[:1].upper() + name[1:]


def scaffold_plugin(
    name: str,
    description: str,
    plugins_dir: Path,
    template_root: Optional[Path] = None,
    overwrite: bool = False,
) -> Path:
    """Generate a new plugin from templates.

    Args:
        name: Plugin name (lowercase, single word)
        description: One-sentence description for manifest.json + README
        plugins_dir: Target plugins directory (e.g., <repo>/plugins)
        template_root: Override template source dir (default: bundled templates)
        overwrite: If True, overwrite an existing plugin directory

    Returns:
        Path to the generated plugin directory.

    Raises:
        ScaffoldError on validation failure or when target exists and
        overwrite is False.
    """
    validate_name(name)

    template_root = template_root or TEMPLATE_ROOT
    if not template_root.exists():
        raise ScaffoldError(f'Template root not found: {template_root}')

    target = plugins_dir / name

    if target.exists():
        if not overwrite:
            raise ScaffoldError(
                f'Plugin directory already exists: {target}. '
                f'Pass overwrite=True or remove it first.'
            )

    substitutions = {
        'name': name,
        'Name': pascal_case(name),
        'description': description,
    }

    target.mkdir(parents=True, exist_ok=True)

    for template_path in template_root.rglob('*.tmpl'):
        rel = template_path.relative_to(template_root)

        out_rel_str = str(rel.with_suffix(''))
        if 'model.py' in out_rel_str:
            out_rel_str = out_rel_str.replace('model.py', f'{name}.py')

        out_path = target / out_rel_str
        out_path.parent.mkdir(parents=True, exist_ok=True)

        body = template_path.read_text()
        rendered = Template(body).safe_substitute(substitutions)

        out_path.write_text(rendered)

    return target
