#!/usr/bin/env python3
"""
linedance_tag_analyse.py

Scanner en mappe med MP3-filer og analyserer om de følger mønsteret:
  TIT2  = dansens navn
  TALB  = sangens rigtige titel
  TCON  = niveau

Verificerer via MusicBrainz API og gemmer resultat i CSV til gennemgang.

Brug:
  python linedance_tag_analyse.py /sti/til/musik [--output rapport.csv] [--apply godkendt.csv]
"""

import os
import sys
import csv
import json
import time
import struct
import argparse
import subprocess
import urllib.request
import urllib.parse
from pathlib import Path

# ── Konfiguration ──────────────────────────────────────────────────────────────
ACOUSTID_API_KEY  = "6fd9DGNDqG"   # Erstat med din egen
ACOUSTID_API_URL  = "https://api.acoustid.org/v2/lookup"
MUSICBRAINZ_URL   = "https://musicbrainz.org/ws/2/recording"
MB_USER_AGENT     = "LineDanceTagFixer/1.0 (dit@email.dk)"  # Skal udfyldes
API_DELAY         = 1.1             # Sek mellem MusicBrainz-kald (max 1/sek)
ACOUSTID_DELAY    = 0.4
MATCH_THRESHOLD   = 0.80            # Mindste lighed for "sikker" match
FPCALC_PATH       = "fpcalc"       # Eller fuld sti f.eks. C:\Tools\fpcalc.exe

# ── Hjælpefunktioner ──────────────────────────────────────────────────────────

def normalize(s: str) -> str:
    """Normaliser streng til sammenligning — lowercase, fjern specialtegn."""
    import unicodedata, re
    s = unicodedata.normalize("NFKD", s or "")
    s = s.encode("ascii", "ignore").decode()
    s = re.sub(r"[^a-z0-9 ]", "", s.lower())
    s = re.sub(r"\s+", " ", s).strip()
    return s

def similarity(a: str, b: str) -> float:
    """Simpel tegnbaseret lighed 0-1."""
    a, b = normalize(a), normalize(b)
    if not a or not b:
        return 0.0
    if a == b:
        return 1.0
    # Longest common subsequence approximation via set overlap
    set_a = set(a.split())
    set_b = set(b.split())
    if not set_a or not set_b:
        return 0.0
    overlap = len(set_a & set_b)
    return overlap / max(len(set_a), len(set_b))

# ── Niveau-normalisering ──────────────────────────────────────────────────────
# Officielle niveauer fra Linedancer Guide to Level Definitions (2017):
#   Absolute Beginner → Beginner → Improver → Intermediate → Advanced
#
# Vi gemmer dem på engelsk da det er den internationale standard.
# Mapper alle kendte varianter til officiel betegnelse.

NIVEAU_MAP = {
    # ── Absolute Beginner ──
    "absolute beginner":    "Absolute Beginner",
    "abs. beginner":        "Absolute Beginner",
    "abs beginner":         "Absolute Beginner",
    "absolute beg":         "Absolute Beginner",
    "ab":                   "Absolute Beginner",
    "absolut begynder":     "Absolute Beginner",

    # ── Beginner ──
    "beginner":             "Beginner",
    "beg":                  "Beginner",
    "begin":                "Beginner",
    "begynder":             "Beginner",
    "newbie":               "Beginner",
    "basic":                "Beginner",

    # ── High Beginner ──
    "high beginner":        "High Beginner",
    "high beg":             "High Beginner",

    # ── Low Improver ──
    "low improver":         "Low Improver",
    "low imp":              "Low Improver",
    "beginner/improver":    "Low Improver",
    "beg/imp":              "Low Improver",
    "beg/improver":         "Low Improver",

    # ── Improver ──
    "improver":             "Improver",
    "imp":                  "Improver",
    "easy":                 "Improver",
    "easy intermediate":    "Improver",
    "easy/intermediate":    "Improver",
    "easy inter":           "Improver",
    "let øvet":             "Improver",
    "let":                  "Improver",

    # ── High Improver ──
    "high improver":        "High Improver",
    "high imp":             "High Improver",
    "improver/intermediate": "High Improver",
    "imp/int":              "High Improver",

    # ── Low Intermediate ──
    "low intermediate":     "Low Intermediate",
    "low inter":            "Low Intermediate",
    "low int":              "Low Intermediate",

    # ── Intermediate ──
    "intermediate":         "Intermediate",
    "inter":                "Intermediate",
    "int":                  "Intermediate",
    "øvet":                 "Intermediate",

    # ── High Intermediate ──
    "high intermediate":    "High Intermediate",
    "high inter":           "High Intermediate",
    "high int":             "High Intermediate",
    "intermediate/advanced": "High Intermediate",
    "int/adv":              "High Intermediate",

    # ── Advanced ──
    "advanced":             "Advanced",
    "adv":                  "Advanced",
    "videregående":         "Advanced",
}

def normaliser_niveau(raw: str) -> tuple[str, bool]:
    """
    Returner (normaliseret_niveau, fundet).
    fundet=True hvis vi genkender niveauet.
    """
    if not raw:
        return "", False
    key = raw.strip().lower()
    if key in NIVEAU_MAP:
        return NIVEAU_MAP[key], True
    # Delvis match — find længste nøgle der er indeholdt
    best_match = ""
    best_len = 0
    for k, v in NIVEAU_MAP.items():
        if k in key and len(k) > best_len:
            best_match = v
            best_len = len(k)
    if best_match:
        return best_match, True
    return raw.strip(), False



def read_id3(path: str) -> dict:
    """Læs relevante ID3v2 tags fra MP3 uden eksterne biblioteker."""
    result = {
        "tit2": "", "tpe1": "", "talb": "", "tcon": "",
        "mbid": "", "linedance_dances": []
    }
    try:
        with open(path, "rb") as f:
            data = f.read(65536)

        if data[:3] != b"ID3":
            return result

        major = data[3]
        tag_size = ((data[6]&0x7f)<<21)|((data[7]&0x7f)<<14)|((data[8]&0x7f)<<7)|(data[9]&0x7f)
        pos = 10

        while pos < min(tag_size + 10, len(data) - 10):
            if major == 3:
                fid = data[pos:pos+4].decode("latin1", errors="replace")
                fsize = struct.unpack(">I", data[pos+4:pos+8])[0]
                pos += 10
            else:  # v2.4
                fid = data[pos:pos+4].decode("latin1", errors="replace")
                fsize = struct.unpack(">I", data[pos+4:pos+8])[0]
                pos += 10

            if fid == "\x00\x00\x00\x00" or fsize <= 0 or fsize > 100000:
                break

            content = data[pos:pos+fsize]
            pos += fsize

            try:
                if fid.startswith("T") and len(content) > 1:
                    enc = content[0]
                    raw = content[1:]
                    if enc in (1, 2):
                        txt = raw.decode("utf-16", errors="replace")
                    elif enc == 3:
                        txt = raw.decode("utf-8", errors="replace")
                    else:
                        txt = raw.decode("latin1", errors="replace")
                    txt = txt.strip("\x00").strip()

                    if fid == "TIT2":
                        result["tit2"] = txt
                    elif fid == "TPE1":
                        result["tpe1"] = txt
                    elif fid == "TALB":
                        result["talb"] = txt
                    elif fid == "TCON":
                        result["tcon"] = txt
                    elif fid == "TXXX":
                        # Format: desc\x00value
                        parts = txt.split("\x00", 1)
                        if len(parts) == 2:
                            desc, val = parts
                        else:
                            desc, val = txt, ""
                        desc_clean = desc.strip()
                        val_clean = val.strip()

                        if desc_clean in ("MusicBrainz Recording Id", "MusicBrainz Track Id"):
                            result["mbid"] = val_clean
                        elif desc_clean.startswith("LINEDANCE_DANCE_"):
                            result["linedance_dances"].append(val_clean)

                elif fid == "UFID" and len(content) > 4:
                    # UFID: owner\x00data
                    null = content.find(b"\x00")
                    if null >= 0:
                        owner = content[:null].decode("latin1", errors="replace")
                        if "musicbrainz" in owner.lower():
                            result["mbid"] = content[null+1:].decode("utf-8", errors="replace").strip("\x00")

                elif fid == "COMM" and len(content) > 4:
                    enc = content[0]
                    raw = content[4:]
                    if enc in (1, 2):
                        txt = raw.decode("utf-16", errors="replace")
                    else:
                        txt = raw.decode("utf-8", errors="replace")
                    result["comm"] = txt.strip("\x00").strip()

            except Exception:
                pass

    except Exception as e:
        result["error"] = str(e)

    return result


# ── AcoustID fingerprinting ────────────────────────────────────────────────────

def fingerprint_file(path: str) -> tuple[str, int] | None:
    """Kør fpcalc og returner (fingerprint, duration)."""
    fpcalc = find_fpcalc()
    if not fpcalc:
        return None
    try:
        r = subprocess.run(
            [fpcalc, "-json", path],
            capture_output=True, text=True, timeout=30
        )
        if r.returncode != 0:
            return None
        d = json.loads(r.stdout)
        fp = d.get("fingerprint")
        dur = int(d.get("duration", 0))
        if fp and dur:
            return fp, dur
        return None
    except FileNotFoundError:
        return None
    except Exception:
        return None


def find_fpcalc() -> str | None:
    """Find fpcalc på systemet — returner sti eller None."""
    import shutil
    # Prøv konfigureret sti først
    if shutil.which(FPCALC_PATH):
        return FPCALC_PATH
    # Prøv kendte Windows-stier
    windows_paths = [
        r"C:\Program Files\fpcalc\fpcalc.exe",
        r"C:\Tools\fpcalc.exe",
        r"C:\fpcalc.exe",
        os.path.join(os.path.dirname(__file__), "fpcalc.exe"),
        os.path.join(os.path.dirname(__file__), "fpcalc"),
    ]
    for p in windows_paths:
        if os.path.isfile(p):
            return p
    return None


def lookup_acoustid(fingerprint: str, duration: int) -> str | None:
    """Slå fingerprint op i AcoustID og returner MBID."""
    try:
        params = urllib.parse.urlencode({
            "client":      ACOUSTID_API_KEY,
            "fingerprint": fingerprint,
            "duration":    duration,
            "meta":        "recordings",
        })
        req = urllib.request.Request(
            f"{ACOUSTID_API_URL}?{params}",
            headers={"User-Agent": MB_USER_AGENT}
        )
        with urllib.request.urlopen(req, timeout=10) as resp:
            data = json.loads(resp.read())

        if data.get("status") != "ok":
            return None

        results = data.get("results", [])
        if not results:
            return None

        best = max(results, key=lambda r: r.get("score", 0))
        if best.get("score", 0) < 0.85:
            return None

        recordings = best.get("recordings", [])
        return recordings[0].get("id") if recordings else None

    except Exception:
        return None


# ── MusicBrainz opslag ────────────────────────────────────────────────────────

def lookup_musicbrainz(mbid: str) -> dict | None:
    """Slå MBID op i MusicBrainz og returner titel + kunstner."""
    try:
        url = f"{MUSICBRAINZ_URL}/{mbid}?fmt=json&inc=artists"
        req = urllib.request.Request(url, headers={"User-Agent": MB_USER_AGENT})
        with urllib.request.urlopen(req, timeout=10) as resp:
            data = json.loads(resp.read())

        title = data.get("title", "")
        artists = data.get("artist-credit", [])
        artist = ""
        if artists:
            parts = []
            for a in artists:
                if isinstance(a, dict) and "artist" in a:
                    parts.append(a["artist"].get("name", ""))
                elif isinstance(a, str):
                    parts.append(a)
            artist = "".join(parts)

        return {"title": title, "artist": artist} if title else None

    except Exception:
        return None


# ── Hoved-analyse ─────────────────────────────────────────────────────────────

def analyse_file(path: str, use_acoustid: bool = True) -> dict:
    """
    Analyser én fil og returner en dict med resultat.
    Status: SIKKER / SANDSYNLIG / USIKKER / UKENDT
    """
    import re

    # ── Værdier der ligner niveau men er genre/andet ──────────────────────────
    IKKE_NIVEAU = {
        "country", "rock", "pop", "jazz", "blues", "latin", "swing",
        "line dance", "linedance", "general country", "waltz", "cha cha",
        "rumba", "tango", "samba", "foxtrot", "quickstep", "two step",
        "west coast swing", "east coast swing",
    }

    def dans_fra_filnavn(filename):
        """Forsøg at udtrække dansenavn fra filnavn."""
        navn = os.path.splitext(filename)[0]
        # Mønster 1: starter med (dans) eller [dans]
        m = re.match(r"^[\(\[](.*?)[\)\]]", navn)
        if m:
            kandidat = m.group(1).strip()
            if len(kandidat) > 2 and not kandidat.isdigit():
                return kandidat
        # Mønster 2: slutter med (dans)
        m = re.search(r"[\(\[](.*?)[\)\]]\s*$", navn)
        if m:
            kandidat = m.group(1).strip()
            if len(kandidat) > 2 and not kandidat.isdigit():
                return kandidat
        return ""

    result = {
        "fil":                path,
        "filename":           os.path.basename(path),
        "tit2":               "",
        "tpe1":               "",
        "talb":               "",
        "tcon":               "",
        "mbid":               "",
        "mb_title":           "",
        "mb_artist":          "",
        "dans_forslag":       "",
        "niveau_rå":          "",
        "niveau_forslag":     "",
        "niveau_genkendt":    "",
        "rigtig_titel":       "",
        "rigtig_artist":      "",
        "match_score":        0.0,
        "status":             "UKENDT",
        "noter":              "",
        "handling":           "",
    }

    # 1. Læs tags
    tags = read_id3(path)
    result["tit2"]  = tags.get("tit2", "")
    result["tpe1"]  = tags.get("tpe1", "")
    result["talb"]  = tags.get("talb", "")
    result["tcon"]  = tags.get("tcon", "")
    result["mbid"]  = tags.get("mbid", "")
    ld_dances       = tags.get("linedance_dances", [])

    noter = []

    # Normaliser niveau fra TCON — filtrer genre-værdier fra
    niveau_rå = result["tcon"]
    if niveau_rå.strip().lower() in IKKE_NIVEAU:
        niveau_normaliseret, niveau_genkendt = "", False
        noter.append(f"TCON er genre, ikke niveau: '{niveau_rå}'")
    else:
        niveau_normaliseret, niveau_genkendt = normaliser_niveau(niveau_rå)
    result["niveau_rå"]       = niveau_rå
    result["niveau_forslag"]  = niveau_normaliseret
    result["niveau_genkendt"] = "JA" if niveau_genkendt else ("NEJ" if niveau_rå else "")

    if niveau_rå and not niveau_genkendt:
        noter.append(f"Ukendt niveau-værdi: '{niveau_rå}'")

    if tags.get("error"):
        result["status"] = "FEJL"
        result["noter"]  = tags["error"]
        return result

    # 2. Hent MBID hvis mangler
    mbid = result["mbid"]
    if not mbid and use_acoustid:
        fp = fingerprint_file(path)
        if fp:
            fingerprint, duration = fp
            time.sleep(ACOUSTID_DELAY)
            mbid = lookup_acoustid(fingerprint, duration)
            if mbid:
                result["mbid"] = mbid
                noter.append("MBID fundet via AcoustID")
            else:
                noter.append("AcoustID: ingen match")
        else:
            noter.append("fpcalc fejlede")

    # 3. MusicBrainz opslag
    mb = None
    if mbid:
        time.sleep(API_DELAY)
        mb = lookup_musicbrainz(mbid)
        if mb:
            result["mb_title"]  = mb["title"]
            result["mb_artist"] = mb["artist"]
        else:
            noter.append("MusicBrainz: ingen data for MBID")

    # 4. Vurder mønster
    tit2 = result["tit2"]
    talb = result["talb"]
    tpe1 = result["tpe1"]

    if mb:
        # Sammenlign TALB med MusicBrainz titel
        score_title  = similarity(talb, mb["title"])
        score_artist = similarity(tpe1, mb["artist"])
        result["match_score"] = round((score_title + score_artist) / 2, 2)

        if score_title >= MATCH_THRESHOLD:
            # TALB matcher rigtig titel → TIT2 er sandsynligvis dansen
            result["dans_forslag"]   = tit2
            result["rigtig_titel"]   = mb["title"]
            result["rigtig_artist"]  = mb["artist"]
            result["niveau_forslag"] = result["tcon"]

            if ld_dances:
                noter.append(f"LINEDANCE_DANCE_1 allerede sat: {ld_dances[0]}")
                if similarity(tit2, ld_dances[0]) > 0.7:
                    result["status"] = "ALLEREDE_RETTET"
                    result["handling"] = ""
                else:
                    noter.append(f"TIT2 og LINEDANCE_DANCE_1 stemmer ikke overens!")
                    result["status"] = "KONFLIKT"
                    result["handling"] = "MANUEL"
            elif score_title >= 0.95 and score_artist >= MATCH_THRESHOLD:
                result["status"]   = "SIKKER"
                result["handling"] = "RET_AUTOMATISK"
            elif score_title >= MATCH_THRESHOLD:
                result["status"]   = "SANDSYNLIG"
                result["handling"] = "GODKEND_MANUEL"
            else:
                result["status"]   = "USIKKER"
                result["handling"] = "MANUEL"
        else:
            noter.append(f"TALB matcher ikke MB titel (score={score_title:.2f}): '{talb}' vs '{mb['title']}'")
            result["status"]   = "USIKKER"
            result["handling"] = "MANUEL"
            # Vis alligevel hvad MB siger
            result["rigtig_titel"]  = mb["title"]
            result["rigtig_artist"] = mb["artist"]
    else:
        # Ingen MB data — vurder ud fra tags alene
        filename = os.path.basename(path)

        if talb and tit2 and tit2 != talb:
            # Klassisk mønster: TIT2=dans, TALB=sang
            noter.append("Ingen MBID — mønster muligvis rigtigt men ukontrolleret")
            result["dans_forslag"]  = tit2
            result["rigtig_titel"]  = talb
            result["status"]        = "UKONTROLLERET"
            result["handling"]      = "GODKEND_MANUEL"

        elif not tit2 and not talb:
            # Helt tomme tags — prøv filnavn
            dans_fn = dans_fra_filnavn(filename)
            if dans_fn:
                noter.append(f"Dans udtrukket fra filnavn: '{dans_fn}'")
                result["dans_forslag"] = dans_fn
                result["status"]       = "FRA_FILNAVN"
                result["handling"]     = "GODKEND_MANUEL"
            else:
                result["status"]   = "UKENDT"
                result["handling"] = "MANUEL"

        elif tit2 and tit2 == talb:
            # Titel = album — dans kan være i filnavn
            dans_fn = dans_fra_filnavn(filename)
            if dans_fn:
                noter.append(f"TIT2=TALB, dans fra filnavn: '{dans_fn}'")
                result["dans_forslag"]  = dans_fn
                result["rigtig_titel"]  = tit2
                result["status"]        = "FRA_FILNAVN"
                result["handling"]      = "GODKEND_MANUEL"
            else:
                result["status"]   = "UKENDT"
                result["handling"] = "MANUEL"

        else:
            result["status"]   = "UKENDT"
            result["handling"] = "MANUEL"

    result["noter"] = "; ".join(noter)
    return result


# ── CSV output ─────────────────────────────────────────────────────────────────

FIELDNAMES = [
    "status", "handling", "filename",
    "dans_forslag", "niveau_forslag", "niveau_genkendt", "niveau_rå",
    "rigtig_titel", "rigtig_artist",
    "mb_title", "mb_artist", "match_score",
    "tit2", "tpe1", "talb", "tcon", "mbid",
    "noter", "fil"
]

def write_csv(rows: list[dict], path: str):
    with open(path, "w", newline="", encoding="utf-8-sig") as f:
        w = csv.DictWriter(f, fieldnames=FIELDNAMES, extrasaction="ignore")
        w.writeheader()
        w.writerows(rows)
    print(f"\nGemt: {path}")


# ── Apply: udfør rettelser fra godkendt CSV ───────────────────────────────────

def apply_corrections(csv_path: str, dry_run: bool = True):
    """Læs godkendt CSV og udfør rettelserne."""
    import shutil

    with open(csv_path, newline="", encoding="utf-8-sig") as f:
        rows = list(csv.DictReader(f))

    to_fix = [r for r in rows if r.get("handling") in ("RET_AUTOMATISK", "GODKEND_MANUEL")]
    print(f"\n{len(to_fix)} filer skal rettes")
    if dry_run:
        print("(DRY RUN — ingen filer ændres endnu)\n")

    ok = fejl = spring = 0

    for row in to_fix:
        path     = row["fil"]
        dans     = row["dans_forslag"].strip()
        titel    = row["rigtig_titel"].strip()
        artist   = row["rigtig_artist"].strip() or row["tpe1"].strip()

        if not dans or not titel:
            print(f"  SPRING: {row['filename']} — mangler dans eller titel")
            spring += 1
            continue

        print(f"  {'[DRY]' if dry_run else '[RET]'} {row['filename']}")
        print(f"    TIT2: '{row['tit2']}' → '{titel}'")
        print(f"    TALB: '{row['talb']}' → beholdes")
        print(f"    LINEDANCE_DANCE_1 → '{dans}'")

        if not dry_run:
            try:
                write_corrections(path, titel, artist, dans)
                ok += 1
            except Exception as e:
                print(f"    FEJL: {e}")
                fejl += 1
        else:
            ok += 1

    print(f"\nResultat: {ok} ok, {fejl} fejl, {spring} sprunget over")


def write_corrections(path: str, title: str, artist: str, dance: str):
    """Skriv rettede tags til fil med mutagen."""
    try:
        from mutagen.id3 import ID3, TIT2, TPE1, TXXX, Encoding
        tags = ID3(path)

        # Sæt rigtig titel
        tags["TIT2"] = TIT2(encoding=Encoding.UTF8, text=title)

        # Sæt kunstner hvis vi har den
        if artist:
            tags["TPE1"] = TPE1(encoding=Encoding.UTF8, text=artist)

        # Sæt LINEDANCE_DANCE_1
        for key in [k for k in tags.keys() if "LINEDANCE_DANCE_" in k]:
            del tags[key]
        tags.add(TXXX(encoding=Encoding.UTF8, desc="LINEDANCE_DANCE_1", text=dance))

        tags.save(path)

    except ImportError:
        # Fallback: skriv raw ID3 — kræver mutagen
        raise RuntimeError("mutagen skal installeres for at skrive tags: pip install mutagen")


# ── Main ──────────────────────────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(description="LineDance tag-analyse og -rettelse")
    parser.add_argument("mappe",          nargs="?", help="Mappe med MP3-filer")
    parser.add_argument("--output",       default="linedance_rapport.csv", help="Output CSV")
    parser.add_argument("--apply",        help="Anvend rettelser fra godkendt CSV")
    parser.add_argument("--dry-run",      action="store_true", help="Vis hvad der ville ske (med --apply)")
    parser.add_argument("--ingen-acoustid", action="store_true", help="Spring AcoustID over (hurtigere)")
    parser.add_argument("--max",          type=int, default=0, help="Maks antal filer (0=alle)")
    args = parser.parse_args()

    # Apply-tilstand
    if args.apply:
        apply_corrections(args.apply, dry_run=args.dry_run)
        return

    if not args.mappe:
        parser.print_help()
        return

    # Find alle MP3-filer
    mp3_filer = []
    for root, dirs, files in os.walk(args.mappe):
        for f in files:
            if f.lower().endswith(".mp3"):
                mp3_filer.append(os.path.join(root, f))

    mp3_filer.sort()
    if args.max:
        mp3_filer = mp3_filer[:args.max]

    total = len(mp3_filer)
    print(f"\nFundet {total} MP3-filer i {args.mappe}")
    print(f"Output: {args.output}")

    # Tjek fpcalc
    if not args.ingen_acoustid:
        fpcalc_sti = find_fpcalc()
        if fpcalc_sti:
            print(f"fpcalc:  fundet ({fpcalc_sti})")
            print(f"AcoustID API: {ACOUSTID_API_KEY[:6]}...")
        else:
            print(f"fpcalc:  IKKE FUNDET — AcoustID deaktiveres")
            print(f"  Download fpcalc fra: https://acoustid.org/chromaprint")
            print(f"  Placer fpcalc.exe i samme mappe som scriptet, eller opdater FPCALC_PATH")
            args.ingen_acoustid = True
    else:
        print("AcoustID: SLÅET FRA")
    print(f"\nStarter analyse...\n")

    resultater = []
    tæller = {"SIKKER": 0, "SANDSYNLIG": 0, "ALLEREDE_RETTET": 0,
              "UKONTROLLERET": 0, "FRA_FILNAVN": 0,
              "USIKKER": 0, "KONFLIKT": 0, "UKENDT": 0, "FEJL": 0}

    for i, path in enumerate(mp3_filer, 1):
        navn = os.path.basename(path)
        print(f"[{i:4}/{total}] {navn[:60]}", end="", flush=True)

        try:
            res = analyse_file(path, use_acoustid=not args.ingen_acoustid)
        except Exception as e:
            res = {"fil": path, "filename": navn, "status": "FEJL", "noter": str(e),
                   "handling": "", "dans_forslag": "", "rigtig_titel": "", "rigtig_artist": "",
                   "mb_title": "", "mb_artist": "", "match_score": 0, "tit2": "", "tpe1": "",
                   "talb": "", "tcon": "", "mbid": ""}

        status = res.get("status", "UKENDT")
        tæller[status] = tæller.get(status, 0) + 1
        resultater.append(res)

        print(f" → {status}")

        # Gem løbende hvert 50. fil
        if i % 50 == 0:
            write_csv(resultater, args.output)

    # Gem endelig rapport
    write_csv(resultater, args.output)

    # Statistik
    print("\n" + "="*55)
    print("RAPPORT")
    print("="*55)
    print(f"  SIKKER          : {tæller.get('SIKKER',0):4}  → kan rettes automatisk")
    print(f"  SANDSYNLIG      : {tæller.get('SANDSYNLIG',0):4}  → bør godkendes")
    print(f"  ALLEREDE_RETTET : {tæller.get('ALLEREDE_RETTET',0):4}  → spring over")
    print(f"  UKONTROLLERET   : {tæller.get('UKONTROLLERET',0):4}  → ingen MBID, men mønster muligt")
    print(f"  FRA_FILNAVN     : {tæller.get('FRA_FILNAVN',0):4}  → dans udtrukket fra filnavn")
    print(f"  USIKKER         : {tæller.get('USIKKER',0):4}  → manuel gennemgang")
    print(f"  KONFLIKT        : {tæller.get('KONFLIKT',0):4}  → tags modstrider hinanden")
    print(f"  UKENDT          : {tæller.get('UKENDT',0):4}  → kan ikke vurderes")
    print(f"  FEJL            : {tæller.get('FEJL',0):4}  → teknisk fejl")
    print("="*55)
    # Niveau-statistik
    niveau_værdier = {}
    for r in resultater:
        rå = r.get("niveau_rå", "").strip()
        if rå:
            niveau_værdier[rå] = niveau_værdier.get(rå, 0) + 1

    if niveau_værdier:
        print("\nNiveau-værdier fundet i TCON:")
        for val, antal in sorted(niveau_værdier.items(), key=lambda x: -x[1]):
            norm, genkendt = normaliser_niveau(val)
            mærke = "✓" if genkendt else "?"
            print(f"  {mærke} '{val}' ({antal}x) → '{norm}'")

    print(f"\nÅbn {args.output} i Excel og:")
    print("  1. Gennemgå SANDSYNLIG og UKONTROLLERET")
    print("  2. Sæt 'handling' til RET_AUTOMATISK eller SPRING for hver")
    print("  3. Kør: python linedance_tag_analyse.py --apply rapport.csv --dry-run")
    print("  4. Tjek output, kør så uden --dry-run")
    print()


if __name__ == "__main__":
    main()