#!/usr/bin/env python3
"""
Shared Validation Module for ELA Week Generators

Usage in your week generator:
    from validate import validate_week

    errors = validate_week(
        words=WORDS,
        word_names=WORD_NAMES,
        fill_data=fill_data,
        detective_words=detective_words,
        detective_answers=detective_answers,
        opposite_pairs=opposite_pairs,
        learning_examples=learning_examples,
        breakdowns=breakdowns,
        generate_friday=generate_friday,  # Pass the function for Friday check
    )

    if errors:
        for e in errors:
            print(f"  {e}")
        exit(1)
    print("[ok] All validation passed")
"""

import os
import re
import sys
import inspect

# Phonetic audit integration
_AUDIT_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "audit")
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))


def _phonetic_audit(words):
    """Run phonetic pattern audit on word list against verified database.

    Returns list of error strings. Empty = all passed.
    Integrates with the centralized audit system (word_db + validators).
    """
    errors = []

    try:
        from audit.word_db import get_word as lookup
        from audit.validators import validate_pattern
    except ImportError:
        return errors  # Skip if audit system unavailable

    for word, pattern, definition in words:
        # 1. Structural validation (pattern matches word structure)
        valid, msg = validate_pattern(word, pattern)
        if not valid:
            errors.append(f"PHONETIC: '{word}' — {msg}")
            continue

        # 2. Database verification (word exists in verified DB)
        db_entry = lookup(word)
        if not db_entry:
            errors.append(f"MISSING: '{word}' not in verified word database — add to audit/word_db.py")
            continue

        # 3. Pattern consistency (claimed pattern matches DB pattern)
        if db_entry.pattern != _normalize_pattern(pattern):
            errors.append(
                f"MISMATCH: '{word}' claims '{pattern}' but database has '{db_entry.pattern}'"
            )
            continue

    return errors


def _normalize_pattern(claimed):
    """Normalize a claimed pattern to database pattern code."""
    claimed = claimed.lower().strip()

    silent_e_map = {
        "long a (silent e)": "long_a_cvc e",
        "long e (silent e)": "long_e_cvc e",
        "long i (silent e)": "long_i_cvc e",
        "long o (silent e)": "long_o_cvc e",
        "long u (silent e)": "long_u_cvc e",
    }
    if claimed in silent_e_map:
        return silent_e_map[claimed]

    short_map = {
        "short a": "short_a", "short e": "short_e", "short i": "short_i",
        "short o": "short_o", "short u": "short_u",
    }
    if claimed in short_map:
        return short_map[claimed]

    team_map = {
        "long a (ai)": "long_a_ai", "long a (ay)": "long_a_ay",
        "long e (ee)": "long_e_ee", "long e (ea)": "long_e_ea",
        "long i (igh)": "long_i_igh", "long o (oa)": "long_o_oa",
        "long o (ow)": "long_o_ow", "long u (ew)": "long_u_ew",
        "long u (oo)": "long_u_oo", "long o (oe)": "long_o_oe",
        "long i (ee)": "long_i_ee",
    }
    if claimed in team_map:
        return team_map[claimed]

    digraph_map = {
        "sh sound (sh)": "digraph_sh", "ch sound (ch)": "digraph_ch",
        "th sound (th)": "digraph_th", "wh sound (wh)": "digraph_wh",
        "ph sound (ph)": "digraph_ph", "ck sound (ck)": "digraph_ck",
    }
    if claimed in digraph_map:
        return digraph_map[claimed]

    r_map = {
        "ar = /ar/": "r_control_ar", "or = /or/": "r_control_or",
        "er = /er/": "r_control_er", "ir = /er/": "r_control_ir",
        "ur = /er/": "r_control_ur",
    }
    if claimed in r_map:
        return r_map[claimed]

    # Try partial matches
    if "soft c" in claimed:
        if "ci" in claimed: return "soft_c_ci"
        if "ce" in claimed: return "soft_c_ce"
        if "cy" in claimed: return "soft_c_cy"
        if "ey" in claimed: return "soft_c_ey"
    if "soft g" in claimed:
        if "gi" in claimed: return "soft_g_gi"
        if "ge" in claimed: return "soft_g_ge"
        if "gy" in claimed: return "soft_g_gy"
        if "ey" in claimed: return "soft_g_ey"
    if "double" in claimed:
        return "double_consonant"
    if "irregular" in claimed:
        return "irregular"

    return claimed


def validate_word_length(words):
    """Check all words are 6+ letters."""
    errors = []
    for word, pattern, _ in words:
        if len(word) < 6:
            errors.append(f"Word '{word}' has {len(word)} letters (need 6+)")
    return errors


def validate_phonetic_patterns(words):
    """Check phonetic patterns match actual pronunciation."""
    errors = []
    pattern_checks = {
        "long a": lambda w: re.search(r'a.{0,3}e$|a.{0,1}[aeiou]', w, re.I),
        "long e": lambda w: re.search(r'e.{0,3}e$|ea|e.{0,1}[aeiou]', w, re.I),
        "long i": lambda w: re.search(r'i.{0,3}e$|i.{0,1}[aeiou]', w, re.I),
        "long o": lambda w: re.search(r'o.{0,3}e$|o.{0,1}[aeiou]', w, re.I),
    }

    for word, pattern, _ in words:
        for vowel_type, check_fn in pattern_checks.items():
            if vowel_type in pattern.lower():
                if not check_fn(word):
                    errors.append(f"'{word}' claims {vowel_type} but doesn't match pattern")
                break

    return errors


def validate_fill_data(fill_data, word_names):
    """Validate fill-in-blank answers are in word list."""
    errors = []
    for sentence, answer in fill_data:
        if answer not in word_names:
            errors.append(f"Fill-in answer '{answer}' not in word list")
        filled = sentence.replace("____", answer)
        if "the" not in filled.lower() and "i" not in filled.lower():
            errors.append(f"Fill-in sentence may be ungrammatical: {filled}")
    return errors


def validate_detective(detective_words, detective_answers, word_names):
    """Validate Spelling Detective misspellings."""
    errors = []
    if len(detective_words) != len(detective_answers):
        errors.append(f"Detective words ({len(detective_words)}) != answers ({len(detective_answers)})")
        return errors

    for i, (misspelled, original) in enumerate(zip(detective_words, detective_answers)):
        if misspelled == original:
            errors.append(f"Detective word {i+1}: '{misspelled}' is identical to original (must be misspelled)")
        if original not in word_names:
            errors.append(f"Detective answer '{original}' not in word list")
    return errors


def validate_opposites(opposite_pairs, word_names):
    """Validate opposite pairs reference words in word list."""
    errors = []
    for word, opp in opposite_pairs:
        if word not in word_names:
            errors.append(f"Opposite word '{word}' not in word list")
    return errors


def validate_learning_examples(learning_examples, word_names):
    """Validate learning examples are in word list."""
    errors = []
    for example in learning_examples:
        if example not in word_names:
            errors.append(f"Learning example '{example}' not in word list")
    return errors


def validate_breakdowns(breakdowns):
    """Validate letter breakdowns spell the correct words."""
    errors = []
    for word, breakdown in breakdowns.items():
        spelled = breakdown.replace("-", "").lower()
        if spelled != word.lower():
            errors.append(f"Breakdown '{breakdown}' spells '{spelled}', not '{word}'")
    return errors


def validate_friday_no_boxes(generate_friday):
    """Validate Friday has no learning/remember boxes."""
    errors = []
    try:
        friday_source = inspect.getsource(generate_friday)
        if "Here's What We're Learning" in friday_source:
            errors.append("Friday contains learning box (should be assessment only)")
        if "Remember This" in friday_source:
            errors.append("Friday contains remember box (should be assessment only)")
    except Exception:
        pass  # If we can't inspect, skip this check
    return errors


def validate_week(words, word_names, fill_data=None, detective_words=None,
                  detective_answers=None, opposite_pairs=None,
                  learning_examples=None, breakdowns=None,
                  generate_friday=None, phonetic_check=True):
    """
    Run all validation checks on a week's content.

    Args:
        words: List of (word, pattern, definition) tuples
        word_names: List of word strings
        fill_data: List of (sentence, answer) tuples for fill-in-blank
        detective_words: List of misspelled words
        detective_answers: List of correct spellings
        opposite_pairs: List of (word, opposite) tuples
        learning_examples: List of example words for "Here's What We're Learning"
        breakdowns: Dict of word -> "l-e-t-t-e-r" breakdown strings
        generate_friday: The Friday generator function (for box check)
        phonetic_check: Run phonetic audit against verified word database (default: True)

    Returns:
        List of error strings. Empty list = all passed.
    """
    all_errors = []

    # 0. Phonetic audit (runs first — catch pattern issues early)
    if phonetic_check:
        phonetic_errors = _phonetic_audit(words)
        all_errors.extend(phonetic_errors)

    # 1. Word length
    all_errors.extend(validate_word_length(words))

    # 2. Phonetic patterns
    all_errors.extend(validate_phonetic_patterns(words))

    # 3. Fill-in-blank
    if fill_data:
        all_errors.extend(validate_fill_data(fill_data, word_names))

    # 4. Spelling Detective
    if detective_words and detective_answers:
        all_errors.extend(validate_detective(detective_words, detective_answers, word_names))

    # 5. Opposite pairs
    if opposite_pairs:
        all_errors.extend(validate_opposites(opposite_pairs, word_names))

    # 6. Learning examples
    if learning_examples:
        all_errors.extend(validate_learning_examples(learning_examples, word_names))

    # 7. Letter breakdowns
    if breakdowns:
        all_errors.extend(validate_breakdowns(breakdowns))

    # 8. Friday no boxes
    if generate_friday:
        all_errors.extend(validate_friday_no_boxes(generate_friday))

    return all_errors


def report_validation(errors):
    """Pretty-print validation results."""
    if errors:
        print(f"\n❌ VALIDATION FAILED — Fix these errors before generating:")
        for i, error in enumerate(errors, 1):
            print(f"  {i}. {error}")
        return False
    else:
        print("[ok] All validation passed")
        return True
