use crate::config::{CheckerConfig, CustomRuleConfig, CustomRuleMatchType, CustomRuleTarget};
use crate::model::{
    Diagnostic, Expression, ExpressionKind, FixHint, FixKind, FunctionDef, Module, RuleId,
    Severity, Statement, StatementKind,
};
use std::collections::BTreeSet;

pub trait Rule {
    fn id(&self) -> RuleId;
    fn check(&self, module: &Module, function: &FunctionDef) -> Vec<Diagnostic>;
}

pub type RuleFactory = fn() -> Box<dyn Rule>;

pub fn run_rules(
    module: &Module,
    function: &FunctionDef,
    config: &CheckerConfig,
) -> Vec<Diagnostic> {
    let mut diagnostics = Vec::new();
    for rule in registered_rule_factories().iter().map(|factory| factory()) {
        if config.is_rule_enabled(&rule.id()) {
            diagnostics.extend(rule.check(module, function));
        }
    }
    diagnostics.extend(run_custom_rules(function, config));
    diagnostics
}

#[derive(Default)]
struct MutableGlobalStateRule;
#[derive(Default)]
struct AssignmentOveruseRule;
#[derive(Default)]
struct DirectIoRule;
#[derive(Default)]
struct NonDeterministicUseRule;
#[derive(Default)]
struct ExternalStateSideEffectRule;
#[derive(Default)]
struct MutableDefaultArgumentRule;
#[derive(Default)]
struct DirectEffectfulCallRule;
#[derive(Default)]
struct GlobalNonlocalUsageRule;
#[derive(Default)]
struct HiddenMutationRule;
#[derive(Default)]
struct LoopSideEffectRule;
#[derive(Default)]
struct ExceptionControlFlowRule;
#[derive(Default)]
struct ClassMethodSideEffectRule;
#[derive(Default)]
struct ReferentialTransparencyBreakRule;
#[derive(Default)]
struct DeprecatedApiUseRule;

fn registered_rule_factories() -> &'static [RuleFactory] {
    &[
        make_rule::<MutableGlobalStateRule>,
        make_rule::<AssignmentOveruseRule>,
        make_rule::<DirectIoRule>,
        make_rule::<NonDeterministicUseRule>,
        make_rule::<ExternalStateSideEffectRule>,
        make_rule::<MutableDefaultArgumentRule>,
        make_rule::<DirectEffectfulCallRule>,
        make_rule::<GlobalNonlocalUsageRule>,
        make_rule::<HiddenMutationRule>,
        make_rule::<LoopSideEffectRule>,
        make_rule::<ExceptionControlFlowRule>,
        make_rule::<ClassMethodSideEffectRule>,
        make_rule::<ReferentialTransparencyBreakRule>,
        make_rule::<DeprecatedApiUseRule>,
    ]
}

fn make_rule<T: Rule + Default + 'static>() -> Box<dyn Rule> {
    Box::new(T::default())
}

impl Rule for MutableGlobalStateRule {
    fn id(&self) -> RuleId {
        RuleId::MutableGlobalState
    }

    fn check(&self, module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        let mutable_globals = module
            .globals
            .iter()
            .filter(|binding| binding.is_mutable)
            .map(|binding| binding.name.clone())
            .collect::<BTreeSet<_>>();
        if mutable_globals.is_empty() {
            return Vec::new();
        }

        let local_bindings = function
            .local_bindings
            .iter()
            .cloned()
            .collect::<BTreeSet<_>>();
        let declared_globals = collect_declared_globals(function);
        let mut diagnostics = Vec::new();
        let mut seen = BTreeSet::new();

        walk_function(function, &mut |statement, expression| {
            if let Some(expression) = expression {
                if let Some(base_name) = expression.base_name() {
                    let is_global = mutable_globals.contains(&base_name)
                        && (!local_bindings.contains(&base_name)
                            || declared_globals.contains(&base_name));
                    if is_global && seen.insert(("ref".to_owned(), base_name.clone())) {
                        diagnostics.push(build_diagnostic(
                            RuleId::MutableGlobalState,
                            function,
                            expression.location.clone(),
                            format!("可変なグローバル状態 `{base_name}` を参照しています。"),
                            Some(
                                "引数で値を受け取り、関数の外側で状態管理してください。".to_owned(),
                            ),
                        ));
                    }
                }
            }

            if let Some(statement) = statement {
                if let StatementKind::Assign { targets, .. } = &statement.kind {
                    for target in targets {
                        if let Some(base_name) = target.base_name() {
                            let is_global = mutable_globals.contains(&base_name)
                                && (!local_bindings.contains(&base_name)
                                    || declared_globals.contains(&base_name));
                            if is_global && seen.insert(("write".to_owned(), base_name.clone())) {
                                diagnostics.push(build_diagnostic(
                                    RuleId::MutableGlobalState,
                                    function,
                                    target.location.clone(),
                                    format!("可変なグローバル状態 `{base_name}` を更新しています。"),
                                    Some("更新結果を戻り値として返し、呼び出し側で反映してください。".to_owned()),
                                ));
                            }
                        }
                    }
                }
            }
        });

        diagnostics
    }
}

impl Rule for AssignmentOveruseRule {
    fn id(&self) -> RuleId {
        RuleId::AssignmentOveruse
    }

    fn check(&self, _module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        let mut assignment_count = 0usize;
        walk_function(function, &mut |statement, _expression| {
            if matches!(
                statement.map(|statement| &statement.kind),
                Some(StatementKind::Assign { .. })
            ) {
                assignment_count += 1;
            }
        });

        if assignment_count >= 4 {
            vec![build_diagnostic(
                RuleId::AssignmentOveruse,
                function,
                function.location.clone(),
                format!("破壊的な代入が {assignment_count} 箇所あり、状態変化が多すぎます。"),
                Some("中間状態を減らし、小さな純関数へ分割してください。".to_owned()),
            )]
        } else {
            Vec::new()
        }
    }
}

impl Rule for DirectIoRule {
    fn id(&self) -> RuleId {
        RuleId::DirectIo
    }

    fn check(&self, _module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        let mut diagnostics = Vec::new();
        walk_function(function, &mut |_, expression| {
            if let Some(call) = expression
                .and_then(as_call)
                .filter(|path| is_direct_io_call(path))
            {
                diagnostics.push(build_diagnostic(
                    RuleId::DirectIo,
                    function,
                    expression.expect("expression exists").location.clone(),
                    format!("I/O を直接実行する呼び出し `{call}` を検出しました。"),
                    Some(
                        "I/O は関数の外側へ分離し、必要な値だけを引数で渡してください。".to_owned(),
                    ),
                ));
            }
        });
        diagnostics
    }
}

impl Rule for NonDeterministicUseRule {
    fn id(&self) -> RuleId {
        RuleId::NonDeterministicUse
    }

    fn check(&self, _module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        let mut diagnostics = Vec::new();
        walk_function(function, &mut |_, expression| {
            if let Some(call) = expression
                .and_then(as_call)
                .filter(|path| is_non_deterministic_call(path))
            {
                diagnostics.push(build_diagnostic(
                    RuleId::NonDeterministicUse,
                    function,
                    expression.expect("expression exists").location.clone(),
                    format!("非決定的な呼び出し `{call}` を検出しました。"),
                    Some("時刻や乱数は引数として注入してください。".to_owned()),
                ));
            }
        });
        diagnostics
    }
}

impl Rule for ExternalStateSideEffectRule {
    fn id(&self) -> RuleId {
        RuleId::ExternalStateSideEffect
    }

    fn check(&self, _module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        let mut diagnostics = Vec::new();
        walk_function(function, &mut |statement, expression| {
            if let Some(statement) = statement {
                if let StatementKind::Assign { targets, .. } = &statement.kind {
                    for target in targets {
                        if matches!(
                            target.kind,
                            ExpressionKind::Attribute { .. } | ExpressionKind::Subscript { .. }
                        ) {
                            diagnostics.push(build_diagnostic(
                                RuleId::ExternalStateSideEffect,
                                function,
                                target.location.clone(),
                                "外部状態に対する代入を検出しました。".to_owned(),
                                Some("新しい値を構築して戻り値で返してください。".to_owned()),
                            ));
                        }
                    }
                }
            }

            if let Some(expression) = expression {
                if let Some(call) = as_call(expression).filter(|path| is_side_effect_call(path)) {
                    diagnostics.push(build_diagnostic(
                        RuleId::ExternalStateSideEffect,
                        function,
                        expression.location.clone(),
                        format!(
                            "外部状態を変化させる可能性のある呼び出し `{call}` を検出しました。"
                        ),
                        Some("副作用を境界層へ押し出してください。".to_owned()),
                    ));
                }
            }
        });
        diagnostics
    }
}

impl Rule for MutableDefaultArgumentRule {
    fn id(&self) -> RuleId {
        RuleId::MutableDefaultArgument
    }

    fn check(&self, _module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        function
            .parameters
            .iter()
            .filter_map(|parameter| {
                parameter
                    .default
                    .as_ref()
                    .filter(|default| default.is_mutable_value())
                    .map(|_| {
                        build_diagnostic(
                            RuleId::MutableDefaultArgument,
                            function,
                            parameter.location.clone(),
                            format!(
                                "引数 `{}` がミュータブルなデフォルト値を持っています。",
                                parameter.name
                            ),
                            Some(
                                "`None` をデフォルトにし、関数内で新しい値を生成してください。"
                                    .to_owned(),
                            ),
                        )
                    })
            })
            .collect()
    }
}

impl Rule for DirectEffectfulCallRule {
    fn id(&self) -> RuleId {
        RuleId::DirectEffectfulCall
    }

    fn check(&self, _module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        let mut diagnostics = Vec::new();
        walk_function(function, &mut |_, expression| {
            if let Some(call) = expression
                .and_then(as_call)
                .filter(|path| is_direct_effectful_call(path))
            {
                diagnostics.push(build_diagnostic(
                    RuleId::DirectEffectfulCall,
                    function,
                    expression.expect("expression exists").location.clone(),
                    format!("副作用を持つ呼び出し `{call}` を直接実行しています。"),
                    Some("呼び出し自体を抽象化し、関数の外から注入してください。".to_owned()),
                ));
            }
        });
        diagnostics
    }
}

impl Rule for GlobalNonlocalUsageRule {
    fn id(&self) -> RuleId {
        RuleId::GlobalNonlocalUsage
    }

    fn check(&self, _module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        let mut diagnostics = Vec::new();
        walk_function(function, &mut |statement, _expression| {
            if let Some(statement) = statement {
                match &statement.kind {
                    StatementKind::Global { names } => diagnostics.push(build_diagnostic(
                        RuleId::GlobalNonlocalUsage,
                        function,
                        statement.location.clone(),
                        format!("`global` の使用を検出しました: {}", names.join(", ")),
                        Some("状態は明示的に引数と戻り値で受け渡してください。".to_owned()),
                    )),
                    StatementKind::Nonlocal { names } => diagnostics.push(build_diagnostic(
                        RuleId::GlobalNonlocalUsage,
                        function,
                        statement.location.clone(),
                        format!("`nonlocal` の使用を検出しました: {}", names.join(", ")),
                        Some("外側の状態を捕捉せず、値を明示的に受け渡してください。".to_owned()),
                    )),
                    _ => {}
                }
            }
        });
        diagnostics
    }
}

impl Rule for HiddenMutationRule {
    fn id(&self) -> RuleId {
        RuleId::HiddenMutation
    }

    fn check(&self, _module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        let parameter_names = function
            .parameters
            .iter()
            .map(|parameter| parameter.name.clone())
            .collect::<BTreeSet<_>>();
        let local_bindings = function
            .local_bindings
            .iter()
            .cloned()
            .collect::<BTreeSet<_>>();
        let mut diagnostics = Vec::new();
        let mut seen = BTreeSet::new();

        walk_function(function, &mut |_, expression| {
            let Some(expression) = expression else {
                return;
            };
            let ExpressionKind::Call {
                function: callee, ..
            } = &expression.kind
            else {
                return;
            };
            let Some(path) = callee.path() else {
                return;
            };
            let Some(base_name) = callee.base_name() else {
                return;
            };

            if is_in_place_mutation_call(&path)
                && (parameter_names.contains(&base_name) || local_bindings.contains(&base_name))
                && seen.insert((
                    expression.location.start.line,
                    expression.location.start.column,
                    path.clone(),
                ))
            {
                diagnostics.push(build_diagnostic(
                    RuleId::HiddenMutation,
                    function,
                    expression.location.clone(),
                    format!(
                        "ローカル状態 `{base_name}` を破壊的に更新する呼び出し `{path}` を検出しました。"
                    ),
                    Some(
                        "新しいコレクションを返す形へ変換し、破壊的変更を避けてください。"
                            .to_owned(),
                    ),
                ));
            }
        });

        diagnostics
    }
}

impl Rule for LoopSideEffectRule {
    fn id(&self) -> RuleId {
        RuleId::LoopSideEffect
    }

    fn check(&self, _module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        let mut diagnostics = Vec::new();
        let mut seen = BTreeSet::new();
        collect_loop_side_effects(&function.body, 0, function, &mut diagnostics, &mut seen);
        diagnostics
    }
}

impl Rule for ExceptionControlFlowRule {
    fn id(&self) -> RuleId {
        RuleId::ExceptionControlFlow
    }

    fn check(&self, _module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        let mut raise_count = 0usize;
        let mut handler_count = 0usize;
        let mut first_location = None;

        walk_function(function, &mut |statement, _expression| {
            let Some(statement) = statement else {
                return;
            };
            match &statement.kind {
                StatementKind::Raise { .. } => {
                    raise_count += 1;
                    if first_location.is_none() {
                        first_location = Some(statement.location.clone());
                    }
                }
                StatementKind::Try { handlers, .. } if !handlers.is_empty() => {
                    handler_count += handlers.len();
                    if first_location.is_none() {
                        first_location = Some(statement.location.clone());
                    }
                }
                _ => {}
            }
        });

        if raise_count + handler_count >= 2 {
            vec![build_diagnostic(
                RuleId::ExceptionControlFlow,
                function,
                first_location.unwrap_or_else(|| function.location.clone()),
                format!(
                    "例外を制御フローとして利用しています (raise={raise_count}, except={handler_count})。"
                ),
                Some("結果型や明示的な分岐で失敗を表現してください。".to_owned()),
            )]
        } else {
            Vec::new()
        }
    }
}

impl Rule for ClassMethodSideEffectRule {
    fn id(&self) -> RuleId {
        RuleId::ClassMethodSideEffect
    }

    fn check(&self, _module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        let Some(class_name) = function.class_name.as_deref() else {
            return Vec::new();
        };

        let mut diagnostics = Vec::new();
        let mut seen = BTreeSet::new();

        walk_function(function, &mut |statement, expression| {
            if let Some(statement) = statement {
                if let StatementKind::Assign { targets, .. } = &statement.kind {
                    for target in targets {
                        let Some(target_path) = class_state_target_path(target) else {
                            continue;
                        };
                        if seen.insert((
                            target.location.start.line,
                            target.location.start.column,
                            target_path.clone(),
                        )) {
                            diagnostics.push(build_diagnostic(
                                RuleId::ClassMethodSideEffect,
                                function,
                                target.location.clone(),
                                format!(
                                    "クラス `{class_name}` のメソッド内でインスタンス状態 `{target_path}` を更新しています。"
                                ),
                                Some(
                                    "オブジェクト内部を直接変更せず、新しい状態を返して外側で反映してください。"
                                        .to_owned(),
                                ),
                            ));
                        }
                    }
                }
            }

            let Some(expression) = expression else {
                return;
            };
            let Some(path) = as_call(expression).filter(|path| is_class_state_mutation_call(path))
            else {
                return;
            };

            if seen.insert((
                expression.location.start.line,
                expression.location.start.column,
                path.clone(),
            )) {
                diagnostics.push(build_diagnostic(
                    RuleId::ClassMethodSideEffect,
                    function,
                    expression.location.clone(),
                    format!(
                        "クラス `{class_name}` のメソッド内でオブジェクト状態を変化させる呼び出し `{path}` を検出しました。"
                    ),
                    Some(
                        "状態更新をメソッド内部へ閉じ込めず、新しい値を組み立てて返してください。"
                            .to_owned(),
                    ),
                ));
            }
        });

        diagnostics
    }
}

impl Rule for ReferentialTransparencyBreakRule {
    fn id(&self) -> RuleId {
        RuleId::ReferentialTransparencyBreak
    }

    fn check(&self, _module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        let mut diagnostics = Vec::new();
        let mut seen = BTreeSet::new();

        walk_function(function, &mut |_, expression| {
            let Some(expression) = expression else {
                return;
            };

            if let Some(call) = as_call(expression) {
                if let Some(hint) = referential_transparency_call_hint(&call) {
                    if seen.insert((
                        expression.location.start.line,
                        expression.location.start.column,
                        call.clone(),
                    )) {
                        diagnostics.push(build_diagnostic(
                            RuleId::ReferentialTransparencyBreak,
                            function,
                            expression.location.clone(),
                            format!(
                                "参照透過性を壊す暗黙依存または動的評価 `{call}` を検出しました。"
                            ),
                            Some(hint.to_owned()),
                        ));
                    }
                    return;
                }
            }

            if let Some(reference) = referential_transparency_reference_path(expression) {
                if seen.insert((
                    expression.location.start.line,
                    expression.location.start.column,
                    reference.clone(),
                )) {
                    diagnostics.push(build_diagnostic(
                        RuleId::ReferentialTransparencyBreak,
                        function,
                        expression.location.clone(),
                        format!("参照透過性を壊す暗黙参照 `{reference}` を検出しました。"),
                        Some(
                            "環境やプロセス状態への依存は呼び出し側で解決し、必要な値だけを引数で受け取ってください。"
                                .to_owned(),
                        ),
                    ));
                }
            }
        });

        diagnostics
    }
}

impl Rule for DeprecatedApiUseRule {
    fn id(&self) -> RuleId {
        RuleId::DeprecatedApiUse
    }

    fn check(&self, _module: &Module, function: &FunctionDef) -> Vec<Diagnostic> {
        let mut diagnostics = Vec::new();
        let mut seen = BTreeSet::new();

        walk_function(function, &mut |_, expression| {
            let Some(expression) = expression else {
                return;
            };
            let Some(call) = as_call(expression) else {
                return;
            };
            let Some(replacement) = deprecated_api_replacement(&call) else {
                return;
            };

            if seen.insert((
                expression.location.start.line,
                expression.location.start.column,
                call.clone(),
            )) {
                diagnostics.push(build_diagnostic(
                    RuleId::DeprecatedApiUse,
                    function,
                    expression.location.clone(),
                    format!("非推奨 API `{call}` を検出しました。"),
                    Some(replacement.to_owned()),
                ));
            }
        });

        diagnostics
    }
}

fn build_diagnostic(
    rule_id: RuleId,
    function: &FunctionDef,
    location: crate::model::SourceSpan,
    message: impl Into<String>,
    hint: Option<String>,
) -> Diagnostic {
    let fix_hint = build_fix_hint(&rule_id, hint.as_deref());
    build_diagnostic_with_severity(
        rule_id,
        Severity::Warning,
        function,
        location,
        message,
        hint,
        fix_hint,
    )
}

fn build_diagnostic_with_severity(
    rule_id: RuleId,
    severity: Severity,
    function: &FunctionDef,
    location: crate::model::SourceSpan,
    message: impl Into<String>,
    hint: Option<String>,
    fix_hint: Option<FixHint>,
) -> Diagnostic {
    Diagnostic::new(
        rule_id,
        severity,
        message,
        Some(function.qualified_name.clone()),
        location,
        hint,
        fix_hint,
    )
}

fn build_fix_hint(rule_id: &RuleId, hint: Option<&str>) -> Option<FixHint> {
    let suggested_action = hint?.to_owned();
    let (kind, summary) = match rule_id {
        RuleId::ParseError => (FixKind::ManualReview, "構文エラーを解消してください。"),
        RuleId::MutableGlobalState | RuleId::ExternalStateSideEffect => (
            FixKind::ReturnNewValue,
            "状態更新を戻り値ベースへ変換してください。",
        ),
        RuleId::DirectIo | RuleId::DirectEffectfulCall => (
            FixKind::ExtractEffectBoundary,
            "副作用を関数の境界へ分離してください。",
        ),
        RuleId::NonDeterministicUse => (
            FixKind::InjectDependency,
            "非決定的な値は外から注入してください。",
        ),
        RuleId::MutableDefaultArgument => (
            FixKind::ReplaceMutableDefault,
            "ミュータブルなデフォルト値を置き換えてください。",
        ),
        RuleId::GlobalNonlocalUsage => (
            FixKind::ReturnNewValue,
            "暗黙的な共有状態を明示的な受け渡しへ変換してください。",
        ),
        RuleId::HiddenMutation => (
            FixKind::AvoidInPlaceMutation,
            "破壊的変更を避けて新しい値を返してください。",
        ),
        RuleId::LoopSideEffect => (
            FixKind::EliminateLoopEffect,
            "ループ内副作用をループ外へ移動してください。",
        ),
        RuleId::ExceptionControlFlow => (
            FixKind::ReplaceExceptionFlow,
            "例外ベースの分岐を明示的な条件分岐へ置き換えてください。",
        ),
        RuleId::ClassMethodSideEffect => (
            FixKind::ReturnNewValue,
            "オブジェクト内部状態の直接変更を避けてください。",
        ),
        RuleId::ReferentialTransparencyBreak => (
            FixKind::InjectDependency,
            "暗黙依存を引数または明示的な依存へ変換してください。",
        ),
        RuleId::DeprecatedApiUse => (
            FixKind::ManualReview,
            "非推奨 API を推奨 API へ置き換えてください。",
        ),
        RuleId::AssignmentOveruse => (
            FixKind::ReturnNewValue,
            "中間状態を減らし、小さな純関数へ分割してください。",
        ),
        RuleId::Custom(_) => (
            FixKind::ManualReview,
            "カスタムルールの指摘内容を確認してください。",
        ),
    };

    Some(FixHint {
        kind,
        summary: summary.to_owned(),
        suggested_action,
    })
}

fn run_custom_rules(function: &FunctionDef, config: &CheckerConfig) -> Vec<Diagnostic> {
    let mut diagnostics = Vec::new();

    for rule in &config.custom_rules {
        if !rule.enabled || !config.is_rule_enabled(&rule.id) {
            continue;
        }
        diagnostics.extend(run_custom_rule(function, rule));
    }

    diagnostics
}

fn run_custom_rule(function: &FunctionDef, rule: &CustomRuleConfig) -> Vec<Diagnostic> {
    let mut diagnostics = Vec::new();
    let mut seen = BTreeSet::new();

    walk_function(function, &mut |statement, expression| match rule.target {
        CustomRuleTarget::Call => {
            let Some(expression) = expression else {
                return;
            };
            let Some(path) = as_call(expression) else {
                return;
            };
            if matches_custom_rule(rule, &path)
                && seen.insert((
                    rule.id.as_str().to_owned(),
                    expression.location.start.line,
                    expression.location.start.column,
                    path.clone(),
                ))
            {
                diagnostics.push(build_custom_diagnostic(
                    rule,
                    function,
                    expression.location.clone(),
                    &path,
                ));
            }
        }
        CustomRuleTarget::Name => {
            let Some(expression) = expression else {
                return;
            };
            if !matches!(
                expression.kind,
                ExpressionKind::Name { .. } | ExpressionKind::Attribute { .. }
            ) {
                return;
            }
            let Some(path) = expression.path() else {
                return;
            };
            if matches_custom_rule(rule, &path)
                && seen.insert((
                    rule.id.as_str().to_owned(),
                    expression.location.start.line,
                    expression.location.start.column,
                    path.clone(),
                ))
            {
                diagnostics.push(build_custom_diagnostic(
                    rule,
                    function,
                    expression.location.clone(),
                    &path,
                ));
            }
        }
        CustomRuleTarget::AttributeWrite => {
            let Some(statement) = statement else {
                return;
            };
            let StatementKind::Assign { targets, .. } = &statement.kind else {
                return;
            };

            for target in targets {
                if !matches!(
                    target.kind,
                    ExpressionKind::Attribute { .. } | ExpressionKind::Subscript { .. }
                ) {
                    continue;
                }
                let candidate = target
                    .path()
                    .or_else(|| target.base_name())
                    .unwrap_or_else(|| "<unknown>".to_owned());
                if matches_custom_rule(rule, &candidate)
                    && seen.insert((
                        rule.id.as_str().to_owned(),
                        target.location.start.line,
                        target.location.start.column,
                        candidate.clone(),
                    ))
                {
                    diagnostics.push(build_custom_diagnostic(
                        rule,
                        function,
                        target.location.clone(),
                        &candidate,
                    ));
                }
            }
        }
    });

    diagnostics
}

fn build_custom_diagnostic(
    rule: &CustomRuleConfig,
    function: &FunctionDef,
    location: crate::model::SourceSpan,
    matched: &str,
) -> Diagnostic {
    let message = render_custom_text(&rule.message, matched);
    let hint = rule
        .hint
        .as_ref()
        .map(|hint| render_custom_text(hint, matched));
    let fix_hint = hint.as_ref().map(|hint| FixHint {
        kind: FixKind::ManualReview,
        summary: format!("カスタムルール `{}` の指摘を確認してください。", rule.id),
        suggested_action: hint.clone(),
    });

    Diagnostic::new(
        rule.id.clone(),
        rule.severity,
        message,
        Some(function.qualified_name.clone()),
        location,
        hint,
        fix_hint,
    )
}

fn matches_custom_rule(rule: &CustomRuleConfig, candidate: &str) -> bool {
    rule.patterns.iter().any(|pattern| match rule.match_type {
        CustomRuleMatchType::Exact => candidate == pattern,
        CustomRuleMatchType::Prefix => candidate.starts_with(pattern),
        CustomRuleMatchType::Suffix => candidate.ends_with(pattern),
        CustomRuleMatchType::Contains => candidate.contains(pattern),
    })
}

fn render_custom_text(template: &str, matched: &str) -> String {
    template.replace("{match}", matched)
}

fn walk_function(
    function: &FunctionDef,
    visitor: &mut impl FnMut(Option<&Statement>, Option<&Expression>),
) {
    for decorator in &function.decorators {
        walk_expression(decorator, visitor);
    }
    if let Some(returns) = &function.returns {
        walk_expression(returns, visitor);
    }
    for statement in &function.body {
        walk_statement(statement, visitor);
    }
}

fn walk_statement(
    statement: &Statement,
    visitor: &mut impl FnMut(Option<&Statement>, Option<&Expression>),
) {
    visitor(Some(statement), None);
    match &statement.kind {
        StatementKind::FunctionDef(_)
        | StatementKind::ClassDef { .. }
        | StatementKind::Import { .. }
        | StatementKind::Global { .. }
        | StatementKind::Nonlocal { .. }
        | StatementKind::ControlFlow { .. }
        | StatementKind::Other { .. } => {}
        StatementKind::Return { value } => {
            if let Some(value) = value {
                walk_expression(value, visitor);
            }
        }
        StatementKind::Delete { targets } => {
            for target in targets {
                walk_expression(target, visitor);
            }
        }
        StatementKind::Assign {
            targets,
            value,
            annotation,
            ..
        } => {
            for target in targets {
                walk_expression(target, visitor);
            }
            if let Some(value) = value {
                walk_expression(value, visitor);
            }
            if let Some(annotation) = annotation {
                walk_expression(annotation, visitor);
            }
        }
        StatementKind::For {
            target,
            iter,
            body,
            orelse,
            ..
        } => {
            walk_expression(target, visitor);
            walk_expression(iter, visitor);
            for nested in body {
                walk_statement(nested, visitor);
            }
            for nested in orelse {
                walk_statement(nested, visitor);
            }
        }
        StatementKind::While { test, body, orelse } | StatementKind::If { test, body, orelse } => {
            walk_expression(test, visitor);
            for nested in body {
                walk_statement(nested, visitor);
            }
            for nested in orelse {
                walk_statement(nested, visitor);
            }
        }
        StatementKind::With { items, body, .. } => {
            for item in items {
                walk_expression(&item.context_expr, visitor);
                if let Some(optional_vars) = &item.optional_vars {
                    walk_expression(optional_vars, visitor);
                }
            }
            for nested in body {
                walk_statement(nested, visitor);
            }
        }
        StatementKind::Try {
            body,
            handlers,
            orelse,
            finalbody,
        } => {
            for nested in body {
                walk_statement(nested, visitor);
            }
            for handler in handlers {
                if let Some(type_expr) = &handler.type_expr {
                    walk_expression(type_expr, visitor);
                }
                for nested in &handler.body {
                    walk_statement(nested, visitor);
                }
            }
            for nested in orelse {
                walk_statement(nested, visitor);
            }
            for nested in finalbody {
                walk_statement(nested, visitor);
            }
        }
        StatementKind::Raise { exc, cause } => {
            if let Some(exc) = exc {
                walk_expression(exc, visitor);
            }
            if let Some(cause) = cause {
                walk_expression(cause, visitor);
            }
        }
        StatementKind::Assert { test, message } => {
            walk_expression(test, visitor);
            if let Some(message) = message {
                walk_expression(message, visitor);
            }
        }
        StatementKind::Expression { expression } => walk_expression(expression, visitor),
    }
}

fn walk_expression(
    expression: &Expression,
    visitor: &mut impl FnMut(Option<&Statement>, Option<&Expression>),
) {
    visitor(None, Some(expression));
    match &expression.kind {
        ExpressionKind::Attribute { value, .. }
        | ExpressionKind::Unary { operand: value, .. }
        | ExpressionKind::Await { value }
        | ExpressionKind::YieldFrom { value }
        | ExpressionKind::Starred { value } => walk_expression(value, visitor),
        ExpressionKind::Call {
            function,
            args,
            keywords,
        } => {
            walk_expression(function, visitor);
            for argument in args {
                walk_expression(argument, visitor);
            }
            for keyword in keywords {
                walk_expression(&keyword.value, visitor);
            }
        }
        ExpressionKind::List { elements }
        | ExpressionKind::Tuple { elements }
        | ExpressionKind::Set { elements }
        | ExpressionKind::BoolOp {
            values: elements, ..
        } => {
            for element in elements {
                walk_expression(element, visitor);
            }
        }
        ExpressionKind::Dict { entries } => {
            for entry in entries {
                if let Some(key) = &entry.key {
                    walk_expression(key, visitor);
                }
                walk_expression(&entry.value, visitor);
            }
        }
        ExpressionKind::Binary { left, right, .. } => {
            walk_expression(left, visitor);
            walk_expression(right, visitor);
        }
        ExpressionKind::Compare {
            left, comparators, ..
        } => {
            walk_expression(left, visitor);
            for comparator in comparators {
                walk_expression(comparator, visitor);
            }
        }
        ExpressionKind::IfElse { test, body, orelse } => {
            walk_expression(test, visitor);
            walk_expression(body, visitor);
            walk_expression(orelse, visitor);
        }
        ExpressionKind::Subscript { value, slice } => {
            walk_expression(value, visitor);
            walk_expression(slice, visitor);
        }
        ExpressionKind::Yield { value } => {
            if let Some(value) = value {
                walk_expression(value, visitor);
            }
        }
        ExpressionKind::Named { target, value } => {
            walk_expression(target, visitor);
            walk_expression(value, visitor);
        }
        ExpressionKind::Slice { lower, upper, step } => {
            if let Some(lower) = lower {
                walk_expression(lower, visitor);
            }
            if let Some(upper) = upper {
                walk_expression(upper, visitor);
            }
            if let Some(step) = step {
                walk_expression(step, visitor);
            }
        }
        ExpressionKind::Name { .. }
        | ExpressionKind::Constant { .. }
        | ExpressionKind::Lambda
        | ExpressionKind::Other { .. } => {}
    }
}

fn collect_declared_globals(function: &FunctionDef) -> BTreeSet<String> {
    let mut names = BTreeSet::new();
    walk_function(function, &mut |statement, _| {
        if let Some(statement) = statement {
            if let StatementKind::Global { names: declared } = &statement.kind {
                names.extend(declared.iter().cloned());
            }
        }
    });
    names
}

fn collect_loop_side_effects(
    statements: &[Statement],
    loop_depth: usize,
    function: &FunctionDef,
    diagnostics: &mut Vec<Diagnostic>,
    seen: &mut BTreeSet<(usize, usize, String)>,
) {
    for statement in statements {
        match &statement.kind {
            StatementKind::For { body, orelse, .. } | StatementKind::While { body, orelse, .. } => {
                collect_loop_side_effects(body, loop_depth + 1, function, diagnostics, seen);
                collect_loop_side_effects(orelse, loop_depth, function, diagnostics, seen);
            }
            StatementKind::If { body, orelse, .. } => {
                if loop_depth > 0 {
                    collect_loop_effectful_calls_in_statement(
                        statement,
                        loop_depth,
                        function,
                        diagnostics,
                        seen,
                    );
                }
                collect_loop_side_effects(body, loop_depth, function, diagnostics, seen);
                collect_loop_side_effects(orelse, loop_depth, function, diagnostics, seen);
            }
            StatementKind::With { body, .. } => {
                if loop_depth > 0 {
                    collect_loop_effectful_calls_in_statement(
                        statement,
                        loop_depth,
                        function,
                        diagnostics,
                        seen,
                    );
                }
                collect_loop_side_effects(body, loop_depth, function, diagnostics, seen);
            }
            StatementKind::Try {
                body,
                handlers,
                orelse,
                finalbody,
            } => {
                if loop_depth > 0 {
                    collect_loop_effectful_calls_in_statement(
                        statement,
                        loop_depth,
                        function,
                        diagnostics,
                        seen,
                    );
                }
                collect_loop_side_effects(body, loop_depth, function, diagnostics, seen);
                for handler in handlers {
                    collect_loop_side_effects(
                        &handler.body,
                        loop_depth,
                        function,
                        diagnostics,
                        seen,
                    );
                }
                collect_loop_side_effects(orelse, loop_depth, function, diagnostics, seen);
                collect_loop_side_effects(finalbody, loop_depth, function, diagnostics, seen);
            }
            StatementKind::FunctionDef(_) | StatementKind::ClassDef { .. } => {}
            _ => {
                if loop_depth > 0 {
                    collect_loop_effectful_calls_in_statement(
                        statement,
                        loop_depth,
                        function,
                        diagnostics,
                        seen,
                    );
                }
            }
        }
    }
}

fn collect_loop_effectful_calls_in_statement(
    statement: &Statement,
    loop_depth: usize,
    function: &FunctionDef,
    diagnostics: &mut Vec<Diagnostic>,
    seen: &mut BTreeSet<(usize, usize, String)>,
) {
    if let StatementKind::Assign { targets, .. } = &statement.kind {
        for target in targets {
            if matches!(
                target.kind,
                ExpressionKind::Attribute { .. } | ExpressionKind::Subscript { .. }
            ) && seen.insert((
                target.location.start.line,
                target.location.start.column,
                "assign".to_owned(),
            )) {
                let weight = 2 + usize::from(loop_depth > 1);
                diagnostics.push(build_diagnostic_with_severity(
                    RuleId::LoopSideEffect,
                    severity_from_loop_weight(weight),
                    function,
                    target.location.clone(),
                    format!("ループ内で外部状態への代入を行っています (weight={weight})。"),
                    Some(
                        "ループの外で更新内容を集約し、最後に一度だけ反映してください。".to_owned(),
                    ),
                    build_fix_hint(
                        &RuleId::LoopSideEffect,
                        Some("ループの外で更新内容を集約し、最後に一度だけ反映してください。"),
                    ),
                ));
            }
        }
    }

    walk_statement(statement, &mut |_, expression| {
        let Some(expression) = expression else {
            return;
        };
        let Some(path) = as_call(expression).filter(|path| is_loop_effect_call(path)) else {
            return;
        };

        if seen.insert((
            expression.location.start.line,
            expression.location.start.column,
            path.clone(),
        )) {
            let weight = loop_effect_weight(&path, loop_depth);
            diagnostics.push(build_diagnostic_with_severity(
                RuleId::LoopSideEffect,
                severity_from_loop_weight(weight),
                function,
                expression.location.clone(),
                format!("ループ内で副作用呼び出し `{path}` を実行しています (weight={weight})。"),
                Some(loop_effect_hint(weight).to_owned()),
                build_fix_hint(&RuleId::LoopSideEffect, Some(loop_effect_hint(weight))),
            ));
        }
    });
}

fn as_call(expression: &Expression) -> Option<String> {
    match &expression.kind {
        ExpressionKind::Call { function, .. } => function.path(),
        _ => None,
    }
}

fn is_direct_io_call(path: &str) -> bool {
    matches!(
        path,
        "print"
            | "input"
            | "open"
            | "sys.stdout.write"
            | "sys.stderr.write"
            | "pathlib.Path.open"
            | "pathlib.Path.read_text"
            | "pathlib.Path.write_text"
            | "pathlib.Path.read_bytes"
            | "pathlib.Path.write_bytes"
    ) || path
        .rsplit('.')
        .next()
        .is_some_and(|segment| matches!(segment, "read" | "write"))
}

fn is_non_deterministic_call(path: &str) -> bool {
    path.starts_with("random.")
        || path.starts_with("secrets.")
        || matches!(
            path,
            "uuid.uuid1"
                | "uuid.uuid4"
                | "time.time"
                | "time.monotonic"
                | "datetime.datetime.now"
                | "datetime.datetime.utcnow"
                | "datetime.date.today"
                | "os.urandom"
        )
}

fn is_side_effect_call(path: &str) -> bool {
    is_in_place_mutation_call(path)
        || path.rsplit('.').next().is_some_and(|last_segment| {
            matches!(
                last_segment,
                "write" | "writelines" | "send" | "save" | "commit" | "flush"
            )
        })
}

fn is_direct_effectful_call(path: &str) -> bool {
    matches!(path, "print" | "open")
        || path.starts_with("logging.")
        || path.starts_with("requests.")
        || path.starts_with("subprocess.")
}

fn is_in_place_mutation_call(path: &str) -> bool {
    path.rsplit('.').next().is_some_and(|last_segment| {
        matches!(
            last_segment,
            "append"
                | "extend"
                | "update"
                | "add"
                | "remove"
                | "discard"
                | "clear"
                | "pop"
                | "insert"
                | "sort"
                | "reverse"
                | "setdefault"
        )
    })
}

fn is_loop_effect_call(path: &str) -> bool {
    is_side_effect_call(path) || is_direct_io_call(path) || is_direct_effectful_call(path)
}

fn class_state_target_path(target: &Expression) -> Option<String> {
    match &target.kind {
        ExpressionKind::Attribute { .. } | ExpressionKind::Subscript { .. } => target
            .base_name()
            .filter(|base_name| matches!(base_name.as_str(), "self" | "cls"))
            .and_then(|_| target.path().or_else(|| target.base_name())),
        _ => None,
    }
}

fn is_class_state_mutation_call(path: &str) -> bool {
    path.split('.')
        .next()
        .is_some_and(|base_name| matches!(base_name, "self" | "cls"))
        && (is_in_place_mutation_call(path) || is_side_effect_call(path))
}

fn referential_transparency_call_hint(path: &str) -> Option<&'static str> {
    match path {
        "eval" | "exec" | "compile" => {
            Some("動的実行を避け、明示的な分岐または安全な parser を利用してください。")
        }
        "globals" | "locals" | "vars" => {
            Some("暗黙の名前空間に依存せず、必要な値を引数として受け取ってください。")
        }
        "os.getenv" | "os.getenvb" | "os.getcwd" | "pathlib.Path.cwd" | "pathlib.Path.home" => {
            Some("環境や作業ディレクトリへの依存は呼び出し側で解決し、値を注入してください。")
        }
        _ => None,
    }
}

fn referential_transparency_reference_path(expression: &Expression) -> Option<String> {
    match &expression.kind {
        ExpressionKind::Attribute { .. } => expression.path().filter(|path| {
            matches!(
                path.as_str(),
                "os.environ" | "os.environb" | "sys.argv" | "sys.path"
            )
        }),
        ExpressionKind::Subscript { value, .. } => value.path().filter(|path| {
            matches!(
                path.as_str(),
                "os.environ" | "os.environb" | "sys.argv" | "sys.path"
            )
        }),
        _ => None,
    }
}

fn deprecated_api_replacement(path: &str) -> Option<&'static str> {
    match path {
        "logging.warn" => Some("`logging.warning` を使用してください。"),
        "locale.getdefaultlocale" => {
            Some("必要な locale 情報は明示的に設定し、個別の locale API を利用してください。")
        }
        "datetime.datetime.utcnow" => {
            Some("`datetime.datetime.now(datetime.UTC)` を使用してください。")
        }
        "datetime.datetime.utcfromtimestamp" => {
            Some("`datetime.datetime.fromtimestamp(ts, datetime.UTC)` を使用してください。")
        }
        "threading.currentThread" => Some("`threading.current_thread()` を使用してください。"),
        "threading.activeCount" => Some("`threading.active_count()` を使用してください。"),
        "imp.load_module" | "imp.find_module" | "imp.find_loader" | "imp.reload"
        | "imp.new_module" => Some("`importlib` ベースの API へ置き換えてください。"),
        "cgi.escape" => Some("`html.escape` を使用してください。"),
        _ => None,
    }
}

fn loop_effect_weight(path: &str, loop_depth: usize) -> usize {
    let mut weight = if path.starts_with("requests.") || path.starts_with("subprocess.") {
        3
    } else if is_direct_io_call(path) || is_side_effect_call(path) || is_direct_effectful_call(path)
    {
        2
    } else {
        1
    };

    if loop_depth > 1 {
        weight += 1;
    }

    weight
}

fn severity_from_loop_weight(weight: usize) -> Severity {
    if weight >= 3 {
        Severity::Error
    } else {
        Severity::Warning
    }
}

fn loop_effect_hint(weight: usize) -> &'static str {
    if weight >= 3 {
        "高コストまたは多重ループ内の副作用はバッチ化し、ループ外へ移してください。"
    } else {
        "ループ本体は値の変換に集中させ、副作用は外側でまとめて処理してください。"
    }
}
