//! Retrieval projection bundle assembly for a manifest snapshot.
//!
//! Orchestrates path-, AST-, and SCIP-backed builders into one `RetrievalProjectionBundle` with
//! versioned heuristic metadata for each projection family.

use std::path::Path;

use crate::domain::FriggResult;
use crate::storage::RetrievalProjectionBundle;

use super::{
    ENTRYPOINT_SURFACE_PROJECTION_HEURISTIC_VERSION,
    PATH_ANCHOR_SKETCH_PROJECTION_HEURISTIC_VERSION, PATH_RELATION_PROJECTION_HEURISTIC_VERSION,
    PATH_SURFACE_TERM_PROJECTION_HEURISTIC_VERSION, RETRIEVAL_PROJECTION_FAMILY_ENTRYPOINT_SURFACE,
    RETRIEVAL_PROJECTION_FAMILY_PATH_ANCHOR_SKETCH, RETRIEVAL_PROJECTION_FAMILY_PATH_RELATION,
    RETRIEVAL_PROJECTION_FAMILY_PATH_SURFACE_TERM, RETRIEVAL_PROJECTION_FAMILY_PATH_WITNESS,
    RETRIEVAL_PROJECTION_FAMILY_SUBTREE_COVERAGE, RETRIEVAL_PROJECTION_FAMILY_TEST_SUBJECT,
    RETRIEVAL_PROJECTION_INPUT_MODE_AST, RETRIEVAL_PROJECTION_INPUT_MODE_PATH,
    SUBTREE_COVERAGE_PROJECTION_HEURISTIC_VERSION, TEST_SUBJECT_PROJECTION_HEURISTIC_VERSION,
    build_path_anchor_sketch_projection_records, build_path_relation_projection_records,
    build_path_surface_term_projection_records, build_subtree_coverage_projection_records,
    normalize_path_anchor_sketch_projection_records, normalize_path_relation_projection_records,
    normalize_path_surface_term_projection_records,
};

#[derive(Default)]
pub(super) struct RetrievalProjectionInputModes {
    pub(super) path_relation: std::collections::BTreeSet<String>,
    pub(super) path_surface_term: std::collections::BTreeSet<String>,
    pub(super) path_anchor_sketch: std::collections::BTreeSet<String>,
}

pub(crate) fn build_retrieval_projection_bundle(
    repository_id: &str,
    workspace_root: &Path,
    manifest_paths: &[String],
) -> FriggResult<RetrievalProjectionBundle> {
    let path_witness =
        super::super::path_witness_projection::build_path_witness_projection_records_from_paths(
            manifest_paths,
        )?;
    let stored_path_witness =
        super::super::path_witness_projection::decode_path_witness_projection_records(
            &path_witness,
        )?;
    let test_subject =
        super::super::overlay_projection::build_test_subject_projection_records(manifest_paths)?;
    let stored_test_subject =
        super::super::overlay_projection::decode_test_subject_projection_records(&test_subject)?;
    let entrypoint_surface =
        super::super::overlay_projection::build_entrypoint_surface_projection_records_from_paths(
            manifest_paths,
        )?;
    let stored_entrypoint_surface =
        super::super::overlay_projection::decode_entrypoint_surface_projection_records(
            &entrypoint_surface,
        )?;
    let path_relations = build_path_relation_projection_records(
        &stored_path_witness,
        &stored_test_subject,
        &stored_entrypoint_surface,
    );
    let subtree_coverage = build_subtree_coverage_projection_records(&stored_path_witness);
    let path_surface_terms = build_path_surface_term_projection_records(
        &stored_path_witness,
        &stored_entrypoint_surface,
    );
    let path_anchor_sketches = build_path_anchor_sketch_projection_records(
        workspace_root,
        &stored_path_witness,
        &path_surface_terms,
    );
    let absolute_manifest_paths = manifest_paths
        .iter()
        .map(|path| workspace_root.join(path))
        .collect::<Vec<_>>();

    let mut input_modes = RetrievalProjectionInputModes::default();
    input_modes
        .path_relation
        .insert(RETRIEVAL_PROJECTION_INPUT_MODE_PATH.to_owned());
    input_modes
        .path_surface_term
        .insert(RETRIEVAL_PROJECTION_INPUT_MODE_PATH.to_owned());
    input_modes
        .path_anchor_sketch
        .insert(RETRIEVAL_PROJECTION_INPUT_MODE_PATH.to_owned());

    let mut path_relations = path_relations;
    let mut path_surface_terms = path_surface_terms;
    let mut path_anchor_sketches = path_anchor_sketches;

    let ast_relation_count_before = path_relations.len();
    super::ast::apply_ast_bundle_contributions(
        workspace_root,
        &absolute_manifest_paths,
        &stored_path_witness,
        &mut path_relations,
        &mut path_surface_terms,
        &mut path_anchor_sketches,
        &mut input_modes,
    );
    if path_relations.len() > ast_relation_count_before {
        input_modes
            .path_relation
            .insert(RETRIEVAL_PROJECTION_INPUT_MODE_AST.to_owned());
    }
    super::scip::apply_scip_projection_contributions(
        repository_id,
        workspace_root,
        &stored_path_witness,
        &mut path_relations,
        &mut path_surface_terms,
        &mut path_anchor_sketches,
        &mut input_modes,
    );

    normalize_path_relation_projection_records(&mut path_relations);
    normalize_path_surface_term_projection_records(&mut path_surface_terms);
    normalize_path_anchor_sketch_projection_records(&mut path_anchor_sketches);

    Ok(RetrievalProjectionBundle {
        heads: vec![
            head(
                RETRIEVAL_PROJECTION_FAMILY_PATH_WITNESS,
                super::super::path_witness_projection::PATH_WITNESS_PROJECTION_HEURISTIC_VERSION,
                path_witness.len(),
                &[RETRIEVAL_PROJECTION_INPUT_MODE_PATH.to_owned()],
            ),
            head(
                RETRIEVAL_PROJECTION_FAMILY_TEST_SUBJECT,
                TEST_SUBJECT_PROJECTION_HEURISTIC_VERSION,
                test_subject.len(),
                &[RETRIEVAL_PROJECTION_INPUT_MODE_PATH.to_owned()],
            ),
            head(
                RETRIEVAL_PROJECTION_FAMILY_ENTRYPOINT_SURFACE,
                ENTRYPOINT_SURFACE_PROJECTION_HEURISTIC_VERSION,
                entrypoint_surface.len(),
                &[RETRIEVAL_PROJECTION_INPUT_MODE_PATH.to_owned()],
            ),
            head(
                RETRIEVAL_PROJECTION_FAMILY_PATH_RELATION,
                PATH_RELATION_PROJECTION_HEURISTIC_VERSION,
                path_relations.len(),
                &input_modes
                    .path_relation
                    .iter()
                    .cloned()
                    .collect::<Vec<_>>(),
            ),
            head(
                RETRIEVAL_PROJECTION_FAMILY_SUBTREE_COVERAGE,
                SUBTREE_COVERAGE_PROJECTION_HEURISTIC_VERSION,
                subtree_coverage.len(),
                &[RETRIEVAL_PROJECTION_INPUT_MODE_PATH.to_owned()],
            ),
            head(
                RETRIEVAL_PROJECTION_FAMILY_PATH_SURFACE_TERM,
                PATH_SURFACE_TERM_PROJECTION_HEURISTIC_VERSION,
                path_surface_terms.len(),
                &input_modes
                    .path_surface_term
                    .iter()
                    .cloned()
                    .collect::<Vec<_>>(),
            ),
            head(
                RETRIEVAL_PROJECTION_FAMILY_PATH_ANCHOR_SKETCH,
                PATH_ANCHOR_SKETCH_PROJECTION_HEURISTIC_VERSION,
                path_anchor_sketches.len(),
                &input_modes
                    .path_anchor_sketch
                    .iter()
                    .cloned()
                    .collect::<Vec<_>>(),
            ),
        ],
        path_witness,
        test_subject,
        entrypoint_surface,
        path_relations,
        subtree_coverage,
        path_surface_terms,
        path_anchor_sketches,
    })
}

fn head(
    family: &str,
    heuristic_version: i64,
    row_count: usize,
    input_modes: &[String],
) -> crate::storage::RetrievalProjectionHeadRecord {
    crate::storage::RetrievalProjectionHeadRecord {
        family: family.to_owned(),
        heuristic_version,
        input_modes: input_modes.to_vec(),
        row_count,
    }
}
