"""
acidcat scan -- batch-scan a directory of audio files.
"""

import csv
import os
import sys

from acidcat.core.riff import (
    smpl_root_or_none, acid_root_or_none, effective_acid_beats,
)
from acidcat.core.aiff import is_aiff
from acidcat.core.tagged import is_tagged_format
from acidcat.util.midi import midi_note_to_name
from acidcat.util.csv_helpers import safe_basename_for_csv


# extensions to pick up during directory walk
AUDIO_EXTENSIONS = {
    ".wav", ".aif", ".aiff",
    ".mp3", ".flac", ".ogg", ".oga", ".opus", ".m4a", ".mp4", ".aac",
}


def register(subparsers):
    p = subparsers.add_parser("scan", help="Batch-scan a directory of audio files.")
    p.add_argument("target", help="Directory containing audio files.")
    p.add_argument("-o", "--output", help="Output CSV filename.")
    p.add_argument("-n", "--num", type=int, default=500, help="Max files to scan (default: 500).")
    p.add_argument("-q", "--quiet", action="store_true", help="Suppress console output.")
    p.add_argument("-v", "--verbose", action="store_true", help="Verbose output.")
    p.add_argument("-f", "--format", default="csv", choices=["table", "json", "csv"],
                   help="Output format (default: csv).")
    p.add_argument("--has", help="Filter: only WAV files containing these chunk IDs (comma-separated).")
    p.add_argument("--fallback", action="store_true",
                   help="Estimate BPM/key with librosa if no metadata found.")
    p.add_argument("--features", action="store_true",
                   help="Extract 50+ audio features for ML analysis.")
    p.set_defaults(func=run)


def _scan_wav(filepath):
    """Extract metadata row from a WAV file, from the inspect walker (the
    single WAV decoder since the 2026-07 unification)."""
    from acidcat.core.walk.wav import inspect_wav

    ctx = {}
    chunks, _warns = inspect_wav(filepath, ctx=ctx)
    seen = list(dict.fromkeys(c["id"] for c in chunks))
    duration = round(ctx["duration"], 4) if ctx.get("duration") else None
    bpm = ctx.get("acid_bpm")

    beats = effective_acid_beats(
        {"acid_beats": ctx.get("acid_beats"),
         "acid_one_shot": ctx.get("acid_one_shot"), "bpm": bpm}, duration)
    expected = diff = None
    if bpm and beats:
        expected = round((beats / bpm) * 60, 4)
        diff = round(duration - expected, 4) if duration else None

    # SMPL/ACID root_key = 0 is the documented "unset" sentinel
    # (MIDI C-1). Coerce to None before formatting so the CSV key
    # column does not ship `C-1` for files whose SMPL chunk is
    # present but unset.
    smpl_root = smpl_root_or_none(ctx.get("smpl_root"))
    acid_root = acid_root_or_none(ctx.get("acid_root"))
    key = midi_note_to_name(smpl_root) or midi_note_to_name(acid_root)

    return {
        "filename": filepath,
        "format": "wav",
        "bpm": bpm,
        "key": key,
        "duration_sec": duration,
        "title": None,
        "artist": None,
        "acid_beats": beats,
        "expected_duration": expected,
        "duration_diff": diff,
        "chunks": ",".join(c for c in seen if c not in ("RIFF", "WAVE", "fmt ", "data")),
    }, seen


def _scan_aiff(filepath):
    """Extract metadata row from an AIFF file, from the inspect walker."""
    from acidcat.core.walk.aiff import inspect_aiff

    with open(filepath, "rb") as f:
        form = "AIFC" if f.read(12)[8:12] == b"AIFC" else "AIFF"
    ctx = {}
    chunks, _warns = inspect_aiff(filepath, form, ctx=ctx)
    seen = [c["id"] for c in chunks]

    return {
        "filename": filepath,
        "format": "aiff",
        "bpm": None,
        "key": None,
        "duration_sec": ctx.get("duration"),
        "title": ctx.get("name"),
        "artist": ctx.get("author"),
        "acid_beats": None,
        "expected_duration": None,
        "duration_diff": None,
        "chunks": ",".join(seen),
    }, seen


def _scan_tagged(filepath):
    """Extract metadata row from a tagged format (MP3, FLAC, OGG, M4A)."""
    from acidcat.core.tagged import parse_tagged

    meta = parse_tagged(filepath)
    if meta is None:
        return None, []

    return {
        "filename": filepath,
        "format": meta.get("format_type", "unknown"),
        "bpm": meta.get("bpm"),
        "key": meta.get("key"),
        "duration_sec": meta.get("duration"),
        "title": meta.get("title"),
        "artist": meta.get("artist"),
        "acid_beats": None,
        "expected_duration": None,
        "duration_diff": None,
        "chunks": None,
    }, []


def run(args):
    directory = args.target
    if not os.path.isdir(directory):
        print(f"acidcat scan: {directory}: Not a directory", file=sys.stderr)
        return 1

    default_base = os.path.basename(os.path.normpath(directory))
    output_csv = safe_basename_for_csv(
        getattr(args, 'output', None) or (default_base + "_metadata.csv")
    )

    wanted = None
    has_val = getattr(args, 'has', None)
    if has_val:
        wanted = set(w.strip().upper() for w in has_val.split(",") if w.strip())

    quiet = getattr(args, 'quiet', False)
    verbose = getattr(args, 'verbose', False) and not quiet
    do_fallback = getattr(args, 'fallback', False)
    do_features = getattr(args, 'features', False)
    num = getattr(args, 'num', 500)

    def _vlog(msg):
        if verbose:
            print(msg, file=sys.stderr)

    _vlog(f"[scan] dir={directory} num={num} fallback={do_fallback} "
          f"features={do_features}")

    rows = []
    count = 0

    for root, _, files in os.walk(directory):
        for file in files:
            ext = os.path.splitext(file)[1].lower()
            if ext not in AUDIO_EXTENSIONS:
                continue

            filepath = os.path.join(root, file)

            # dispatch by format
            try:
                if ext in (".wav",):
                    row, seen = _scan_wav(filepath)
                elif ext in (".aif", ".aiff") or is_aiff(filepath):
                    row, seen = _scan_aiff(filepath)
                elif is_tagged_format(filepath):
                    row, seen = _scan_tagged(filepath)
                    if row is None:
                        continue
                else:
                    continue
            except Exception as e:
                if not quiet:
                    print(f"  [skip] {file}: {e}", file=sys.stderr)
                continue

            # chunk filter (only applies to chunk-based formats)
            if wanted and seen:
                upper_seen = {s.upper() for s in seen}
                if not (upper_seen & wanted):
                    continue

            # optional ML features
            if do_features:
                from acidcat.core.features import extract_audio_features
                if not quiet:
                    print(f"  [features] {os.path.basename(filepath)}...", file=sys.stderr)
                feats = extract_audio_features(filepath)
                if feats:
                    row.update(feats)

            # fallback BPM/key via librosa
            if do_fallback and not row.get("bpm"):
                from acidcat.core.detect import estimate_librosa_metadata
                estimates = estimate_librosa_metadata(filepath)
                if estimates.get("estimated_bpm") is not None:
                    row["bpm"] = estimates["estimated_bpm"]
                if estimates.get("estimated_key") is not None:
                    row["key"] = estimates["estimated_key"]
                if estimates.get("duration_sec") is not None and not row.get("duration_sec"):
                    row["duration_sec"] = estimates["duration_sec"]

            if not quiet:
                bpm_str = row.get("bpm") or "-"
                print(f"  {os.path.basename(filepath):40s} BPM={bpm_str}", file=sys.stderr)
            if verbose:
                _vlog(f"    format={row.get('format')} "
                      f"key={row.get('key') or '-'} "
                      f"dur={row.get('duration_sec') or '-'}")

            rows.append(row)
            count += 1
            if count >= num:
                break
        if count >= num:
            break

    if not rows:
        if not quiet:
            print("acidcat scan: No audio files found.", file=sys.stderr)
        return 0

    # fieldnames: core set, then any extras from features
    base_fieldnames = [
        "filename", "format", "bpm", "key", "duration_sec",
        "title", "artist",
        "acid_beats", "expected_duration", "duration_diff", "chunks",
    ]
    if do_features and rows:
        all_keys = set()
        for r in rows:
            all_keys.update(r.keys())
        feature_keys = sorted(k for k in all_keys if k not in base_fieldnames)
        fieldnames = base_fieldnames + feature_keys
    else:
        fieldnames = base_fieldnames

    # output
    with open(output_csv, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction="ignore")
        writer.writeheader()
        writer.writerows(rows)
    if not quiet:
        print(f"\n[INFO] Wrote metadata for {len(rows)} files to {output_csv}",
              file=sys.stderr)

    return 0
