// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Generates warnings for undesirable pallet code.

use crate::pallet::parse::call::{CallVariantDef, CallWeightDef};
use proc_macro_warning::Warning;
use syn::{
	spanned::Spanned,
	visit::{self, Visit},
};

/// Warn if any of the call arguments starts with a underscore and is used in a weight formula.
pub(crate) fn weight_witness_warning(
	method: &CallVariantDef,
	dev_mode: bool,
	warnings: &mut Vec<Warning>,
) {
	if dev_mode {
		return
	}
	let CallWeightDef::Immediate(w) = &method.weight else { return };

	let partial_warning = Warning::new_deprecated("UncheckedWeightWitness")
		.old("not check weight witness data")
		.new("ensure that all witness data for weight calculation is checked before usage")
		.help_link("https://github.com/paritytech/polkadot-sdk/pull/1818");

	for (_, arg_ident, _) in method.args.iter() {
		if !arg_ident.to_string().starts_with('_') || !contains_ident(w.clone(), &arg_ident) {
			continue
		}

		let warning = partial_warning
			.clone()
			.index(warnings.len())
			.span(arg_ident.span())
			.build_or_panic();

		warnings.push(warning);
	}
}

/// Warn if the weight is a constant and the pallet not in `dev_mode`.
pub(crate) fn weight_constant_warning(
	weight: &syn::Expr,
	dev_mode: bool,
	warnings: &mut Vec<Warning>,
) {
	if dev_mode {
		return
	}
	let syn::Expr::Lit(lit) = weight else { return };

	let warning = Warning::new_deprecated("ConstantWeight")
		.index(warnings.len())
		.old("use hard-coded constant as call weight")
		.new("benchmark all calls or put the pallet into `dev` mode")
		.help_link("https://github.com/paritytech/substrate/pull/13798")
		.span(lit.span())
		.build_or_panic();

	warnings.push(warning);
}

/// Returns whether `expr` contains `ident`.
fn contains_ident(mut expr: syn::Expr, ident: &syn::Ident) -> bool {
	struct ContainsIdent {
		ident: syn::Ident,
		found: bool,
	}

	impl<'a> Visit<'a> for ContainsIdent {
		fn visit_ident(&mut self, i: &syn::Ident) {
			if *i == self.ident {
				self.found = true;
			}
		}
	}

	let mut visitor = ContainsIdent { ident: ident.clone(), found: false };
	visit::visit_expr(&mut visitor, &mut expr);
	visitor.found
}
