//! Health output context, grouping, timings, and report assembly.

use std::time::Instant;

use fallow_config::ResolvedConfig;
use fallow_output::{
    ComplexityViolation, FileHealthScore, HealthGrouping, HealthTimings, HotspotEntry,
    HotspotSummary, RefactoringTarget,
};

use super::actions::build_health_action_context;
use super::analysis_data::HealthAnalysisData;
use super::assembly::{HealthReportAssembly, assemble_health_report};
use super::findings_pipeline::HealthFindingsData;
use super::framework_health::build_framework_health_diagnostics;
use super::pipeline::{HealthPipelineTimings, HealthScope, HealthTimingBaseInput};
use super::result::HealthOutputParts;
use super::timings::{HealthTimingInput, build_health_timings};
use super::{
    HealthDerivedSections, HealthOptions, HealthVitalData, grouping, health_file_scores_slice,
    scoring,
};

pub(super) struct HealthOutputContextInput<'a, R> {
    pub(super) config: &'a ResolvedConfig,
    pub(super) files: &'a [fallow_types::discover::DiscoveredFile],
    pub(super) modules: &'a [crate::source::ModuleInfo],
    pub(super) scope: &'a HealthScope<'a, R>,
    pub(super) needs_file_scores: bool,
    pub(super) report_coverage_gaps: bool,
    pub(super) has_istanbul_coverage: bool,
    pub(super) findings_data: HealthFindingsData,
    pub(super) analysis_data: HealthAnalysisData,
    pub(super) derived_sections: HealthDerivedSections,
    pub(super) vital_data: HealthVitalData,
    pub(super) timings: HealthPipelineTimings,
    pub(super) start: &'a Instant,
}

pub(super) struct HealthOutputContext<'a, R> {
    pub(super) build: HealthOutputBuildInput<'a, R>,
    pub(super) sections: HealthOutputSectionInput,
}

pub(super) struct HealthOutputBuildInput<'a, R> {
    config: &'a ResolvedConfig,
    files: &'a [fallow_types::discover::DiscoveredFile],
    modules: &'a [crate::source::ModuleInfo],
    file_paths: &'a rustc_hash::FxHashMap<crate::discover::FileId, &'a std::path::PathBuf>,
    group_resolver: Option<&'a R>,
    needs_file_scores: bool,
    report_coverage_gaps: bool,
    has_istanbul_coverage: bool,
    threshold_overrides: Vec<fallow_output::ThresholdOverrideState>,
    max_cyclomatic: u16,
    max_cognitive: u16,
    max_crap: f64,
    files_analyzed: usize,
    total_functions: usize,
    total_above_threshold: usize,
    sev_critical: usize,
    sev_high: usize,
    sev_moderate: usize,
    timing_base: HealthTimingBaseInput,
    start: &'a Instant,
}

pub(super) struct HealthOutputSectionInput {
    analysis_data: HealthAnalysisData,
    derived_sections: HealthDerivedSections,
    vital_data: HealthVitalData,
    findings: Vec<ComplexityViolation>,
}

struct HealthOutputSupportingParts {
    grouping: Option<fallow_output::HealthGrouping>,
    timings: Option<fallow_output::HealthTimings>,
}

pub(super) fn prepare_health_output_context<R>(
    input: HealthOutputContextInput<'_, R>,
) -> HealthOutputContext<'_, R> {
    let HealthFindingsData {
        findings,
        threshold_overrides,
        files_analyzed,
        total_functions,
        complexity_ms,
        total_above_threshold,
        sev_critical,
        sev_high,
        sev_moderate,
        loaded_baseline: _,
    } = input.findings_data;

    HealthOutputContext {
        build: HealthOutputBuildInput {
            config: input.config,
            files: input.files,
            modules: input.modules,
            file_paths: &input.scope.file_paths,
            group_resolver: input.scope.group_resolver.as_ref(),
            needs_file_scores: input.needs_file_scores,
            report_coverage_gaps: input.report_coverage_gaps,
            has_istanbul_coverage: input.has_istanbul_coverage,
            threshold_overrides,
            max_cyclomatic: input.scope.max_cyclomatic,
            max_cognitive: input.scope.max_cognitive,
            max_crap: input.scope.max_crap,
            files_analyzed,
            total_functions,
            total_above_threshold,
            sev_critical,
            sev_high,
            sev_moderate,
            timing_base: input.timings.into_base_input(complexity_ms),
            start: input.start,
        },
        sections: HealthOutputSectionInput {
            analysis_data: input.analysis_data,
            derived_sections: input.derived_sections,
            vital_data: input.vital_data,
            findings,
        },
    }
}

pub(super) fn build_health_output_parts<R: super::HealthGroupResolver>(
    opts: &HealthOptions<'_>,
    build: &HealthOutputBuildInput<'_, R>,
    sections: HealthOutputSectionInput,
) -> HealthOutputParts {
    let HealthOutputSectionInput {
        analysis_data,
        derived_sections,
        vital_data,
        findings,
    } = sections;
    let coverage_gaps_has_findings =
        health_coverage_gaps_has_findings(analysis_data.score_output.as_ref());
    let action_ctx = build_health_action_context(
        opts,
        build.config,
        build.max_cyclomatic,
        build.max_cognitive,
        build.max_crap,
    );

    let HealthOutputSupportingParts { grouping, timings } =
        build_health_supporting_parts(HealthSupportingPartsInput {
            opts,
            build,
            analysis_data: &analysis_data,
            derived_sections: &derived_sections,
            vital_data: &vital_data,
            findings: &findings,
            action_ctx: &action_ctx,
        });

    let framework_health =
        build_framework_health_diagnostics(build.config, analysis_data.framework_health_facts);

    let report = build_health_report_from_pipeline(
        opts,
        &action_ctx,
        build_health_report_pipeline_input(
            build,
            analysis_data,
            vital_data,
            derived_sections,
            findings,
            framework_health,
        ),
    );

    HealthOutputParts {
        report,
        grouping,
        timings,
        coverage_gaps_has_findings,
    }
}

fn build_health_report_pipeline_input<R>(
    build: &HealthOutputBuildInput<'_, R>,
    analysis_data: HealthAnalysisData,
    vital_data: HealthVitalData,
    derived_sections: HealthDerivedSections,
    findings: Vec<ComplexityViolation>,
    framework_health: Option<fallow_output::FrameworkHealthDiagnostics>,
) -> HealthReportPipelineInput {
    HealthReportPipelineInput {
        report_coverage_gaps: build.report_coverage_gaps,
        findings,
        threshold_overrides: build.threshold_overrides.clone(),
        files_analyzed: build.files_analyzed,
        total_functions: build.total_functions,
        total_above_threshold: build.total_above_threshold,
        max_cyclomatic: build.max_cyclomatic,
        max_cognitive: build.max_cognitive,
        max_crap: build.max_crap,
        analysis_data,
        vital_data,
        hotspots: derived_sections.hotspots,
        hotspot_summary: derived_sections.hotspot_summary,
        targets: derived_sections.targets,
        target_thresholds: derived_sections.target_thresholds,
        has_istanbul_coverage: build.has_istanbul_coverage,
        framework_health,
        sev_critical: build.sev_critical,
        sev_high: build.sev_high,
        sev_moderate: build.sev_moderate,
    }
}

#[derive(Clone, Copy)]
struct HealthSupportingPartsInput<'a, R> {
    opts: &'a HealthOptions<'a>,
    build: &'a HealthOutputBuildInput<'a, R>,
    analysis_data: &'a HealthAnalysisData,
    derived_sections: &'a HealthDerivedSections,
    vital_data: &'a HealthVitalData,
    findings: &'a [ComplexityViolation],
    action_ctx: &'a fallow_output::HealthActionContext,
}

#[expect(
    clippy::needless_pass_by_value,
    reason = "input is a Copy struct; by-value matches the original CLI signature"
)]
fn build_health_supporting_parts<R: super::HealthGroupResolver>(
    input: HealthSupportingPartsInput<'_, R>,
) -> HealthOutputSupportingParts {
    let grouping = build_health_output_grouping(&input);
    let timings = build_health_timings_from_pipeline(
        input.opts,
        input.build.start,
        input.analysis_data,
        input.derived_sections,
        &input.build.timing_base,
    );

    HealthOutputSupportingParts { grouping, timings }
}

fn build_health_output_grouping<R: super::HealthGroupResolver>(
    input: &HealthSupportingPartsInput<'_, R>,
) -> Option<fallow_output::HealthGrouping> {
    let file_scores = health_file_scores_slice(input.analysis_data.score_output.as_ref());
    build_health_grouping_from_context(HealthGroupingContextInput {
        opts: input.opts,
        config: input.build.config,
        group_resolver: input.build.group_resolver,
        candidate_paths: &input.derived_sections.candidate_paths,
        files: input.build.files,
        modules: input.build.modules,
        file_paths: input.build.file_paths,
        score_output: input.analysis_data.score_output.as_ref(),
        file_scores,
        findings: input.findings,
        hotspots: &input.derived_sections.hotspots,
        vital_data: input.vital_data,
        targets: &input.derived_sections.targets,
        needs_file_scores: input.build.needs_file_scores,
        action_ctx: input.action_ctx,
    })
}

struct HealthReportPipelineInput {
    report_coverage_gaps: bool,
    findings: Vec<ComplexityViolation>,
    threshold_overrides: Vec<fallow_output::ThresholdOverrideState>,
    files_analyzed: usize,
    total_functions: usize,
    total_above_threshold: usize,
    max_cyclomatic: u16,
    max_cognitive: u16,
    max_crap: f64,
    analysis_data: HealthAnalysisData,
    vital_data: HealthVitalData,
    hotspots: Vec<HotspotEntry>,
    hotspot_summary: Option<HotspotSummary>,
    targets: Vec<RefactoringTarget>,
    target_thresholds: Option<fallow_output::TargetThresholds>,
    has_istanbul_coverage: bool,
    framework_health: Option<fallow_output::FrameworkHealthDiagnostics>,
    sev_critical: usize,
    sev_high: usize,
    sev_moderate: usize,
}

fn build_health_report_from_pipeline(
    opts: &HealthOptions<'_>,
    action_ctx: &fallow_output::HealthActionContext,
    input: HealthReportPipelineInput,
) -> fallow_output::HealthReport {
    assemble_health_report(
        opts,
        action_ctx,
        HealthReportAssembly {
            report_coverage_gaps: input.report_coverage_gaps,
            findings: input.findings,
            threshold_overrides: input.threshold_overrides,
            files_analyzed: input.files_analyzed,
            total_functions: input.total_functions,
            total_above_threshold: input.total_above_threshold,
            max_cyclomatic: input.max_cyclomatic,
            max_cognitive: input.max_cognitive,
            max_crap: input.max_crap,
            files_scored: input.analysis_data.files_scored,
            average_maintainability: input.analysis_data.average_maintainability,
            vital_signs: input.vital_data.vital_signs,
            health_score: input.vital_data.health_score,
            score_output: input.analysis_data.score_output,
            hotspots: input.hotspots,
            hotspot_summary: input.hotspot_summary,
            targets: input.targets,
            target_thresholds: input.target_thresholds,
            health_trend: input.vital_data.health_trend,
            has_istanbul_coverage: input.has_istanbul_coverage,
            runtime_coverage: input.analysis_data.runtime_coverage,
            framework_health: input.framework_health,
            large_functions: input.vital_data.large_functions,
            sev_critical: input.sev_critical,
            sev_high: input.sev_high,
            sev_moderate: input.sev_moderate,
        },
    )
}

#[derive(Clone, Copy)]
struct HealthGroupingContextInput<'a, R> {
    opts: &'a HealthOptions<'a>,
    config: &'a ResolvedConfig,
    group_resolver: Option<&'a R>,
    candidate_paths: &'a rustc_hash::FxHashSet<std::path::PathBuf>,
    files: &'a [fallow_types::discover::DiscoveredFile],
    modules: &'a [crate::source::ModuleInfo],
    file_paths: &'a rustc_hash::FxHashMap<crate::discover::FileId, &'a std::path::PathBuf>,
    score_output: Option<&'a scoring::FileScoreOutput>,
    file_scores: &'a [FileHealthScore],
    findings: &'a [ComplexityViolation],
    hotspots: &'a [HotspotEntry],
    vital_data: &'a HealthVitalData,
    targets: &'a [RefactoringTarget],
    needs_file_scores: bool,
    action_ctx: &'a fallow_output::HealthActionContext,
}

#[expect(
    clippy::needless_pass_by_value,
    reason = "input is a Copy struct; by-value matches the original CLI signature"
)]
fn build_health_grouping_from_context<R: super::HealthGroupResolver>(
    input: HealthGroupingContextInput<'_, R>,
) -> Option<fallow_output::HealthGrouping> {
    build_optional_health_grouping_opt(
        input.group_resolver,
        &input.config.root,
        input.candidate_paths,
        &grouping::HealthGroupingInput {
            files: input.files,
            modules: input.modules,
            file_paths: input.file_paths,
            score_output: input.score_output,
            file_scores: input.file_scores,
            findings: input.findings,
            hotspots: input.hotspots,
            large_functions: &input.vital_data.large_functions,
            targets: input.targets,
            score_requested: input.opts.score,
            duplicates_config: input.opts.score.then_some(&input.config.duplicates),
            needs_file_scores: input.needs_file_scores,
            needs_hotspots: input.opts.hotspots || input.opts.targets,
            show_vital_signs: !input.opts.score_only_output,
            action_ctx: input.action_ctx,
        },
    )
}

fn health_coverage_gaps_has_findings(score_output: Option<&scoring::FileScoreOutput>) -> bool {
    score_output.is_some_and(|output| !output.coverage.report.is_empty())
}

fn build_health_timings_from_pipeline(
    opts: &HealthOptions<'_>,
    start: &Instant,
    analysis_data: &HealthAnalysisData,
    sections: &HealthDerivedSections,
    input: &HealthTimingBaseInput,
) -> Option<HealthTimings> {
    build_health_timings(
        opts,
        start,
        &HealthTimingInput {
            config_ms: input.config_ms,
            discover_ms: input.discover_ms,
            parse_ms: input.parse_ms,
            parse_cpu_ms: input.parse_cpu_ms,
            complexity_ms: input.complexity_ms,
            file_scores_ms: analysis_data.file_scores_ms,
            git_churn_ms: analysis_data.git_churn_ms,
            git_churn_cache_hit: analysis_data.git_churn_cache_hit,
            hotspots_ms: sections.hotspots_ms,
            duplication_ms: sections.duplication_ms,
            targets_ms: sections.targets_ms,
            shared_parse: input.shared_parse,
        },
    )
}

fn build_optional_health_grouping_opt<R: super::HealthGroupResolver>(
    resolver: Option<&R>,
    project_root: &std::path::Path,
    candidate_paths: &rustc_hash::FxHashSet<std::path::PathBuf>,
    input: &grouping::HealthGroupingInput<'_>,
) -> Option<HealthGrouping> {
    let resolver = resolver?;
    Some(grouping::build_health_grouping(
        resolver as &dyn super::HealthGroupResolver,
        project_root,
        candidate_paths,
        input,
    ))
}
