"""
Generic simulated-annealing search over a SearchSpace, scored by a Goal.
Nothing in here is CoH-specific - it operates purely on
SearchSpace.random_assignment/propose_move/assignment_to_build and
Goal.score, so a new goal or a richer search space (e.g. real power-pick
prerequisite modeling later) plugs in without touching this file.
"""
import math
import random
import time

from build_stats import compute_build_stats


def optimize(db, attrib_mods, search_space, goal, character_level=50,
             iterations=4000, start_temp=50.0, end_temp=0.05, seed=None,
             progress_every=None):
    rng = random.Random(seed)

    assignment = search_space.random_assignment(rng)
    build = search_space.assignment_to_build(assignment)
    stats = compute_build_stats(db, attrib_mods, build, character_level=character_level)
    score = goal.score(stats)

    best_assignment, best_stats, best_score = assignment, stats, score

    cooling = (end_temp / start_temp) ** (1.0 / max(iterations, 1))
    temp = start_temp
    t0 = time.time()

    for i in range(iterations):
        candidate_assignment = search_space.propose_move(assignment, rng)
        candidate_build = search_space.assignment_to_build(candidate_assignment)
        candidate_stats = compute_build_stats(db, attrib_mods, candidate_build, character_level=character_level)
        candidate_score = goal.score(candidate_stats)

        delta = candidate_score - score
        if delta >= 0 or rng.random() < math.exp(delta / max(temp, 1e-9)):
            assignment, stats, score = candidate_assignment, candidate_stats, candidate_score
            if score > best_score:
                best_assignment, best_stats, best_score = assignment, stats, score

        temp *= cooling

        if progress_every and (i + 1) % progress_every == 0:
            elapsed = time.time() - t0
            print(f"  [{i+1}/{iterations}] temp={temp:.3f} current={score:.2f} best={best_score:.2f} "
                  f"({elapsed:.1f}s elapsed) - {goal.describe(stats)}")

    return {
        "assignment": best_assignment,
        "build": search_space.assignment_to_build(best_assignment),
        "stats": best_stats,
        "score": best_score,
    }
