#pragma once

#include "universal_container/tiered_storage.hpp"

#include <algorithm>
#include <array>
#include <bit>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <optional>
#include <utility>

namespace uc {

enum class StorageMode : std::uint8_t {
    vector,
    tiered
};

enum class ResidencyMode : std::uint8_t {
    automatic,
    forced_vector,
    forced_tiered
};

// Deferred is the safe default: reads collect statistics, but a representation
// change is performed only at a mutation boundary or by adapt_now().  Eager is
// retained as an explicitly unsafe experiment for non-const reads.
enum class ReadAdaptationMode : std::uint8_t {
    deferred,
    eager_nonconst
};

enum class OperationKind : std::uint8_t {
    random_read,
    sequential_read,
    append,
    insert,
    erase,
    set
};

struct OperationSample {
    OperationKind kind = OperationKind::random_read;
    std::size_t size = 0;
    std::size_t position = 0;
    std::size_t count = 1;
    std::size_t element_bytes = 4;
};

struct AdaptationDecision {
    StorageMode target = StorageMode::vector;
    TieredConfig tiered_config{};
    double expected_saving = 0.0;
};

struct AdaptationConfig {
    // Selected by the paired aggressive/moderate/balanced benchmark: this
    // reacts early in a sustained edit phase but leaves a 128-edit negative
    // control in vector mode.
    std::size_t evaluation_interval = 256;
    std::size_t minimum_observations = 2048;
    std::size_t edit_evaluation_interval = 32;
    std::size_t minimum_edit_observations = 128;
    std::size_t minimum_tiered_size = 8 * 1024;
    // The forecast is deliberately bounded by both evidence accumulated in
    // the current residency and this hard ceiling.  Treating the ceiling as a
    // guaranteed future phase length makes short edit bursts look much more
    // profitable than they really are.
    std::size_t forecast_operations = 128 * 1024;
    std::size_t minimum_forecast_operations = 256;
    double forecast_growth_factor = 8.0;
    // A sampled read carries `count = read_sample_rate`, so the statistical
    // weight is preserved while the hot operator[] path pays the full policy
    // update only once per cache-line-sized batch of accesses.
    std::size_t read_sample_rate = 256;
    // Structural samples retain their aggregate weight.  Sampling every edit
    // made the replaceable policy itself a visible part of edit latency.
    std::size_t edit_sample_rate = 4;
    std::size_t minimum_residency_operations = 256;
    // Storage-mode hysteresis uses the edit share of the workload EWMA.  The
    // wider entry threshold and narrower exit threshold form a Schmitt trigger:
    // a tiered sequence is not converted back to vector while edits are still
    // a material part of the current phase.
    double tiered_entry_edit_fraction = 0.05;
    double tiered_exit_edit_fraction = 0.01;
    std::size_t required_confirmation_windows = 2;
    // Shape changes are independent from storage-mode changes.  They need a
    // longer residency and an edit-heavy phase because changing leaf capacity
    // is itself a full O(N) rebuild.
    std::size_t minimum_shape_residency_operations = 4 * 1024;
    double minimum_shape_edit_fraction = 0.10;
    std::size_t locality_radius_divisor = 50;
    std::size_t maximum_directory_levels = 4;
    double vector_to_tiered_safety = 1.50;
    double tiered_to_vector_safety = 2.00;
    double tiered_rebuild_safety = 2.50;
    // A leaf rebuild needs a material advantage over the incumbent, not merely
    // to win a noisy window.  Directory-only changes still have their much
    // smaller rebuild cost, but use the same relative stability guard.
    double minimum_shape_improvement = 0.15;
    double ewma_alpha = 0.35;

    // The search space is data, not a hard-coded branch in the container.  A
    // different policy/config can replace it without touching storage code.
    std::array<std::size_t, 5> tiered_leaf_candidates{64, 128, 256, 512, 1024};

    // Calibratable cost units.  The leaf movement term grows with B, while the
    // directory/fragmentation term grows with N/B.  Their intersection is why
    // the selected block size changes with both N and edit locality.
    double vector_read = 1.0;
    double tiered_read_base = 12.0;
    double tiered_read_per_level = 2.0;
    double tiered_lookup_per_log_top = 1.20;
    double vector_scan_element = 0.70;
    double tiered_scan_element = 0.92;
    double tiered_scan_boundary = 12.0;
    double vector_append = 2.0;
    double tiered_append = 2.8;
    double vector_edit_fixed = 6.0;
    double tiered_edit_fixed = 30.0;
    double vector_move_unit = 0.025;
    double tiered_move_unit = 4.0;
    double tiered_directory_unit = 0.65;
    double localized_directory_multiplier = 0.15;
    double conversion_unit_per_element = 1.0;
    double directory_rebuild_unit_per_leaf = 8.0;
};

struct AdaptationTelemetry {
    std::uint64_t observed_operations = 0;
    std::uint64_t sampled_reads = 0;
    std::uint64_t evaluations = 0;
    std::uint64_t vector_to_tiered = 0;
    std::uint64_t tiered_to_vector = 0;
    std::uint64_t tiered_rebuilds = 0;
    std::uint64_t tiered_leaf_rebuilds = 0;
    std::uint64_t tiered_directory_rebuilds = 0;
    std::size_t last_recommended_leaf = 0;
    std::size_t last_recommended_levels = 0;
    double last_vector_cost_per_operation = 0.0;
    double last_tiered_cost_per_operation = 0.0;
    double last_current_cost_per_operation = 0.0;
    double last_expected_saving = 0.0;
    double last_conversion_cost = 0.0;
    double last_local_edit_fraction = 0.0;
    double last_edit_fraction = 0.0;
    std::size_t last_forecast_operations = 0;
    std::size_t last_evidence_windows = 0;
};

// The production policy is deliberately small.  It does not try to infer a
// workload phase from every operation; AdaptiveSequence consults these limits
// only when its logical capacity grows or shrinks.  The crossover value is a
// calibratable constant and is updated from the focused benchmark rather than
// from the retired benchmark matrix.
struct ResizePolicyConfig {
    std::size_t minimum_tiered_size = 4 * 1024;
    std::size_t shrink_denominator = 8; // shrink at 12.5% occupancy
};

class ResizePolicy {
public:
    explicit ResizePolicy(ResizePolicyConfig config = {}, TieredConfig tiered = {}) noexcept
        : config_(normalize(config)), active_tiered_(normalize_tiered(tiered)) {}

    void set_tiered_config(TieredConfig tiered) noexcept {
        active_tiered_ = normalize_tiered(tiered);
    }

    // Kept as no-op compatibility hooks so a custom CostModelPolicy can still
    // be substituted for experiments without putting policy work on the
    // default read/edit paths.
    void observe(const OperationSample&) noexcept {}
    [[nodiscard]] bool decision_ready() const noexcept { return false; }
    [[nodiscard]] std::optional<AdaptationDecision>
    recommended_decision(StorageMode, std::size_t, TieredConfig) noexcept {
        return std::nullopt;
    }

    void on_transition(StorageMode from,
                       StorageMode to,
                       TieredConfig active_config) noexcept {
        const auto normalized = normalize_tiered(active_config);
        if (from == StorageMode::vector && to == StorageMode::tiered) {
            ++telemetry_.vector_to_tiered;
        } else if (from == StorageMode::tiered && to == StorageMode::vector) {
            ++telemetry_.tiered_to_vector;
        } else if (from == StorageMode::tiered && to == StorageMode::tiered) {
            ++telemetry_.tiered_rebuilds;
            if (normalized.leaf_capacity == active_tiered_.leaf_capacity) {
                ++telemetry_.tiered_directory_rebuilds;
            } else {
                ++telemetry_.tiered_leaf_rebuilds;
            }
        }
        active_tiered_ = normalized;
    }

    void on_transition(StorageMode from, StorageMode to) noexcept {
        on_transition(from, to, active_tiered_);
    }

    void reset() noexcept { telemetry_ = {}; }

    [[nodiscard]] const ResizePolicyConfig& config() const noexcept { return config_; }
    [[nodiscard]] const AdaptationTelemetry& telemetry() const noexcept {
        return telemetry_;
    }

private:
    static ResizePolicyConfig normalize(ResizePolicyConfig config) noexcept {
        config.minimum_tiered_size = std::max<std::size_t>(1,
            config.minimum_tiered_size);
        config.shrink_denominator = std::max<std::size_t>(2,
            config.shrink_denominator);
        return config;
    }

    static TieredConfig normalize_tiered(TieredConfig config) noexcept {
        config.leaf_capacity = std::clamp<std::size_t>(
            config.leaf_capacity, 4, 1U << 20U);
        config.directory_fanout = std::clamp<std::size_t>(
            config.directory_fanout, 2, 1U << 16U);
        config.directory_levels = std::clamp<std::size_t>(
            config.directory_levels, 1, 8);
        return config;
    }

    ResizePolicyConfig config_{};
    TieredConfig active_tiered_{};
    AdaptationTelemetry telemetry_{};
};

class CostModelPolicy {
public:
    static constexpr std::size_t candidate_count = 5;

    explicit CostModelPolicy(AdaptationConfig config = {}, TieredConfig tiered = {})
        : config_(normalize(config)), active_tiered_(normalize_tiered(tiered)),
          search_fanout_(active_tiered_.directory_fanout),
          search_max_levels_(config_.maximum_directory_levels) {}

    void set_tiered_config(TieredConfig tiered) noexcept {
        active_tiered_ = normalize_tiered(tiered);
        search_fanout_ = active_tiered_.directory_fanout;
        search_max_levels_ = config_.maximum_directory_levels;
    }

    void observe(const OperationSample& sample) noexcept {
        const auto count = static_cast<double>(std::max<std::size_t>(1, sample.count));
        last_element_bytes_ = std::max<std::size_t>(1, sample.element_bytes);
        last_observed_size_ = sample.size;
        operations_since_transition_ += sample.count;
        operations_since_shape_transition_ += sample.count;
        telemetry_.observed_operations += sample.count;
        if (sample.kind == OperationKind::random_read) {
            ++telemetry_.sampled_reads;
        }

        const bool edit = is_edit(sample.kind);
        // In vector mode, reads/appends cannot make tiered storage preferable.
        // Before the first relevant edit (or below the measured small-size
        // threshold), avoid evaluating all five counterfactual shapes in the
        // hot path.  Once edits exist, sampled reads are evaluated normally so
        // that a short burst can be rejected rather than converted later.
        if (active_mode_ == StorageMode::vector
            && (sample.size < config_.minimum_tiered_size
                || (!edit && total_edit_weight_ == 0.0
                    && window_edit_weight_ == 0.0
                    && !decision_ready_ && !pending_decision_))) {
            return;
        }

        const bool localized = classify_edit_locality(sample);

        window_vector_cost_ += estimate_vector(sample);
        window_active_tiered_cost_ += estimate_tiered(sample, active_tiered_, localized);
        for (std::size_t i = 0; i < candidate_count; ++i) {
            window_candidate_costs_[i] += estimate_tiered(
                sample, candidate_config(i, sample.size), localized);
        }
        window_weight_ += count;
        if (edit) {
            window_edit_weight_ += count;
            total_edit_weight_ += count;
            if (localized) {
                window_local_edit_weight_ += count;
            }
        }

        if (window_weight_ >= static_cast<double>(config_.evaluation_interval)
            || window_edit_weight_ >= static_cast<double>(
                   config_.edit_evaluation_interval)) {
            close_window();
        }
    }

    [[nodiscard]] bool decision_ready() const noexcept { return decision_ready_; }

    [[nodiscard]] std::optional<AdaptationDecision>
    recommended_decision(StorageMode current,
                         std::size_t size,
                         TieredConfig current_config) noexcept {
        active_mode_ = current;
        if (!decision_ready_) {
            return std::nullopt;
        }
        const auto evidence_windows = closed_windows_since_decision_;
        decision_ready_ = false;
        closed_windows_since_decision_ = 0;
        ++telemetry_.evaluations;
        telemetry_.last_evidence_windows = evidence_windows;
        const auto evaluation = evaluate_decision(current, size, current_config);
        telemetry_.last_vector_cost_per_operation = evaluation.vector_cost;
        telemetry_.last_tiered_cost_per_operation = evaluation.best_tiered_cost;
        telemetry_.last_current_cost_per_operation = evaluation.current_cost;
        telemetry_.last_recommended_leaf = evaluation.best_config.leaf_capacity;
        telemetry_.last_recommended_levels = evaluation.best_config.directory_levels;
        telemetry_.last_conversion_cost = evaluation.selected_rebuild_cost;
        telemetry_.last_expected_saving = evaluation.decision
            ? evaluation.decision->expected_saving : 0.0;
        telemetry_.last_forecast_operations = evaluation.forecast_operations;
        telemetry_.last_edit_fraction = ewma_edit_fraction_;

        return confirm(evaluation.decision);
    }

    // Compatibility with simpler storage wrappers: this intentionally loses a
    // same-mode tiered shape recommendation.
    [[nodiscard]] std::optional<StorageMode>
    recommended_mode(StorageMode current, std::size_t size) noexcept {
        const auto decision = recommended_decision(current, size, active_tiered_);
        return decision ? std::optional<StorageMode>(decision->target) : std::nullopt;
    }

    void on_transition(StorageMode from,
                       StorageMode to,
                       TieredConfig active_config) noexcept {
        if (from == StorageMode::vector && to == StorageMode::tiered) {
            ++telemetry_.vector_to_tiered;
        } else if (from == StorageMode::tiered && to == StorageMode::vector) {
            ++telemetry_.tiered_to_vector;
        } else if (from == StorageMode::tiered && to == StorageMode::tiered) {
            ++telemetry_.tiered_rebuilds;
            if (normalize_tiered(active_config).leaf_capacity
                == active_tiered_.leaf_capacity) {
                ++telemetry_.tiered_directory_rebuilds;
            } else {
                ++telemetry_.tiered_leaf_rebuilds;
            }
        }
        const auto normalized_active = normalize_tiered(active_config);
        if (to == StorageMode::tiered) {
            synchronize_active_tiered_ewma(normalized_active);
        }
        active_tiered_ = normalized_active;
        active_mode_ = to;
        clear_transition_evidence();
    }

    void on_transition(StorageMode from, StorageMode to) noexcept {
        on_transition(from, to, active_tiered_);
    }

    void reset() noexcept {
        const auto active = active_tiered_;
        const auto mode = active_mode_;
        const auto fanout = search_fanout_;
        const auto levels = search_max_levels_;
        *this = CostModelPolicy(config_, active);
        active_mode_ = mode;
        search_fanout_ = fanout;
        search_max_levels_ = levels;
    }

    [[nodiscard]] const AdaptationConfig& config() const noexcept { return config_; }
    [[nodiscard]] const AdaptationTelemetry& telemetry() const noexcept { return telemetry_; }

private:
    static bool is_edit(OperationKind kind) noexcept {
        return kind == OperationKind::insert || kind == OperationKind::erase;
    }

    static TieredConfig normalize_tiered(TieredConfig config) noexcept {
        config.leaf_capacity = std::clamp<std::size_t>(config.leaf_capacity, 4, 1U << 20U);
        config.directory_fanout = std::clamp<std::size_t>(
            config.directory_fanout, 2, 1U << 16U);
        config.directory_levels = std::clamp<std::size_t>(config.directory_levels, 1, 8);
        return config;
    }

    static AdaptationConfig normalize(AdaptationConfig config) noexcept {
        config.evaluation_interval = std::max<std::size_t>(16, config.evaluation_interval);
        config.minimum_observations = std::max(config.evaluation_interval,
                                               config.minimum_observations);
        config.edit_evaluation_interval = std::max<std::size_t>(8,
            config.edit_evaluation_interval);
        config.minimum_edit_observations = std::max(
            config.edit_evaluation_interval, config.minimum_edit_observations);
        config.forecast_operations = std::max<std::size_t>(1, config.forecast_operations);
        config.minimum_forecast_operations = std::clamp<std::size_t>(
            config.minimum_forecast_operations, 1, config.forecast_operations);
        config.forecast_growth_factor = std::max(1.0, config.forecast_growth_factor);
        config.read_sample_rate = std::max<std::size_t>(1, config.read_sample_rate);
        config.edit_sample_rate = std::max<std::size_t>(1, config.edit_sample_rate);
        config.tiered_entry_edit_fraction = std::clamp(
            config.tiered_entry_edit_fraction, 0.0, 1.0);
        config.tiered_exit_edit_fraction = std::clamp(
            config.tiered_exit_edit_fraction, 0.0,
            config.tiered_entry_edit_fraction);
        config.required_confirmation_windows = std::max<std::size_t>(
            1, config.required_confirmation_windows);
        config.minimum_shape_edit_fraction = std::clamp(
            config.minimum_shape_edit_fraction, 0.0, 1.0);
        config.locality_radius_divisor = std::max<std::size_t>(1,
                                                               config.locality_radius_divisor);
        config.maximum_directory_levels = std::clamp<std::size_t>(
            config.maximum_directory_levels, 1, 8);
        config.minimum_shape_improvement = std::clamp(
            config.minimum_shape_improvement, 0.0, 1.0);
        config.ewma_alpha = std::clamp(config.ewma_alpha, 0.01, 1.0);
        for (auto& leaf : config.tiered_leaf_candidates) {
            leaf = std::clamp<std::size_t>(leaf, 4, 1U << 20U);
        }
        std::sort(config.tiered_leaf_candidates.begin(),
                  config.tiered_leaf_candidates.end());
        return config;
    }

    [[nodiscard]] std::size_t actual_levels(std::size_t size,
                                            std::size_t leaf,
                                            std::size_t fanout,
                                            std::size_t maximum) const noexcept {
        auto nodes = (size + leaf - 1) / leaf;
        std::size_t levels = 1;
        while (nodes > fanout && levels < maximum) {
            nodes = (nodes + fanout - 1) / fanout;
            ++levels;
        }
        return levels;
    }

    [[nodiscard]] TieredConfig candidate_config(std::size_t index,
                                                 std::size_t size) const noexcept {
        const auto leaf = config_.tiered_leaf_candidates[index];
        return {leaf, search_fanout_,
                actual_levels(size, leaf, search_fanout_, search_max_levels_)};
    }

    bool classify_edit_locality(const OperationSample& sample) noexcept {
        if (!is_edit(sample.kind)) {
            return false;
        }
        bool localized = false;
        if (has_last_edit_) {
            const auto distance = sample.position > last_edit_position_
                ? sample.position - last_edit_position_
                : last_edit_position_ - sample.position;
            const auto radius = std::max<std::size_t>(
                32, sample.size / config_.locality_radius_divisor);
            localized = distance <= radius;
        }
        last_edit_position_ = sample.position;
        has_last_edit_ = true;
        return localized;
    }

    [[nodiscard]] double estimate_vector(const OperationSample& sample) const noexcept {
        const auto count = static_cast<double>(std::max<std::size_t>(1, sample.count));
        switch (sample.kind) {
        case OperationKind::random_read:
            return config_.vector_read * count;
        case OperationKind::sequential_read:
            return config_.vector_scan_element * count;
        case OperationKind::append:
            return config_.vector_append * count;
        case OperationKind::set:
            return config_.vector_read * 1.3 * count;
        case OperationKind::insert:
        case OperationKind::erase: {
            const auto width_scale = std::max(1.0,
                static_cast<double>(sample.element_bytes) / sizeof(std::uint32_t));
            const auto tail = sample.position < sample.size
                ? sample.size - sample.position : 0;
            return (config_.vector_edit_fixed
                    + config_.vector_move_unit * width_scale * static_cast<double>(tail))
                 * count;
        }
        }
        return count;
    }

    [[nodiscard]] double estimate_tiered(const OperationSample& sample,
                                         TieredConfig tiered,
                                         bool localized) const noexcept {
        tiered = normalize_tiered(tiered);
        const auto count = static_cast<double>(std::max<std::size_t>(1, sample.count));
        const auto leaf_count = std::max<std::size_t>(
            1, (sample.size + tiered.leaf_capacity - 1) / tiered.leaf_capacity);
        const auto level_count = actual_levels(
            sample.size, tiered.leaf_capacity, tiered.directory_fanout,
            tiered.directory_levels);
        auto top_nodes = leaf_count;
        for (std::size_t level = 1; level < level_count; ++level) {
            top_nodes = (top_nodes + tiered.directory_fanout - 1)
                      / tiered.directory_fanout;
        }
        const auto levels = static_cast<double>(level_count);
        const auto top_log2 = top_nodes <= 1 ? 0.0 : static_cast<double>(
            std::bit_width(top_nodes) - 1U);
        const auto lookup = config_.tiered_read_base
                          + config_.tiered_read_per_level * levels
                          + config_.tiered_lookup_per_log_top * top_log2;
        switch (sample.kind) {
        case OperationKind::random_read:
            return lookup * count;
        case OperationKind::sequential_read:
            return (config_.tiered_scan_element
                    + config_.tiered_scan_boundary
                      / static_cast<double>(tiered.leaf_capacity)) * count;
        case OperationKind::append:
            return (config_.tiered_append
                    + 8.0 / static_cast<double>(tiered.leaf_capacity)) * count;
        case OperationKind::set:
            return lookup * 1.3 * count;
        case OperationKind::insert:
        case OperationKind::erase: {
            const auto width_scale = std::max(1.0,
                static_cast<double>(sample.element_bytes) / sizeof(std::uint32_t));
            const auto local = sample.position % tiered.leaf_capacity;
            const auto local_moves = std::min(local, tiered.leaf_capacity - local);
            const auto directory_multiplier = localized
                ? config_.localized_directory_multiplier : 1.0;
            const auto directory_work = config_.tiered_directory_unit
                                      * directory_multiplier
                                      * static_cast<double>(leaf_count);
            return (config_.tiered_edit_fixed
                    + config_.tiered_move_unit * width_scale
                      * static_cast<double>(local_moves)
                    + directory_work + lookup) * count;
        }
        }
        return count;
    }

    struct DecisionEvaluation {
        std::optional<AdaptationDecision> decision;
        TieredConfig best_config{};
        double vector_cost = 0.0;
        double best_tiered_cost = 0.0;
        double current_cost = 0.0;
        double selected_rebuild_cost = 0.0;
        std::size_t forecast_operations = 0;
    };

    [[nodiscard]] DecisionEvaluation evaluate_decision(
        StorageMode current,
        std::size_t size,
        TieredConfig current_config) const noexcept {
        DecisionEvaluation evaluation;

        std::size_t best_index = 0;
        for (std::size_t i = 1; i < candidate_count; ++i) {
            if (ewma_candidate_costs_[i] < ewma_candidate_costs_[best_index]) {
                best_index = i;
            }
        }
        evaluation.best_config = candidate_config(best_index, size);
        evaluation.vector_cost = ewma_vector_cost_;
        evaluation.best_tiered_cost = ewma_candidate_costs_[best_index];
        const auto current_tiered_cost = ewma_active_tiered_cost_;
        evaluation.current_cost = current == StorageMode::vector
            ? evaluation.vector_cost : current_tiered_cost;

        const auto width_scale = std::max(1.0,
            static_cast<double>(last_element_bytes_) / sizeof(std::uint32_t));
        const auto conversion_cost = static_cast<double>(size)
                                   * config_.conversion_unit_per_element * width_scale;
        evaluation.selected_rebuild_cost = conversion_cost;
        const auto evidence_forecast = static_cast<std::size_t>(std::ceil(
            total_weight_ * config_.forecast_growth_factor));
        evaluation.forecast_operations = std::clamp(
            evidence_forecast,
            config_.minimum_forecast_operations,
            config_.forecast_operations);

        const bool enough_evidence =
            total_weight_ >= static_cast<double>(config_.minimum_observations)
            || total_edit_weight_
                >= static_cast<double>(config_.minimum_edit_observations);
        if (!initialized_ || !enough_evidence
            || operations_since_transition_ < config_.minimum_residency_operations) {
            return evaluation;
        }

        const auto forecast = static_cast<double>(evaluation.forecast_operations);
        double best_net_saving = 0.0;

        if (current == StorageMode::vector) {
            if (size >= config_.minimum_tiered_size
                && ewma_edit_fraction_ >= config_.tiered_entry_edit_fraction
                && evaluation.best_tiered_cost < evaluation.vector_cost) {
                const auto gross = (evaluation.vector_cost
                                  - evaluation.best_tiered_cost) * forecast;
                const auto threshold = conversion_cost
                                     * config_.vector_to_tiered_safety;
                if (gross > threshold) {
                    evaluation.decision = AdaptationDecision{
                        StorageMode::tiered, evaluation.best_config, gross};
                    best_net_saving = gross - threshold;
                }
            }
            return evaluation;
        }

        // A tiered sequence returns to vector only after the workload has
        // crossed the low (read-dominant) side of the phase hysteresis band.
        if (ewma_edit_fraction_ <= config_.tiered_exit_edit_fraction
            && evaluation.vector_cost < current_tiered_cost) {
            const auto gross = (current_tiered_cost - evaluation.vector_cost) * forecast;
            const auto threshold = conversion_cost * config_.tiered_to_vector_safety;
            if (gross > threshold) {
                evaluation.decision = AdaptationDecision{
                    StorageMode::vector, normalize_tiered(current_config), gross};
                best_net_saving = gross - threshold;
            }
        }

        const auto normalized_current = normalize_tiered(current_config);
        const bool shape_differs = evaluation.best_config != normalized_current;
        const bool directory_only = evaluation.best_config.leaf_capacity
                                  == normalized_current.leaf_capacity;
        const auto shape_rebuild_cost = directory_only
            ? static_cast<double>((size + evaluation.best_config.leaf_capacity - 1)
                                  / evaluation.best_config.leaf_capacity)
              * config_.directory_rebuild_unit_per_leaf
            : conversion_cost;
        const auto relative_improvement = current_tiered_cost > 0.0
            ? (current_tiered_cost - evaluation.best_tiered_cost)
              / current_tiered_cost
            : 0.0;
        if (operations_since_shape_transition_
                >= config_.minimum_shape_residency_operations
            && ewma_edit_fraction_ >= config_.minimum_shape_edit_fraction
            && shape_differs
            && evaluation.best_tiered_cost < current_tiered_cost
            && relative_improvement >= config_.minimum_shape_improvement) {
            const auto gross = (current_tiered_cost
                              - evaluation.best_tiered_cost) * forecast;
            const auto threshold = shape_rebuild_cost * config_.tiered_rebuild_safety;
            const auto net = gross - threshold;
            if (gross > threshold && net > best_net_saving) {
                evaluation.decision = AdaptationDecision{
                    StorageMode::tiered, evaluation.best_config, gross};
                evaluation.selected_rebuild_cost = shape_rebuild_cost;
            }
        }
        return evaluation;
    }

    void close_window() noexcept {
        const auto inverse_weight = 1.0 / window_weight_;
        const auto vector_per_operation = window_vector_cost_ * inverse_weight;
        const auto active_per_operation = window_active_tiered_cost_ * inverse_weight;
        const auto edit_fraction = window_edit_weight_ * inverse_weight;
        std::array<double, candidate_count> candidates{};
        for (std::size_t i = 0; i < candidate_count; ++i) {
            candidates[i] = window_candidate_costs_[i] * inverse_weight;
        }
        if (!initialized_) {
            ewma_vector_cost_ = vector_per_operation;
            ewma_active_tiered_cost_ = active_per_operation;
            ewma_candidate_costs_ = candidates;
            ewma_edit_fraction_ = edit_fraction;
            initialized_ = true;
        } else {
            const auto alpha = config_.ewma_alpha;
            ewma_vector_cost_ = alpha * vector_per_operation
                              + (1.0 - alpha) * ewma_vector_cost_;
            ewma_active_tiered_cost_ = alpha * active_per_operation
                                     + (1.0 - alpha) * ewma_active_tiered_cost_;
            for (std::size_t i = 0; i < candidate_count; ++i) {
                ewma_candidate_costs_[i] = alpha * candidates[i]
                                         + (1.0 - alpha) * ewma_candidate_costs_[i];
            }
            ewma_edit_fraction_ = alpha * edit_fraction
                                + (1.0 - alpha) * ewma_edit_fraction_;
        }
        if (window_edit_weight_ > 0.0) {
            telemetry_.last_local_edit_fraction =
                window_local_edit_weight_ / window_edit_weight_;
        }
        telemetry_.last_edit_fraction = ewma_edit_fraction_;
        total_weight_ += window_weight_;
        window_vector_cost_ = 0.0;
        window_active_tiered_cost_ = 0.0;
        window_candidate_costs_.fill(0.0);
        window_weight_ = 0.0;
        window_edit_weight_ = 0.0;
        window_local_edit_weight_ = 0.0;
        update_confirmation(evaluate_decision(
            active_mode_, last_observed_size_, active_tiered_).decision);
        decision_ready_ = true;
        if (closed_windows_since_decision_
            != std::numeric_limits<std::size_t>::max()) {
            ++closed_windows_since_decision_;
        }
    }

    static bool same_decision(const AdaptationDecision& left,
                              const AdaptationDecision& right) noexcept {
        return left.target == right.target
            && left.tiered_config == right.tiered_config;
    }

    void update_confirmation(
        const std::optional<AdaptationDecision>& decision) noexcept {
        if (!decision) {
            pending_decision_.reset();
            confirmation_count_ = 0;
            return;
        }
        if (pending_decision_ && same_decision(*pending_decision_, *decision)) {
            confirmation_count_ = std::min(
                config_.required_confirmation_windows,
                confirmation_count_ + 1);
            pending_decision_ = decision;
        } else {
            pending_decision_ = decision;
            confirmation_count_ = 1;
        }
    }

    [[nodiscard]] std::optional<AdaptationDecision>
    confirm(const std::optional<AdaptationDecision>& decision) noexcept {
        if (!decision || !pending_decision_
            || !same_decision(*pending_decision_, *decision)
            || confirmation_count_ < config_.required_confirmation_windows) {
            return std::nullopt;
        }
        auto confirmed = decision;
        pending_decision_.reset();
        confirmation_count_ = 0;
        return confirmed;
    }

    void synchronize_active_tiered_ewma(TieredConfig active) noexcept {
        if (!initialized_) {
            return;
        }
        for (std::size_t i = 0; i < candidate_count; ++i) {
            if (config_.tiered_leaf_candidates[i] == active.leaf_capacity) {
                ewma_active_tiered_cost_ = ewma_candidate_costs_[i];
                return;
            }
        }
    }

    // A committed transition starts a new payback/residency epoch, but the
    // workload signal is deliberately retained.  Throwing the EWMA away made
    // the policy relearn the same stationary phase from noisy short windows and
    // allowed vector<->tiered and leaf-shape ping-pong.
    void clear_transition_evidence() noexcept {
        operations_since_transition_ = 0;
        operations_since_shape_transition_ = 0;
        total_weight_ = 0.0;
        total_edit_weight_ = 0.0;
        window_weight_ = 0.0;
        window_vector_cost_ = 0.0;
        window_active_tiered_cost_ = 0.0;
        window_candidate_costs_.fill(0.0);
        window_edit_weight_ = 0.0;
        window_local_edit_weight_ = 0.0;
        pending_decision_.reset();
        confirmation_count_ = 0;
        has_last_edit_ = false;
        decision_ready_ = false;
        closed_windows_since_decision_ = 0;
    }

    AdaptationConfig config_;
    TieredConfig active_tiered_;
    StorageMode active_mode_ = StorageMode::vector;
    std::size_t search_fanout_ = 64;
    std::size_t search_max_levels_ = 4;
    AdaptationTelemetry telemetry_;
    std::array<double, candidate_count> window_candidate_costs_{};
    std::array<double, candidate_count> ewma_candidate_costs_{};
    double window_vector_cost_ = 0.0;
    double window_active_tiered_cost_ = 0.0;
    double window_weight_ = 0.0;
    double window_edit_weight_ = 0.0;
    double window_local_edit_weight_ = 0.0;
    double total_weight_ = 0.0;
    double total_edit_weight_ = 0.0;
    double ewma_vector_cost_ = 0.0;
    double ewma_active_tiered_cost_ = 0.0;
    double ewma_edit_fraction_ = 0.0;
    std::size_t operations_since_transition_ = 0;
    std::size_t operations_since_shape_transition_ = 0;
    std::size_t last_edit_position_ = 0;
    std::size_t last_observed_size_ = 0;
    std::size_t last_element_bytes_ = sizeof(std::uint32_t);
    std::size_t confirmation_count_ = 0;
    std::optional<AdaptationDecision> pending_decision_;
    bool has_last_edit_ = false;
    bool initialized_ = false;
    bool decision_ready_ = false;
    std::size_t closed_windows_since_decision_ = 0;
};

} // namespace uc
