import os
import re
from pathlib import Path

import click

from abcforge._batch import run_batch
from abcforge.vendor.abctoolkit.utils import (
    Barlines,
    Quote_re,
    remove_bar_no_annotations,
    remove_information_field,
    strip_empty_bars,
)
from abcforge.vendor.abctoolkit.check import check_alignment_unrotated
from abcforge.vendor.abctoolkit.rotate import rotate_abc


def _interleave(abc_lines):
    """Core ABC → interleaved ABC transform. Returns (rotated_lines, err_str)."""
    abc_lines = [l for l in abc_lines if l.strip()]
    abc_lines = remove_information_field(
        abc_lines, ["X:", "T:", "C:", "W:", "w:", "Z:", "%%MIDI"]
    )
    abc_lines = remove_bar_no_annotations(abc_lines)

    for i, line in enumerate(abc_lines):
        if not (re.search(r"^[A-Za-z]:", line) or line.startswith("%")):
            abc_lines[i] = line.replace(r'\"', "")
            for qc in re.findall(Quote_re, line):
                for barline in Barlines:
                    if barline in qc:
                        abc_lines[i] = abc_lines[i].replace(qc, "")

    try:
        stripped, _ = strip_empty_bars(abc_lines)
    except Exception as e:
        return None, f"strip error: {e}"

    if stripped is None:
        return None, "failed to strip"

    _, bar_no_equal, _ = check_alignment_unrotated(stripped)
    if not bar_no_equal:
        return None, "unequal bar number"

    try:
        rotated = rotate_abc(stripped)
    except Exception as e:
        return None, f"rotate error: {e}"

    if rotated is None:
        return None, "rotate returned None"

    return rotated, None


def _make_convert_fn(input_dir, output_dir):
    def convert_one(file_path):
        try:
            with open(file_path, encoding="utf-8") as f:
                abc_lines = f.readlines()
        except Exception as e:
            return file_path, False, f"read error: {e}"

        rotated, err = _interleave(abc_lines)
        if rotated is None:
            return file_path, False, err

        rel = os.path.relpath(file_path, input_dir)
        out_path = os.path.join(output_dir, rel)
        os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
        try:
            with open(out_path, "w", encoding="utf-8") as f:
                f.writelines(rotated)
        except Exception as e:
            return file_path, False, f"write error: {e}"

        return file_path, True, None

    return convert_one


@click.command()
@click.argument("input_dir", type=click.Path(exists=True, file_okay=False))
@click.argument("output_dir", type=click.Path(file_okay=False))
@click.option("--workers", default=None, type=int, help="Parallel workers (default: cpu count)")
@click.option("--log", "log_path", default=None, type=click.Path(), help="Write errors to file instead of stderr")
@click.option("--resume", is_flag=True, default=False, help="Skip files whose output already exists")
def command(input_dir, output_dir, workers, log_path, resume):
    """Convert ABC files to interleaved ABC notation."""
    input_dir = os.path.abspath(input_dir)
    output_dir = os.path.abspath(output_dir)
    os.makedirs(output_dir, exist_ok=True)

    files = [
        os.path.join(root, f)
        for root, _, fnames in os.walk(input_dir)
        for f in fnames
        if f.endswith(".abc")
    ]
    if not files:
        raise click.ClickException(f"No .abc files found in {input_dir}")

    if resume:
        def _out(fp):
            return os.path.join(output_dir, os.path.relpath(fp, input_dir))
        skipped = sum(1 for f in files if os.path.exists(_out(f)))
        files = [f for f in files if not os.path.exists(_out(f))]
        click.echo(f"Resuming: {skipped} already done, {len(files)} remaining.", err=True)

    if not files:
        click.echo("Nothing to do.", err=True)
        return

    click.echo(f"Found {len(files)} files.", err=True)
    ok, failed = run_batch(
        files,
        _make_convert_fn(input_dir, output_dir),
        desc="abc2abci",
        workers=workers or os.cpu_count(),
        log_path=log_path,
    )
    click.echo(f"\nDone. Converted: {ok}, Failed: {failed}", err=True)
    if failed and log_path:
        click.echo(f"Failures logged to: {log_path}", err=True)
