#!/usr/bin/env bash
#
# name-check — gate de nomenclatura grep-friendly (ADR-004, plano 05 §3.1).
#
# Para cada módulo em src/modules, deriva o token canônico e exige que:
#   1. todo arquivo de código/Blade/migration/teste tenha o token NO NOME (algum estilo);
#   2. exista >=1 arquivo com o token em cada camada presente (Model/Action/View/Migration/Test).
#
# Arquivos de infraestrutura genéricos (provider/rotas/seeders) são isentos.
# Exceções justificadas:
#   - por arquivo:  comentar `@name-check-exception: <motivo>`
#   - por módulo:   comentar `@name-check-exception: <motivo>` no ModuleProvider/ServiceProvider
#
# Uso:
#   tools/name-check.sh [RAIZ]
#   tools/name-check.sh --grep <token>
set -Eeuo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if [ "${1:-}" != "" ] && [ "${1:-}" != "--grep" ]; then
    ROOT="$1"
fi
MODULES="$ROOT/src/modules"

[ -d "$MODULES" ] || { echo "ERRO: '$MODULES' não existe." >&2; exit 2; }

norm() { printf '%s' "$1" | tr 'A-Z' 'a-z' | sed 's/[ _-]//g'; }
# variantes: nome completo + singular (remove 's' final)
variants() {
    local n; n="$(norm "$1")"
    printf '%s\n' "$n"
    case "$n" in *s) printf '%s\n' "${n%s}" ;; esac
}
contains_token() { # $1=candidato $2..=variantes
    local cand; cand="$(norm "$1")"; shift
    local v; for v in "$@"; do case "$cand" in *"$v"*) return 0 ;; esac; done
    return 1
}
is_generic() { # basename isento (infra/convenção)
    case "$1" in
        ModuleProvider.php | ServiceProvider.php) return 0 ;;
        index.blade.php | show.blade.php | create.blade.php | edit.blade.php | form.blade.php | embed.blade.php | cover.blade.php | cover-preview.blade.php) return 0 ;;
        *) return 1 ;;
    esac
}

# ------------------------------------------------------------------ --grep --
if [ "${1:-}" = "--grep" ]; then
    term="${2:-}"
    [ -n "$term" ] || { echo "ERRO: use --grep <token>." >&2; exit 2; }
    declare -A layers=([Model]=0 [Action]=0 [View]=0 [Migration]=0 [Test]=0)
    while IFS= read -r f; do
        case "$f" in
            */Database/Models/*) layers[Model]=$((layers[Model] + 1)) ;;
            */Logic/Actions/*) layers[Action]=$((layers[Action] + 1)) ;;
            *.blade.php) layers[View]=$((layers[View] + 1)) ;;
            */Database/Migrations/*) layers[Migration]=$((layers[Migration] + 1)) ;;
            */Test/* | */Tests/*) layers[Test]=$((layers[Test] + 1)) ;;
        esac
    done < <(grep -rIl --exclude-dir=vendor --exclude-dir=node_modules "$term" "$ROOT/src" 2>/dev/null || true)
    # A camada só é exigida se a feature do token **tem** essa camada: módulo sem persistência
    # (ex.: `Home`) ou service sem banco não devem acusar falta — o token ainda alcança o que existe.
    layer_present() { # $1=dir relativo da camada
        local dir="$1" root name
        while IFS= read -r root; do
            name="$(basename "$root")"
            [ "$(norm "$name")" = "$(norm "$term")" ] || continue
            [ -d "$root/$dir" ] && return 0
        done < <(find "$ROOT/src" -type f \( -name ModuleProvider.php -o -name ServiceProvider.php \) -printf '%h\n' | sort -u)
        return 1
    }

    total=0
    for count in "${layers[@]}"; do total=$((total + count)); done

    # Token que não aparece em **nenhum** artefato é o erro central da ADR-004.
    [ "$total" -gt 0 ] || { echo "ERRO: nenhum artefato contém '$term'." >&2; exit 1; }

    rc=0
    for spec in "Model:Database/Models" "Action:Logic/Actions" "View:Layout/Views" "Migration:Database/Migrations" "Test:Test"; do
        layer="${spec%%:*}"; dir="${spec##*:}"
        printf '  %-10s %s\n' "$layer" "${layers[$layer]}"
        [ "${layers[$layer]}" -ge 1 ] && continue
        if layer_present "$dir"; then
            echo "ERRO: camada '$layer' sem '$term'." >&2; rc=1
        else
            echo "AVISO: '$term' não tem a camada '$layer' (feature sem $dir)." >&2
        fi
    done
    exit "$rc"
fi

mapfile -t module_roots < <(
    find "$MODULES" -type f \( -name ModuleProvider.php -o -name ServiceProvider.php \) -printf '%h\n' | sort -u
)

failures=0
exceptions=0

for root in "${module_roots[@]}"; do
    name="$(basename "$root")"
    mapfile -t vars < <(variants "$name")
    [ -n "${vars[0]}" ] || continue

    # Isenção do módulo inteiro (provider com @name-check-exception).
    if grep -q '@name-check-exception' "$root"/ModuleProvider.php "$root"/ServiceProvider.php 2>/dev/null; then
        echo "EXCEÇÃO justificada (módulo): $root"
        exceptions=$((exceptions + 1))
        continue
    fi

    # 1. Nome do arquivo contém o token (arquivos genéricos isentos).
    while IFS= read -r file; do
        base="$(basename "$file")"
        is_generic "$base" && continue
        rel="${file#"$root"/}"
        case "$rel" in Routes/* | routes/* | */Routes/* | */routes/*) continue ;; esac
        # Traduções: o arquivo é a **seção** da chave (`Lang/{locale}/index.php` → `token::index.title`),
        # exigência do resolvedor do Laravel; o token de domínio está no caminho (ADR-004/ADR-012).
        case "$rel" in Lang/* | */Lang/*) continue ;; esac
        if ! contains_token "$base" "${vars[@]}"; then
            if grep -q '@name-check-exception' "$file" 2>/dev/null; then
                exceptions=$((exceptions + 1))
                continue
            fi
            echo "ERRO: $file: nome não contém o token '$name'." >&2
            failures=$((failures + 1))
        fi
    done < <(find "$root" -type f \( -name '*.php' -o -name '*.blade.php' -o -name '*.js' -o -name '*.css' \) | sort)

    # 2. Camadas presentes precisam de >=1 arquivo com o token no nome.
    for spec in "Database/Models:Model" "Logic/Actions:Action" "Database/Migrations:Migration" "Test:Test" "Tests:Test"; do
        dir="${spec%%:*}"; label="${spec##*:}"
        [ -d "$root/$dir" ] || continue
        found=0
        while IFS= read -r f; do
            contains_token "$(basename "$f")" "${vars[@]}" && found=1
        done < <(find "$root/$dir" -type f | sort)
        [ "$found" -eq 1 ] || { echo "ERRO: $root/$dir: camada '$label' sem arquivo com o token '$name'." >&2; failures=$((failures + 1)); }
    done

    # Views (Blade): nomes convencionais (index/show/...) são aceitos; sem token => AVISO.
    if find "$root" -type f -name '*.blade.php' | grep -q .; then
        found=0
        while IFS= read -r f; do
            contains_token "$(basename "$f")" "${vars[@]}" && found=1
        done < <(find "$root" -type f -name '*.blade.php' | sort)
        [ "$found" -eq 1 ] || echo "AVISO: $root: nenhuma view com o token '$name' no nome (nomes convencionais ok)."
    fi
done

[ "$exceptions" -gt 0 ] && echo "EXCEÇÕES justificadas: $exceptions."

if [ "$failures" -gt 0 ]; then
    echo "name-check: $failures problema(s)." >&2
    exit 1
fi

echo "name-check: OK (${#module_roots[@]} módulo(s))"
exit 0
