from __future__ import annotations

import csv
import datetime as dt
import json
import shutil
from pathlib import Path


ROOT = Path(r"C:\BalancedTrim\analyzer")
PACKAGE_NAME = "STUDY_000I_ADAPTIVE_ENVELOPE_THEORY_20260610"
PACKAGE_DIR = ROOT / PACKAGE_NAME
ZIP_PATH = ROOT / f"{PACKAGE_NAME}.zip"

STUDY_F_OUTPUTS = Path(
    r"C:\Study\Studies\STUDY_000F_SUCCESSFUL_OPERATING_ENVELOPE_HYPOTHESIS_20260609\outputs"
)
STUDY_H_OUTPUTS = Path(
    r"C:\Study\Studies\STUDY_000H_ENVELOPE_SUPPORT_FACTORS_20260610\outputs"
)


def ensure_dirs() -> None:
    for subdir in ["analysis", "appendices", "figures", "manifest", "manuscript", "outputs", "reports", "source_tables"]:
        (PACKAGE_DIR / subdir).mkdir(parents=True, exist_ok=True)


def read_csv(path: Path) -> list[dict[str, str]]:
    with path.open(newline="", encoding="utf-8") as handle:
        return list(csv.DictReader(handle))


def write_csv(path: Path, rows: list[dict[str, object]], fieldnames: list[str]) -> None:
    with path.open("w", newline="", encoding="utf-8") as handle:
        writer = csv.DictWriter(handle, fieldnames=fieldnames)
        writer.writeheader()
        for row in rows:
            writer.writerow(row)


def mean(values: list[float]) -> float:
    return sum(values) / len(values) if values else 0.0


def round_or_blank(value: float | None, digits: int = 4) -> float | str:
    if value is None:
        return ""
    return round(value, digits)


def escape_xml(text: str) -> str:
    return (
        str(text)
        .replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace('"', "&quot;")
    )


def build_outputs() -> dict[str, list[dict[str, object]]]:
    boundary = read_csv(STUDY_F_OUTPUTS / "study000f_boundary_expression_table.csv")
    support_cases = read_csv(STUDY_H_OUTPUTS / "study000h_support_case_table.csv")

    early_low = [row for row in boundary if row["calendar_date"] == "2025-04-16"][0]
    late_low = [row for row in boundary if row["calendar_date"] == "2026-04-09"][0]
    october = [row for row in boundary if row["cluster_label"] == "october_2025_outside_cluster"]

    october_mean = {
        "distance_miles_norm": mean([float(row["distance_miles_norm"]) for row in october]),
        "speed_mps_norm": mean([float(row["speed_mps_norm"]) for row in october]),
        "hr_residual_bpm": mean([float(row["hr_residual_bpm"]) for row in october]),
        "running_share_28d_activity_pct": mean([float(row["running_share_28d_activity_pct"]) for row in october]),
        "treadmill_neighbors_21d": mean([float(row["treadmill_neighbors_21d"]) for row in october]),
        "cadence_residual_pct": mean([float(row["cadence_residual_pct"]) for row in october]),
        "stride_residual_pct": mean([float(row["stride_residual_pct"]) for row in october]),
    }

    state_case_table = [
        {
            "state_label": "preformation_low_burden_outside_expression",
            "calendar_date": early_low["calendar_date"],
            "phase_context": "early_2025_preformation",
            "burden_state": "low_burden",
            "distance_miles_norm": float(early_low["distance_miles_norm"]),
            "speed_mps_norm": float(early_low["speed_mps_norm"]),
            "hr_residual_bpm": float(early_low["hr_residual_bpm"]),
            "running_share_28d_activity_pct": float(early_low["running_share_28d_activity_pct"]),
            "treadmill_neighbors_21d": float(early_low["treadmill_neighbors_21d"]),
            "cadence_residual_pct": float(early_low["cadence_residual_pct"]),
            "stride_residual_pct": float(early_low["stride_residual_pct"]),
            "why_it_matters": "Low-burden outside expression occurred before later stabilized running specialization fully formed.",
        },
        {
            "state_label": "october_high_burden_outside_cluster",
            "calendar_date": "2025-10_cluster_mean",
            "phase_context": "mid_2025_low_support_cluster",
            "burden_state": "high_burden",
            "distance_miles_norm": round(october_mean["distance_miles_norm"], 4),
            "speed_mps_norm": round(october_mean["speed_mps_norm"], 4),
            "hr_residual_bpm": round(october_mean["hr_residual_bpm"], 4),
            "running_share_28d_activity_pct": round(october_mean["running_share_28d_activity_pct"], 4),
            "treadmill_neighbors_21d": round(october_mean["treadmill_neighbors_21d"], 4),
            "cadence_residual_pct": round(october_mean["cadence_residual_pct"], 4),
            "stride_residual_pct": round(october_mean["stride_residual_pct"], 4),
            "why_it_matters": "High-burden outside expression appeared with similar cadence protection but much weaker support context.",
        },
        {
            "state_label": "mature_low_burden_boundary_expression",
            "calendar_date": late_low["calendar_date"],
            "phase_context": "late_2026_mature_embedding",
            "burden_state": "low_burden",
            "distance_miles_norm": float(late_low["distance_miles_norm"]),
            "speed_mps_norm": float(late_low["speed_mps_norm"]),
            "hr_residual_bpm": float(late_low["hr_residual_bpm"]),
            "running_share_28d_activity_pct": float(late_low["running_share_28d_activity_pct"]),
            "treadmill_neighbors_21d": float(late_low["treadmill_neighbors_21d"]),
            "cadence_residual_pct": float(late_low["cadence_residual_pct"]),
            "stride_residual_pct": float(late_low["stride_residual_pct"]),
            "why_it_matters": "Low-burden outside expression reappeared later under radically different embedding support and adaptation state.",
        },
    ]

    static_vs_adaptive_matrix = [
        {
            "observation_id": "I1",
            "observation_label": "low_burden_outside_expression_appeared_in_early_preformation_state",
            "static_envelope_explanation": "weak",
            "adaptive_envelope_explanation": "strong",
            "preferred_construct": "adaptive_envelope",
            "why": "A fixed high-support envelope struggles to explain why the early 10.23-mile outdoor run stayed low burden despite modest running-share context and only 3 treadmill neighbors.",
        },
        {
            "observation_id": "I2",
            "observation_label": "low_burden_outside_expression_appeared_again_in_mature_high_embedding_state",
            "static_envelope_explanation": "partial",
            "adaptive_envelope_explanation": "strong",
            "preferred_construct": "adaptive_envelope",
            "why": "The April 2026 boundary probe fits a high-support mature envelope state, but it is very different from the earlier low-burden state.",
        },
        {
            "observation_id": "I3",
            "observation_label": "high_burden_outside_cluster_sat_between_the_two_low_burden_states",
            "static_envelope_explanation": "partial",
            "adaptive_envelope_explanation": "strong",
            "preferred_construct": "adaptive_envelope",
            "why": "The October 2025 cluster keeps cadence protection but loses support and becomes costly, which is easier to organize as a transitional or degraded envelope state than as one fixed boundary.",
        },
        {
            "observation_id": "I4",
            "observation_label": "the_same_support_threshold_does_not_capture_both_low_burden_states",
            "static_envelope_explanation": "weak",
            "adaptive_envelope_explanation": "strong",
            "preferred_construct": "adaptive_envelope",
            "why": "A single threshold on running-share or treadmill-neighbor support cannot organize both the early low-burden and late low-burden cases without contradiction.",
        },
        {
            "observation_id": "I5",
            "observation_label": "the_archive_looks_more_like_state_dependent_expression_than_fixed_context_expression",
            "static_envelope_explanation": "weak",
            "adaptive_envelope_explanation": "strong",
            "preferred_construct": "adaptive_envelope",
            "why": "The archive now has enough phase-separated cases to suggest that envelope state changes with adaptation rather than remaining fixed.",
        },
    ]

    theory_tests = [
        {
            "test_label": "fixed_high_support_threshold_fails",
            "result": "supported",
            "interpretation": "The early 2025 low-burden outside-expression case stayed low burden despite much lower support markers than the mature April 2026 boundary probe.",
        },
        {
            "test_label": "phase_separated_low_burden_states_exist",
            "result": "supported",
            "interpretation": "Low-burden outside expression occurred in both an early preformation state and a later mature state, suggesting the envelope did not remain static.",
        },
        {
            "test_label": "adaptive_envelope_organizes_the_archive_better_than_static_threshold_logic",
            "result": "supported",
            "interpretation": "An adaptive-envelope model better explains how low-burden expression can recur under very different support structures while high-burden expression appears in between them.",
        },
    ]

    evidence_family_matrix = [
        {
            "family_id": "I1",
            "family_label": "phase-separated_low_burden_outside_expression",
            "observation": "The archive contains two low-burden outside-expression states separated by major adaptation change.",
            "key_values": "2025-04-16 HR residual -3.75 bpm; 2026-04-09 HR residual +0.69 bpm",
            "support_strength": "strong",
        },
        {
            "family_id": "I2",
            "family_label": "support_threshold_contradiction",
            "observation": "The early low-burden state had much lower embedding support than the later low-burden state.",
            "key_values": "running-share 28.25% and neighbors 3 versus running-share 91.08% and neighbors 32",
            "support_strength": "strong",
        },
        {
            "family_id": "I3",
            "family_label": "intermediate_high_burden_cluster",
            "observation": "The October 2025 cluster sat between the two low-burden states chronologically but showed high burden with low embedding.",
            "key_values": "HR residual +13.88 bpm, running-share 5.13%, neighbors 1.75",
            "support_strength": "strong",
        },
        {
            "family_id": "I4",
            "family_label": "mechanical_pattern_does_not_resolve_theory",
            "observation": "Cadence-protected mechanics alone do not explain why one low-burden state existed with low support and another with high support.",
            "key_values": "early cadence +5.91 / stride -7.89; late cadence +10.22 / stride -9.28",
            "support_strength": "moderate_to_strong",
        },
        {
            "family_id": "I5",
            "family_label": "adaptive_theory_gain",
            "observation": "The archive is better explained if the envelope itself is allowed to change with phase and state.",
            "key_values": "One static support threshold cannot organize all three key states cleanly.",
            "support_strength": "strong",
        },
    ]

    scope_claims = [
        {
            "claim_type": "supported",
            "claim": "The operating envelope behaves more like an adaptive state than a single fixed threshold boundary in the current archive.",
        },
        {
            "claim_type": "supported",
            "claim": "The archive contains at least two distinct low-burden outside-expression states separated by substantial phase change.",
        },
        {
            "claim_type": "not_supported",
            "claim": "The exact mathematical boundary of each envelope state has been fully defined.",
        },
        {
            "claim_type": "not_supported",
            "claim": "Every support-factor change automatically represents a distinct envelope state.",
        },
    ]

    discovered_questions = [
        {
            "question_id": "Q019",
            "status": "ADDRESSED",
            "answered_in": "Study 000I",
            "question": "Is the successful operating envelope plastic across training state and phase rather than fixed?",
        },
        {
            "question_id": "Q025",
            "status": "OPEN",
            "answered_in": "",
            "question": "What measurable markers best define transitions between envelope states in advance rather than after the fact?",
        },
    ]

    primary_answer = [
        {
            "question_id": "Q019",
            "study_id": "000I",
            "core_question": "Is the successful operating envelope plastic across training state and phase rather than fixed?",
            "answer": "yes_supported",
            "answer_plain": "Yes. The archive is better explained by an adaptive-envelope model than by a single fixed support-threshold model.",
        }
    ]

    return {
        "study000i_primary_answer": primary_answer,
        "study000i_state_case_table": state_case_table,
        "study000i_static_vs_adaptive_matrix": static_vs_adaptive_matrix,
        "study000i_theory_tests": theory_tests,
        "study000i_evidence_family_matrix": evidence_family_matrix,
        "study000i_scope_claims": scope_claims,
        "study000i_discovered_questions": discovered_questions,
    }


def write_outputs(outputs: dict[str, list[dict[str, object]]]) -> None:
    out_dir = PACKAGE_DIR / "outputs"
    for stem, rows in outputs.items():
        write_csv(out_dir / f"{stem}.csv", rows, list(rows[0].keys()) if rows else [])


def build_figures(outputs: dict[str, list[dict[str, object]]]) -> None:
    fig_dir = PACKAGE_DIR / "figures"
    state_rows = outputs["study000i_state_case_table"]
    width = 920
    height = 360
    left = 80
    bottom = 290
    top = 45
    chart_width = width - left - 40
    chart_height = bottom - top
    max_x = max(float(row["running_share_28d_activity_pct"]) for row in state_rows) + 5
    max_y = max(float(row["hr_residual_bpm"]) for row in state_rows) + 5
    min_y = min(float(row["hr_residual_bpm"]) for row in state_rows) - 5
    y_span = max_y - min_y
    svg = [
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">',
        '<rect width="100%" height="100%" fill="#fbfaf6"/>',
        '<text x="28" y="26" font-size="16" font-family="Georgia" fill="#222">Phase-Separated Low-Burden States</text>',
    ]
    for row in state_rows:
        x = left + (float(row["running_share_28d_activity_pct"]) / max_x) * chart_width
        y = bottom - ((float(row["hr_residual_bpm"]) - min_y) / y_span) * chart_height
        color = "#2a9d8f" if row["burden_state"] == "low_burden" else "#d62828"
        svg.append(f'<circle cx="{x:.2f}" cy="{y:.2f}" r="7" fill="{color}"/>')
        svg.append(f'<text x="{x+8:.2f}" y="{y-4:.2f}" font-size="10" font-family="Arial">{row["state_label"]}</text>')
    svg.extend([
        '<rect x="28" y="42" width="12" height="12" fill="#2a9d8f"/><text x="46" y="52" font-size="11" font-family="Arial">Low-burden state</text>',
        '<rect x="28" y="60" width="12" height="12" fill="#d62828"/><text x="46" y="70" font-size="11" font-family="Arial">High-burden state</text>',
        '</svg>',
    ])
    (fig_dir / "figure01_phase_state_scatter.svg").write_text("\n".join(svg), encoding="utf-8")

    width = 900
    height = 340
    left = 70
    bottom = 285
    top = 45
    chart_height = bottom - top
    rows = outputs["study000i_state_case_table"]
    max_neighbors = max(float(row["treadmill_neighbors_21d"]) for row in rows)
    bar_w = 180
    gap = 35
    svg = [
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">',
        '<rect width="100%" height="100%" fill="#fffdf8"/>',
        '<text x="28" y="26" font-size="16" font-family="Georgia" fill="#222">Treadmill-Neighbor Density by Envelope State</text>',
    ]
    for idx, row in enumerate(rows):
        value = float(row["treadmill_neighbors_21d"])
        h = (value / max_neighbors) * chart_height if max_neighbors else 0
        x = left + idx * (bar_w + gap)
        y = bottom - h
        color = "#4c78a8" if "low_burden" in row["burden_state"] else "#d62828"
        svg.append(f'<rect x="{x}" y="{y:.2f}" width="{bar_w}" height="{h:.2f}" fill="{color}" rx="4"/>')
        svg.append(f'<text x="{x+bar_w/2:.2f}" y="305" font-size="10" font-family="Arial" text-anchor="middle">{escape_xml(row["state_label"])}</text>')
    svg.append('</svg>')
    (fig_dir / "figure02_neighbor_density_states.svg").write_text("\n".join(svg), encoding="utf-8")

    width = 880
    height = 340
    left = 70
    bottom = 285
    top = 45
    chart_height = bottom - top
    max_dist = max(float(row["distance_miles_norm"]) for row in rows)
    bar_w = 180
    gap = 35
    svg = [
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">',
        '<rect width="100%" height="100%" fill="#fbfcff"/>',
        '<text x="28" y="26" font-size="16" font-family="Georgia" fill="#222">Bout Length Across Envelope States</text>',
    ]
    for idx, row in enumerate(rows):
        value = float(row["distance_miles_norm"])
        h = (value / max_dist) * chart_height if max_dist else 0
        x = left + idx * (bar_w + gap)
        y = bottom - h
        color = "#4c78a8" if "low_burden" in row["burden_state"] else "#d62828"
        svg.append(f'<rect x="{x}" y="{y:.2f}" width="{bar_w}" height="{h:.2f}" fill="{color}" rx="4"/>')
        svg.append(f'<text x="{x+bar_w/2:.2f}" y="305" font-size="10" font-family="Arial" text-anchor="middle">{escape_xml(row["state_label"])}</text>')
    svg.append('</svg>')
    (fig_dir / "figure03_bout_length_states.svg").write_text("\n".join(svg), encoding="utf-8")


def build_reports(outputs: dict[str, list[dict[str, object]]]) -> None:
    reports_dir = PACKAGE_DIR / "reports"
    state_rows = outputs["study000i_state_case_table"]
    early = next(row for row in state_rows if row["state_label"] == "preformation_low_burden_outside_expression")
    october = next(row for row in state_rows if row["state_label"] == "october_high_burden_outside_cluster")
    late = next(row for row in state_rows if row["state_label"] == "mature_low_burden_boundary_expression")

    summary_text = """# Study 000I Plain-Language Summary

Study `000I` asks whether the operating envelope is fixed or adaptive. The archive now supports the adaptive version more strongly.

Why? Because low-burden outside expression appeared in two very different states. One happened early, before later stabilized running specialization fully formed. The other happened later, inside a deeply embedded mature state. A single fixed support threshold struggles to explain both at once. An adaptive-envelope model explains them more cleanly.
"""
    (reports_dir / "STUDY000I_PLAIN_LANGUAGE_SUMMARY.md").write_text(summary_text, encoding="utf-8")

    results_text = f"""# Study 000I Results

## Primary Answer

`Yes, supported.`

The archive is better explained by an adaptive-envelope model than by a single fixed support-threshold model.

## Key State Contrast

- Early low-burden outside expression (`{early["calendar_date"]}`):
  - running-share context: `{early["running_share_28d_activity_pct"]}%`
  - treadmill neighbors: `{early["treadmill_neighbors_21d"]}`
  - distance: `{early["distance_miles_norm"]} miles`
  - HR residual: `{early["hr_residual_bpm"]} bpm`

- October high-burden outside cluster:
  - running-share context: `{october["running_share_28d_activity_pct"]}%`
  - treadmill neighbors: `{october["treadmill_neighbors_21d"]}`
  - distance: `{october["distance_miles_norm"]} miles`
  - HR residual: `{october["hr_residual_bpm"]} bpm`

- Late low-burden boundary expression (`{late["calendar_date"]}`):
  - running-share context: `{late["running_share_28d_activity_pct"]}%`
  - treadmill neighbors: `{late["treadmill_neighbors_21d"]}`
  - distance: `{late["distance_miles_norm"]} miles`
  - HR residual: `{late["hr_residual_bpm"]} bpm`

## Interpretation

The critical result is that the archive contains two low-burden outside-expression states with very different support structures. A static-envelope model based on one fixed support threshold does not organize those cases well. An adaptive-envelope model does.
"""
    (reports_dir / "STUDY000I_RESULTS.md").write_text(results_text, encoding="utf-8")

    methods_text = """# Study 000I Methods

Study `000I` is a flagship theory test built from already-audited outputs from:

- `Study 000F`
- `Study 000H`

The theory question was not whether an envelope exists. `000F` already answered that. The theory question here was whether the envelope behaves as a fixed boundary or as an adaptive state that changes across phase and support structure.

The study compared three state anchors:

1. early low-burden outside expression
2. October 2025 high-burden outside cluster
3. late low-burden boundary expression

The main test was explanatory rather than mechanistic:

`Can one fixed support-threshold logic explain all three states as cleanly as an adaptive-envelope model?`
"""
    (reports_dir / "STUDY000I_METHODS.md").write_text(methods_text, encoding="utf-8")

    discussion_text = """# Study 000I Discussion

The archive now has a richer central model than it had after `000F`.

It is no longer enough to say:

`the system formed an operating envelope`

The better statement is:

`the system appears to have formed an adaptive operating envelope`

That does not mean the envelope is infinitely fluid or fully mapped. It means the archive is better explained if successful expression is allowed to depend on state and phase, not just one stable boundary.

This result raises the theory level of the whole program. Mechanism studies like `000G` and `000H` can now be interpreted inside envelope states rather than outside them.
"""
    (reports_dir / "STUDY000I_DISCUSSION.md").write_text(discussion_text, encoding="utf-8")

    audit_text = """# Study 000I Audit

## Structural Audit

This package is structurally sound.

It includes:

- theory-focused state table
- static-versus-adaptive explanatory matrix
- figures
- manuscript/report set
- build script
- manifest

## Scientific Audit

Study `000I` asks:

`Is the successful operating envelope plastic across training state and phase rather than fixed?`

The answer is:

`Yes, supported.`

Why:

- low-burden outside expression appeared in both an early low-support state and a later high-support state
- the October high-burden state sat between them and remained costly
- a single fixed support threshold does not organize all three cases as cleanly as an adaptive-envelope model

## Boundaries

- This is a theory study, not a direct mechanistic proof.
- It does not fully map all possible envelope states.
- It does support the conclusion that the archive behaves more like an adaptive-envelope system than a static one.
"""
    (reports_dir / "STUDY000I_AUDIT.md").write_text(audit_text, encoding="utf-8")

    manuscript_text = """# Study 000I: Adaptive Envelope Theory

## Core Question

Is the successful operating envelope plastic across training state and phase rather than fixed?

## Answer

Yes. The archive is better explained by an adaptive-envelope model than by a single fixed support-threshold model.

## Thesis

`The altered-mechanics system appears to express successful running through an adaptive operating envelope whose burden properties shift across phase and support state, rather than through one static boundary.`
"""
    (PACKAGE_DIR / "manuscript" / "STUDY000I_MANUSCRIPT.md").write_text(manuscript_text, encoding="utf-8")


def build_readme() -> None:
    readme = """# STUDY_000I_ADAPTIVE_ENVELOPE_THEORY_20260610

Flagship theory study testing whether the operating envelope behaves as a fixed boundary or as an adaptive state across phase and support structure.
"""
    (PACKAGE_DIR / "README.md").write_text(readme, encoding="utf-8")


def build_manifest(outputs: dict[str, list[dict[str, object]]]) -> None:
    manifest = {
        "package_name": PACKAGE_NAME,
        "generated_at": dt.datetime.now(dt.timezone.utc).isoformat(),
        "primary_answer": outputs["study000i_primary_answer"][0]["answer"],
        "state_case_rows": len(outputs["study000i_state_case_table"]),
        "theory_tests": len(outputs["study000i_theory_tests"]),
    }
    (PACKAGE_DIR / "manifest" / "study000i_state.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")


def update_question_log() -> None:
    log_path = Path(r"C:\Study\Program Support\DISCOVERED_PROGRAM_QUESTIONS_20260609.txt")
    if not log_path.exists():
        return
    text = log_path.read_text(encoding="utf-8")
    if "Q019" in text:
        lines = text.splitlines()
        out = []
        i = 0
        while i < len(lines):
            line = lines[i]
            out.append(line)
            if line.strip() == "Q019":
                j = i + 1
                while j < len(lines) and lines[j].strip():
                    current = lines[j]
                    if current.startswith("Status:"):
                        out.append("Status: ADDRESSED")
                    else:
                        out.append(current)
                    j += 1
                out.append("Answered In:")
                out.append("Study 000I")
                out.append("Answer:")
                out.append("Yes. The archive behaved more like an adaptive-envelope system than a single fixed-threshold system.")
                i = j - 1
            i += 1
        text = "\n".join(out)
    if "Q025" not in text:
        text = text.rstrip() + "\n\nQ025\nStatus: OPEN\nBorn From: Study 000I\nQuestion:\nWhat measurable markers best define transitions between envelope states in advance rather than after the fact?\nWhy It Matters:\nIf answered, adaptive-envelope theory becomes prospectively testable instead of only retrospectively interpretable.\nNotes:\nStudy 000I supported adaptive-envelope behavior but did not fully define all transition markers.\n"
    log_path.write_text(text, encoding="utf-8")


def copy_builder() -> None:
    shutil.copy2(Path(__file__), PACKAGE_DIR / "analysis" / Path(__file__).name)


def build_zip() -> None:
    if ZIP_PATH.exists():
        ZIP_PATH.unlink()
    shutil.make_archive(str(PACKAGE_DIR), "zip", root_dir=PACKAGE_DIR)


def main() -> None:
    if PACKAGE_DIR.exists():
        shutil.rmtree(PACKAGE_DIR)
    ensure_dirs()
    outputs = build_outputs()
    write_outputs(outputs)
    build_figures(outputs)
    build_reports(outputs)
    build_readme()
    build_manifest(outputs)
    update_question_log()
    copy_builder()
    build_zip()
    print(PACKAGE_DIR)
    print(ZIP_PATH)


if __name__ == "__main__":
    main()
