"""Dataset Doctor: finds the data problems that quietly ruin a model.

A Python port of web/doctor.js; both engines give the same results on the same file
(tests/parity.py checks this).

    import doctor
    report = doctor.analyze("data.csv", target="churned", compare="test.csv")
    report["score"]["overall"], report["issues"], report["featureStrength"]
    doctor.cleaning_script(report)              # pandas code
    doctor.to_csv(doctor.apply_fixes(doctor.parse_csv(text), report))

The report dict uses the same keys as the JavaScript engine, so its JSON is interchangeable.
"""
from .checks import check_column, check_duplicates, check_missing_together, check_ragged, check_redundant
from .compare import compare_tables
from .csvio import Table, as_table, from_dataframe, parse_csv, read_csv, to_csv
from .fixes import apply_fixes, cleaning_script
from .profiler import guess_target, histogram, looks_like_id, profile_column
from .report import report_markdown, summary
from .scoring import IssueList, calculate_score, score_issues
from .target import analyze_target

__all__ = ["analyze", "parse_csv", "read_csv", "to_csv", "cleaning_script", "apply_fixes", "report_markdown",
           "summary", "Table", "from_dataframe", "calculate_score"]

AUTO = object()     # target not given: guess it from the column names


def analyze(data, target=AUTO, compare=None, today=None, max_rows=None):
    """Analyse a table and return the report dict.

    data     a CSV path, a pandas DataFrame, a Table (from parse_csv) or {"columns", "rows"}
    target   column to predict; leave out to guess it by name, None for no target
    compare  optional second file (test set), same kinds of input as `data`
    today    "YYYY-MM-DD"; enables the future-dates check
    max_rows cap on rows used by the leakage check (default 100000)
    """
    table = as_table(data)
    cols, n = table.columns, len(table.rows)
    issues = IssueList()

    col_values = [[(r[j] if j < len(r) and r[j] is not None else "") for r in table.rows] for j in range(len(cols))]
    prof = [profile_column(c, col_values[j], n) for j, c in enumerate(cols)]
    by_name = {}
    for j, p in enumerate(prof):
        p.index = j
        by_name[p.name] = p
    for p in prof:
        p.is_id = looks_like_id(p, n)

    tgt = None
    if target is AUTO:
        tgt = guess_target(prof)
    elif target is not None and target != "":
        tgt = by_name.get(target)

    check_ragged(table, n, issues)
    for p in prof:
        check_column(p, n, tgt, issues, today)
    check_missing_together(prof, n, tgt, issues)
    check_duplicates(table, prof, n, issues)
    check_redundant(prof, n, issues)

    strength, target_info = [], None
    if tgt is not None:
        target_info = analyze_target(tgt, prof, n, issues, table, strength, max_rows)

    cmp = None
    if compare is not None:
        test = as_table(compare)
        if test.rows:
            cmp = compare_tables(table, prof, test, issues, tgt)

    score = score_issues(issues)

    columns_out = []
    for p in prof:
        o = {"name": p.name, "type": "id" if p.is_id else p.type, "missing": p.missing,
             "missingFrac": p.missing_frac if p.missing_frac is not None else p.missing / max(1, n),
             "unique": p.unique, "top": p.top[:5]}
        if p.stats:
            o["stats"] = p.stats
            o["hist"] = histogram(p.sorted, 24, p.sentinels)
        if p.type == "date":
            iso = sorted(v for v in p.iso if v)
            o["range"] = [iso[0] if iso else None, iso[-1] if iso else None]
        o["issues"] = [i["id"] for i in issues if i.get("column") == p.name or (i.get("columns") and p.name in i["columns"])]
        columns_out.append(o)

    return {"rows": n, "columns": columns_out, "issues": list(issues), "score": score, "target": target_info,
            "featureStrength": strength, "compare": cmp, "_prof": prof}
