import os
import re
import spacy
import pandas as pd
from collections import Counter
import sys
import sqlite3
from dotenv import load_dotenv

load_dotenv()

# --- CONFIGURATION ---
DIARY_FOLDER_PATH = os.environ["DIARY_FOLDER_PATH"]
DIARY_ENTRIES_REGEXP = "\\.txt"
SUMMARY_CSV = os.environ["SUMMARY_CSV"]
VOCAB_CSV = os.environ["VOCAB_CSV"]
ANKI_CSV = os.environ["ANKI_CSV"]
ANKI_SEPARATOR = "\t" # --- CHANGED: Using Tab (\t) for robust Anki import ---
ANKI_METADATA_SEP = " | " # --- NEW: Using Pipe for internal metadata separation ---
LEXIQUE_DB_PATH = os.environ["LEXIQUE_DB_PATH"]
JUST_WORDS = os.environ["JUST_WORDS"]

def get_lexique_ipa(word):
    """
    Queries the Lexique DB for the word's phonetics and converts custom
    characters to standard IPA, mirroring the Emacs Lisp logic.
    """
    # 1. Map custom Lexique characters to IPA (Direct translation of your ELisp pcase)
    ipa_map = {
        'O': 'ɔ',
        'E': 'ɛ',
        '°': 'ə',
        '2': 'ø',
        '9': 'œ',
        'S': 'ʃ',
        '5': 'ɛ̃',
        'Z': 'ʒ',
        '@': 'ɑ̃',
        '1': 'œ̃',
        '§': 'ɔ̃',
        '8': 'ɥ',
        'R': 'ʁ'
    }

    phon_raw = None

    # 2. Query the Database
    if os.path.exists(LEXIQUE_DB_PATH):
        try:
            conn = sqlite3.connect(LEXIQUE_DB_PATH)
            cursor = conn.cursor()
            # Order by freqfilms2 DESC to get the most common pronunciation
            cursor.execute(
                "SELECT syll FROM lexique WHERE ortho=? ORDER BY freqfilms2 DESC LIMIT 1",
                (word.lower(),)
            )
            result = cursor.fetchone()
            conn.close()

            if result:
                phon_raw = result[0]
        except sqlite3.Error as e:
            print(f"SQLite Error: {e}")

    if not phon_raw:
        return None

    # 3. Convert string using the map
    ipa_string = "".join(ipa_map.get(char, char) for char in phon_raw)
    return ipa_string

if len(sys.argv) >= 2:
    DATE = sys.argv[1]
else:
    DATE = None

if len(sys.argv) >= 3:
    END_DATE = sys.argv[2]
else:
    END_DATE = None

def clean_org_content(text):
    """Cleans Org Mode specific markup."""
    text = re.sub(r'#\+begin_comment.*?#\+end_comment', '', text, flags=re.DOTALL)
    text = re.sub(r'\d{4}-\d{2}-\d{2}-\d{2}', '', text)
    text = re.sub(r'\[\[.*?\]\[(.*?)\]\]', r'\1', text)
    text = re.sub(r'\[\[(.*?)\]\]', r'\1', text)
    text = re.sub(r'{(.*?)}', r'\1', text)
    return text

def get_verb_tense_tag(token):
    """
    Analyzes a token to determine its grammatical tense/mood.
    Returns 'N/A' if not a verb.
    """
    if token.pos_ not in ["VERB", "AUX"]:
        return "N/A"

    morph = str(token.morph)

    # Skip auxiliaries that are part of a compound tense (leur tête est un participe passé).
    # e.g. "a" in "a mangé" should not be counted as Présent — the participle handles it.
    if token.pos_ == "AUX" and token.dep_.startswith("aux") and "VerbForm=Part" in str(token.head.morph):
        return "N/A"

    # --- 0. CORRECTION BASÉE SUR LA RÈGLE GRAMMATICALE ---
    # Détecter la subordination au subjonctif.
    # Nous vérifions si le verbe est la tête d'une clause subordonnée
    # et si le mot précédent est un marqueur (mark) comme 'que'.

    # 1. Vérifier si un 'mark' (comme 'que') dépend de ce verbe.
    is_marked_subordinate = any(child.dep_ == "mark" for child in token.children)

    # 2. Vérifier si le verbe suit immédiatement un 'que'
    # (moins précis, mais utile si le verbe est le 'root' de la subordonnée).
    if token.i > 0 and token.n_lefts > 0: # Ensure token has left children
         left_children = list(token.children)
         if left_children and left_children[0].dep_ == "mark" and left_children[0].lower_ == 'que':
            is_marked_subordinate = True

    # Si le modèle a fait une erreur (Ind ou Cnd) mais que le contexte est 'afin que/pour que',
    # nous corrigeons pour Subjonctif.
    if is_marked_subordinate and token.lemma_ in ["pouvoir", "faire", "savoir", "vouloir", "aller", "être", "avoir"]:
        # Si le modèle a donné Indicatif (Mood=Ind) ou Conditionnel (Mood=Cnd), on le force.
        if "Mood=Ind" in morph or "Mood=Cnd" in morph:
            return "Subjonctif Corrigé (Règle)"

    # --- 1. TENSES COMPOSÉS (Context Dependent) ---

    # Passé Composé Check
    # The fr_dep_news_trf model uses "aux:tense" (not "aux") for passé composé auxiliaries,
    # so we check startswith("aux") to catch aux, aux:tense, aux:pass, etc.
    children_deps = [child.dep_ for child in token.children]
    if token.pos_ == "VERB" and "VerbForm=Part" in morph and any(dep.startswith("aux") for dep in children_deps):
        return "Passé Composé"

    # Futur Proche Check (inchangé)
    if token.lemma_ == "aller":
        for child in token.children:
            if child.dep_ == "xcomp" and "VerbForm=Inf" in str(child.morph):
                return "Futur Proche"

    # --- 2. TENSES SIMPLES (Morphology Dependent) ---

    # La priorité est inversée: on détecte maintenant le Subjonctif/Conditionnel en premier
    # avant le Présent/Imparfait, car le modèle est plus précis sur le Mood.

    # Subjonctif
    if "Mood=Sub" in morph:
        if "Tense=Imp" in morph: return "Subjonctif Imparfait"
        return "Subjonctif" # Subjonctif Présent

    # Conditionnel
    if "Mood=Cnd" in morph:
        return "Conditionnel"

    # Futur Simple
    if "Tense=Fut" in morph:
        return "Futur Simple"

    # Imparfait
    if "Tense=Imp" in morph:
        return "Imparfait"

    # Présent
    if "Tense=Pres" in morph:
        return "Présent"

    # Infinitif
    if "VerbForm=Inf" in morph:
        return "Infinitif"

    # Impératif
    if "Mood=Imp" in morph:
        return "Impératif"

    return "Autre/Participe"

def create_anki_card_data(sentence, new_tokens_info, date_tag):
    """
    Creates the cloze-formatted string and metadata for an Anki card.
    Metadata now includes the word and its IPA pronunciation.
    """

    # Sort new tokens by their index in the sentence to ensure proper cloze ordering
    new_tokens_info.sort(key=lambda x: x[0].i)

    # 1. Create the base sentence text
    cloze_sentence = sentence.text

    # 2. Iterate through tokens to apply cloze deletion
    cloze_index = 1
    metadata_string = ""
    replacements = {}

    for token, lemma, pos, tense in new_tokens_info:
        # Check if the token is part of the current sentence span
        if token.sent == sentence:
            token_start = token.idx - sentence.start_char
            token_end = token.idx + len(token.text) - sentence.start_char

            # Add the replacement instruction
            replacements[(token_start, token_end)] = f"{{{{c{cloze_index}::{token.text}}}}}"

            # --- NEW METADATA LOGIC ---
            # Lookup IPA for the surface form of the word (token.text)
            ipa = get_lexique_ipa(token.text)

            # Format: **lemma** (word) /ipa/
            # Use ' | ' separator if multiple words are clozed in one sentence
            if metadata_string:
                metadata_string += ANKI_METADATA_SEP

            pronunciation_part = f"/{ipa}/" if ipa else ""
            metadata_string += f"<strong>{token.text}</strong> {pronunciation_part} ({lemma})"

            cloze_index += 1

    # Apply replacements in reverse order of index
    sorted_indices = sorted(replacements.keys(), key=lambda x: x[0], reverse=True)

    for start, end in sorted_indices:
        cloze_sentence = cloze_sentence[:start] + replacements[(start, end)] + cloze_sentence[end:]

    # Final Anki line format: Field 1 \t Field 2 \t Field 3
    final_line = f"{cloze_sentence}{ANKI_SEPARATOR}{metadata_string}{ANKI_SEPARATOR}french-vocab {date_tag}"

    return final_line

def analyze_entries():
    model = "fr_dep_news_trf"
    print("Loading French language model (this may take a moment)...")
    try:
        nlp = spacy.load(model)
    except OSError:
        print("Error: Run 'python -m spacy download %s'" % model)
        return

    files = sorted([f for f in os.listdir(DIARY_FOLDER_PATH) if re.search(DIARY_ENTRIES_REGEXP, f)])

    if not files:
        print("No files found!")
        return

    known_lemmas = set()
    summary_log = []
    vocab_log = []
    anki_log = []

    cumulative_words = 0
    print(f"Found {len(files)} entries. Analyzing grammar...\n")

    for i, filename in enumerate(files):
        print("Analyzing %s" % filename, file=sys.stderr)
        filepath = os.path.join(DIARY_FOLDER_PATH, filename)
        date_tag = filename[:10] # e.g., '2025-12-15'
        with open(filepath, "r", encoding="utf-8") as f:
            raw_text = f.read()

        clean_text = clean_org_content(raw_text)
        doc = nlp(clean_text)
        tokens = [token for token in doc if token.is_alpha]

        # Tracking for this specific file
        file_lemmas = set()
        file_tokens = set()
        tense_counter = Counter()

        # Tracking new lemmas to create cards
        new_lemmas_to_cloze = {} # {lemma: {token:..., pos:..., tense:..., sentence:...}}

        # Iterate through tokens to get POS, Tense, and find new lemmas
        for token in tokens:
            if token.pos_ is None or token.pos_ == "PROPN":
                continue
            t_lemma = token.lemma_.lower()
            # DETECT TENSE (only for VERB/AUX)
            tense_tag = "N/A"
            if token.pos_ in ["VERB", "AUX"]:
                tense_tag = get_verb_tense_tag(token)
                tense_counter[tense_tag] += 1

            # Check for NEW LEMMA and log the FIRST instance
            if t_lemma not in known_lemmas and t_lemma not in new_lemmas_to_cloze:
                # Store the token object and its full context info
                new_lemmas_to_cloze[t_lemma] = {
                    'token': token,
                    'pos': token.pos_,
                    'tense': tense_tag,
                    'sentence': token.sent # The sentence object
                }

            # LOG WORD for VOCAB_CSV
            vocab_log.append({
                "Date": date_tag,
                "Original Term": token.text.lower(),
                "Base Word (Lemma)": t_lemma,
                "POS": token.pos_,
                "Tense/Mood": tense_tag
            })

            file_lemmas.add(t_lemma)
            file_tokens.add(token.text.lower())

        # ANKI CARD CREATION LOGIC
        sentences_to_cloze = {} # {sentence_obj: [ (token_obj, lemma, pos, tense), ... ]}

        for lemma, info in new_lemmas_to_cloze.items():
            sentence = info['sentence']
            token = info['token']
            pos = info['pos']
            tense = info['tense']

            if sentence not in sentences_to_cloze:
                sentences_to_cloze[sentence] = []

            # Store the data required for cloze creation
            sentences_to_cloze[sentence].append((token, lemma, pos, tense))

        # Create Anki card for each unique sentence that contains new lemmas
        if (DATE is None or date_tag >= DATE) and (END_DATE is None or date_tag < END_DATE):
            for sentence, new_tokens_info in sentences_to_cloze.items():
                anki_card_line = create_anki_card_data(sentence, new_tokens_info, date_tag)
                anki_log.append(anki_card_line)

        # LOG SUMMARY
        new_lemmas = file_lemmas - known_lemmas

        # Create comma-separated list, but skip it for the very first file
        if i == 0:
            new_lemmas_str = ""
        else:
            new_lemmas_str = ", ".join(sorted(new_lemmas))

        known_lemmas.update(new_lemmas)
        cumulative_words += len(tokens)

        summary_data = {
            "Date": date_tag,
            "Total Word Count": len(tokens),
            "Cumulative Word Count": cumulative_words,
            "Unique Lemmas": len(file_lemmas),
            "New Lemmas": len(new_lemmas),
            "Cumulative Vocab": len(known_lemmas),
            "Count: Présent": tense_counter["Présent"],
            "Count: Passé Composé": tense_counter["Passé Composé"],
            "Count: Imparfait": tense_counter["Imparfait"],
            "Count: Futur Proche": tense_counter["Futur Proche"],
            "Count: Futur Simple": tense_counter["Futur Simple"],
            "Count: Subjonctif": tense_counter["Subjonctif"],
            "Count: Subjonctif Corrigé (Règle)": tense_counter["Subjonctif Corrigé (Règle)"],
            "Count: Conditionnel": tense_counter["Conditionnel"],
            "New Lemmas List": new_lemmas_str
        }
        summary_log.append(summary_data)

    # EXPORT
    pd.DataFrame(summary_log).to_csv(SUMMARY_CSV, index=False)
    pd.DataFrame(vocab_log).to_csv(VOCAB_CSV, index=False)
    with open(JUST_WORDS, 'w', encoding='utf-8') as f:
        f.write('\n'.join(sorted(known_lemmas)))

    # ANKI EXPORT
    print(f"3. Anki Cloze Data: {ANKI_CSV}")
    with open(ANKI_CSV, 'w', encoding='utf-8') as f:
        f.write(f"\n".join(anki_log)) # The log is already tab-separated

    pd.set_option('display.width', None)
    print("-" * 30)
    print(f"Analysis Complete. Processed {len(files)} files.")
    print(f"1. Summary Data: {SUMMARY_CSV}")
    print(f"2. Vocabulary & Tense Data: {VOCAB_CSV}")
    print(f"3. Anki Cloze Card Data: {ANKI_CSV} (Tab separated)")
    print("-" * 30)
    print(pd.DataFrame(summary_log)[['Date', 'New Lemmas', 'Total Word Count', 'Cumulative Vocab', 'New Lemmas List']].tail(21).iloc[::-1])
    print('Avg new lemmas per entry: ', summary_log[-1]['Cumulative Vocab'] * 1.0 / len(summary_log))
    print('Average word count per entry: ', pd.DataFrame(summary_log)['Total Word Count'].sum() / len(summary_log))
    print('Total word count: ', pd.DataFrame(summary_log)['Total Word Count'].sum())
if __name__ == "__main__":
    analyze_entries()
