"""
init_conference.py — Scaffold a new autoconference-skill project directory.

Usage:
    python init_conference.py --goal "..." --metric "..." --direction {maximize|minimize} \
        [--target "..."] [--researchers 3] [--strategy assigned] \
        [--devils-advocate {yes|no}] [--output ./conference/]

    python init_conference.py --goal "..." --mode qualitative --criteria "..." \
        [--researchers 3] [--output ./conference/]
"""

import argparse
import math
import string
import sys
import textwrap
from datetime import datetime
from pathlib import Path


CONFERENCE_TEMPLATE_METRIC = """\
# Conference: {title}

## Goal
{goal}

## Mode
metric

## Success Metric
- **Metric:** {metric}
- **Target:** {target}
- **Direction:** {direction}

## Researchers
- **Count:** {researchers}
- **Iterations per round:** {iterations_per_round}
- **Max rounds:** {max_rounds}

## Pre-Flight Gate
- **Researcher count:** {researchers}
- **Iterations per round:** {iterations_per_round}
- **Max rounds:** {max_rounds}
- **Max total iterations:** {max_total_iterations}
- **Success definition:** {gate_success_definition}
- **Critic / Devil's Advocate:** {critic_setting}
- **Final confirmation:** pending — Conference Chair must summarize these conditions and get explicit user confirmation before execution.
- **Missing or vague values:** ask the user for exact values first, then recalculate this gate and request final confirmation. Do not start Phase 1 until confirmation is explicit.

## Search Space
- **Allowed changes:** _Define what researchers can modify._
- **Forbidden changes:** _Define what must NOT change (e.g., test data, eval scripts)._

## Search Space Partitioning
- **Strategy:** {strategy}

{researcher_sections}

## Guard
{guard}

## Noise Handling
- **Noise runs:** {noise_runs}
- **Min consensus delta:** {min_delta}

## Constraints
- **Max total iterations:** {max_total_iterations}
- **Time budget:** 2h
- **Researcher timeout:** 30m

## Current Approach
_Describe the baseline. What exists now?_

## Shared Knowledge
<!-- Auto-populated after each round with validated findings -->

## Context & References
_Add relevant papers, docs, or URLs here._

---

## Conference Log
<!-- Auto-maintained by Conference Chair. Do not edit manually. -->
| Round | Researcher | Best Metric | Key Finding | Status |
|-------|-----------|-------------|-------------|--------|
"""

CONFERENCE_TEMPLATE_QUALITATIVE = """\
# Conference: {title}

## Goal
{goal}

## Mode
qualitative

## Success Criteria
{criteria}

## Researchers
- **Count:** {researchers}
- **Iterations per round:** {iterations_per_round}
- **Max rounds:** {max_rounds}

## Pre-Flight Gate
- **Researcher count:** {researchers}
- **Iterations per round:** {iterations_per_round}
- **Max rounds:** {max_rounds}
- **Max total iterations:** {max_total_iterations}
- **Success definition:** {gate_success_definition}
- **Critic / Devil's Advocate:** {critic_setting}
- **Final confirmation:** pending — Conference Chair must summarize these conditions and get explicit user confirmation before execution.
- **Missing or vague values:** ask the user for exact values first, then recalculate this gate and request final confirmation. Do not start Phase 1 until confirmation is explicit.

## Search Space
- **Allowed changes:** _Define what researchers can modify._
- **Forbidden changes:** _Define what must NOT change._

## Search Space Partitioning
- **Strategy:** {strategy}

{researcher_sections}

## Guard
{guard}

## Noise Handling
- **Noise runs:** {noise_runs}
- **Min consensus delta:** {min_delta}

## Constraints
- **Max total iterations:** {max_total_iterations}
- **Time budget:** 2h
- **Researcher timeout:** 30m

## Current Approach
_Describe the baseline. What exists now?_

## Shared Knowledge
<!-- Auto-populated after each round with validated findings -->

## Context & References
_Add relevant papers, docs, or URLs here._

---

## Conference Log
<!-- Auto-maintained by Conference Chair. Do not edit manually. -->
| Round | Researcher | Best Metric | Key Finding | Status |
|-------|-----------|-------------|-------------|--------|
"""

LOG_TEMPLATE = """\
# Researcher {rid} Log

> Auto-generated by init_conference.py on {date}
> Conference goal: {goal}

---

"""

RESEARCHER_TSV_HEADER = "iteration\tmetric_value\tdelta\tdelta_pct\tstatus\tdescription\tevaluator_source\ttimestamp\n"
RESEARCHER_TSV_BASELINE = "0\tTBD\t-\t-\tbaseline\tInitial state\tmanual\t{date}\n"

CONFERENCE_TSV_HEADER = (
    "round\tresearcher\titeration\tmetric_value\tdelta\tdelta_pct\t"
    "status\tdescription\tevaluator_source\tpeer_review_verdict\ttimestamp\n"
)


def goal_to_title(goal: str) -> str:
    words = goal.split()
    short = " ".join(words[:8])
    if len(words) > 8:
        short += "..."
    return short.title()


def researcher_ids(count: int) -> list[str]:
    return [string.ascii_uppercase[i] for i in range(count)]


def make_researcher_sections(rids: list[str], strategy: str) -> str:
    if strategy != "assigned":
        return ""
    lines = []
    for rid in rids:
        lines.append(f"### Researcher {rid} Focus")
        lines.append(f"_Describe Researcher {rid}'s exploration area._")
        lines.append("")
    return "\n".join(lines)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Scaffold a new autoconference-skill project.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=textwrap.dedent("""\
            Examples:
              # Metric mode
              python init_conference.py \\
                --goal "Optimize inference latency" \\
                --metric "p95_latency_ms" \\
                --direction minimize \\
                --target "< 50" \\
                --researchers 3 \\
                --devils-advocate yes \\
                --strategy assigned \\
                --output ./my-conference/

              # Qualitative mode
              python init_conference.py \\
                --goal "Survey LLM agent papers" \\
                --mode qualitative \\
                --criteria "Comprehensive taxonomy with 15+ papers" \\
                --researchers 2 \\
                --output ./lit-review/
        """),
    )
    parser.add_argument("--goal", required=True, help="Conference goal description.")
    parser.add_argument(
        "--mode",
        default="metric",
        choices=["metric", "qualitative"],
        help="Conference mode. Default: metric",
    )
    parser.add_argument(
        "--metric",
        default=None,
        help="Metric name to track (required for metric mode).",
    )
    parser.add_argument(
        "--direction",
        default=None,
        choices=["maximize", "minimize"],
        help="Whether to maximize or minimize the metric (required for metric mode).",
    )
    parser.add_argument(
        "--target",
        default=None,
        help="Target value or expression (e.g. '> 0.9', '< 50'). Required for metric mode.",
    )
    parser.add_argument(
        "--criteria",
        default=None,
        help="Success criteria text (required for qualitative mode).",
    )
    parser.add_argument(
        "--researchers",
        type=int,
        default=3,
        metavar="N",
        help="Number of researchers. Default: 3",
    )
    parser.add_argument(
        "--strategy",
        default="free",
        choices=["assigned", "free"],
        help="Search space partitioning strategy. Default: free",
    )
    parser.add_argument(
        "--iterations-per-round",
        type=int,
        default=5,
        metavar="N",
        help="Iterations per round per researcher. Default: 5",
    )
    parser.add_argument(
        "--max-rounds",
        type=int,
        default=4,
        metavar="N",
        help="Maximum number of rounds. Default: 4",
    )
    parser.add_argument(
        "--devils-advocate",
        default=None,
        choices=["yes", "no"],
        help=(
            "Whether one researcher should be assigned as a Devil's Advocate. "
            "If omitted, conference.md marks this as a required user prompt before running."
        ),
    )
    parser.add_argument(
        "--guard",
        default=None,
        help="Safety constraint applied to all researchers (e.g., 'Do not modify the test set').",
    )
    parser.add_argument(
        "--noise-runs",
        type=int,
        default=1,
        metavar="N",
        help="Repeated evaluations to average for noise reduction. Default: 1",
    )
    parser.add_argument(
        "--min-delta",
        type=float,
        default=0.0,
        metavar="F",
        help="Minimum average improvement across kept researchers to advance. Default: 0.0",
    )
    parser.add_argument(
        "--output",
        default="./conference/",
        metavar="PATH",
        help="Output directory. Default: ./conference/",
    )
    parser.add_argument(
        "--force",
        action="store_true",
        help="Overwrite existing output directory without prompting.",
    )
    return parser.parse_args()


def validate_args(args: argparse.Namespace) -> None:
    if args.mode == "metric":
        if not args.metric:
            print("Error: --metric is required when --mode is metric.", file=sys.stderr)
            sys.exit(1)
        if not args.direction:
            print("Error: --direction is required when --mode is metric.", file=sys.stderr)
            sys.exit(1)
        normalized_target = args.target.strip().lower() if args.target else ""
        if not normalized_target or normalized_target in {"tbd", "todo", "unknown"}:
            print("Error: --target is required when --mode is metric.", file=sys.stderr)
            sys.exit(1)
    elif args.mode == "qualitative":
        if not args.criteria:
            print("Error: --criteria is required when --mode is qualitative.", file=sys.stderr)
            sys.exit(1)

    if args.researchers < 1:
        print("Error: --researchers must be >= 1.", file=sys.stderr)
        sys.exit(1)

    if args.researchers > 26:
        print("Error: --researchers must be <= 26.", file=sys.stderr)
        sys.exit(1)

    if args.iterations_per_round < 1:
        print("Error: --iterations-per-round must be >= 1.", file=sys.stderr)
        sys.exit(1)

    if args.max_rounds < 1:
        print("Error: --max-rounds must be >= 1.", file=sys.stderr)
        sys.exit(1)

    if args.noise_runs < 1:
        print("Error: --noise-runs must be >= 1.", file=sys.stderr)
        sys.exit(1)

    if not math.isfinite(args.min_delta) or args.min_delta < 0:
        print("Error: --min-delta must be finite and >= 0.", file=sys.stderr)
        sys.exit(1)


def scaffold(args: argparse.Namespace) -> None:
    date = datetime.now().strftime("%Y-%m-%d")
    title = goal_to_title(args.goal)
    output = Path(args.output)
    rids = researcher_ids(args.researchers)
    max_total_iterations = args.researchers * args.iterations_per_round * args.max_rounds
    researcher_sections = make_researcher_sections(rids, args.strategy)
    guard_text = args.guard if args.guard else "_No guard constraint. Leave blank or define a safety invariant._"
    noise_runs = args.noise_runs
    min_delta = args.min_delta
    if args.devils_advocate == "yes":
        critic_setting = "enabled"
    elif args.devils_advocate == "no":
        critic_setting = "disabled"
    else:
        critic_setting = "ask user before run"

    # Check existing directory
    if output.exists() and any(output.iterdir()):
        if not args.force:
            print(
                f"Error: output directory '{output}' already exists and is not empty. "
                "Use --force to overwrite.",
                file=sys.stderr,
            )
            sys.exit(1)

    output.mkdir(parents=True, exist_ok=True)

    if args.force:
        active_rids = set(rids)
        for rid in researcher_ids(26):
            if rid in active_rids:
                continue
            for suffix in ("log.md", "results.tsv"):
                stale = output / f"researcher_{rid}_{suffix}"
                if stale.is_symlink() or (stale.exists() and not stale.is_file()):
                    raise FileExistsError(f"refusing to prune non-file generated path '{stale}'")
                stale.unlink(missing_ok=True)

    # Create workspace subdirectory
    (output / "workspace").mkdir(exist_ok=True)

    # Write conference.md
    if args.mode == "metric":
        gate_success_definition = f"metric {args.metric} ({args.direction}) target {args.target}"
        conf_text = CONFERENCE_TEMPLATE_METRIC.format(
            title=title,
            goal=args.goal,
            metric=args.metric,
            target=args.target,
            direction=args.direction,
            researchers=args.researchers,
            iterations_per_round=args.iterations_per_round,
            max_rounds=args.max_rounds,
            strategy=args.strategy,
            researcher_sections=researcher_sections,
            max_total_iterations=max_total_iterations,
            guard=guard_text,
            noise_runs=noise_runs,
            min_delta=min_delta,
            gate_success_definition=gate_success_definition,
            critic_setting=critic_setting,
        )
    else:
        gate_success_definition = f"criteria: {args.criteria}"
        conf_text = CONFERENCE_TEMPLATE_QUALITATIVE.format(
            title=title,
            goal=args.goal,
            criteria=args.criteria,
            researchers=args.researchers,
            iterations_per_round=args.iterations_per_round,
            max_rounds=args.max_rounds,
            strategy=args.strategy,
            researcher_sections=researcher_sections,
            max_total_iterations=max_total_iterations,
            guard=guard_text,
            noise_runs=noise_runs,
            min_delta=min_delta,
            gate_success_definition=gate_success_definition,
            critic_setting=critic_setting,
        )
    (output / "conference.md").write_text(conf_text, encoding="utf-8")

    # Write per-researcher files
    for rid in rids:
        log = LOG_TEMPLATE.format(rid=rid, date=date, goal=args.goal)
        (output / f"researcher_{rid}_log.md").write_text(log, encoding="utf-8")

        tsv = RESEARCHER_TSV_HEADER + RESEARCHER_TSV_BASELINE.format(date=date)
        (output / f"researcher_{rid}_results.tsv").write_text(tsv, encoding="utf-8")

    # Write conference_results.tsv (header only)
    (output / "conference_results.tsv").write_text(CONFERENCE_TSV_HEADER, encoding="utf-8")

    # Write conference_events.jsonl (empty)
    (output / "conference_events.jsonl").write_text("", encoding="utf-8")

    # Print success
    print(f"\nConference project scaffolded at: {output.resolve()}")
    print(f"\nFiles created:")
    print(f"  conference.md")
    for rid in rids:
        print(f"  researcher_{rid}_log.md")
        print(f"  researcher_{rid}_results.tsv")
    print(f"  conference_results.tsv")
    print(f"  conference_events.jsonl")
    print(f"  workspace/")
    print(f"\nPre-flight gate:")
    print(f"  Researcher count: {args.researchers}")
    print(f"  Max total iterations: {max_total_iterations}")
    print(f"  Success definition: {gate_success_definition}")
    print(f"  Critic / Devil's Advocate: {critic_setting}")
    print("  Final confirmation: pending before Phase 1")
    print(f"\nNext steps:")
    print(f"  1. Open conference.md and fill in 'Current Approach' and 'Search Space'.")
    print(f"  2. Edit researcher focus areas if using 'assigned' strategy.")
    print("  3. Ask the LitHermes root chair to review conference.md and capability gates.")
    print("  4. Run only in the approved attended Hermes session; no background runner is shipped.")


def main() -> None:
    args = parse_args()
    validate_args(args)
    scaffold(args)


if __name__ == "__main__":
    main()
