use llmy::harness::memory::AgentMemorySystemPromptCriteria;

pub struct FunctionAgentPromptInput {
    pub source_manifest: String,
    pub analyzed_functions_manifest: String,
    pub start_contract_label: String,
    pub start_contract_source: String,
    pub start_function_label: String,
    pub start_function_selector: String,
    pub start_function_source: String,
    pub semantic_count: usize,
}

pub fn function_agent_system_prompt() -> String {
    r#"You are a Solidity callgraph expansion agent.

Your job is to discover repository-local call edges starting from one Solidity callable. A callable can be a function, constructor, receive function, fallback function, or modifier.

Tools:
- Use read_file to inspect source files under the repository root. Use it before relying on imported files or target implementations that are not already shown in the prompt.
- Use memory tools to read project semantic context and the short-term callable index. Tree-sitter callable index entries are titled as relative_file_path::contract_name.
- Use record_call_relation for every repository-local call edge you identify. Never put call edges only in the final message.
- Use record_call_relation for external dependency calls too, but only after the missing/external callee has been confirmed with record_terminal_node reason=missing_tree_sitter_node. record_call_relation does not create missing callees by itself.
- Use record_terminal_node for every leaf you prove is terminal. This includes concrete functions with no outgoing repository-local or external dependency calls, missing tree-sitter nodes, and interface functions. Repeated terminal records are allowed.

Rules:
- Do not generate ids. The record_call_relation tool resolves call endpoints and assigns ids on the Rust side.
- A terminal node means exactly one of: the concrete function body has no outgoing call edges, the selector is not present in tree-sitter extraction, or the node is an interface function declaration.
- Record modifier invocations as call edges from the function/constructor/fallback/receive function to the modifier.
- Record direct calls from modifiers to functions and from functions to modifiers/functions when the target is in the short-term callable index memory.
- Record external protocol/library/dependency calls only after confirming the target implementation is not present in the tree-sitter callable index, recording that missing target with record_terminal_node, and then retrying record_call_relation.
- The repository callable index is kept in short-term memory, one entry per tree-sitter contract/interface/library. Use search_memory/read_memory to find relative_file_path, contract, and callable name. Omit line_number and line_column on the first record_call_relation attempt because source locations may be easy to hallucinate; if the tool reports ambiguity, it will return candidate line_number/line_column values for a precise retry.
- Treat preloaded tree-sitter callable index memory as read-only evidence generated by Rust extraction. Do not update, delete, or rewrite those entries.
- Interface headings contain declarations with no body; record calls to interface functions but do not recurse into them.
- After inspecting a concrete contract or library callable and finding no outgoing call edges, call record_terminal_node with reason=no_external_call for that callable before finishing.
- When record_call_relation says the callee selector does not exist, it has recorded nothing. Re-check source and short-term callable index memory. If the callee is truly absent from tree-sitter extraction, call record_terminal_node with reason=missing_tree_sitter_node for that callee, then call record_call_relation again.
- Interface functions are terminal leaves. Record edges to them, and if you mention the interface node directly, call record_terminal_node with reason=interface_function.
- If a repository-local callee is already listed as analyzed, record the edge and do not recurse into that callee.
- If a callee is not analyzed, inspect its implementation with read_file and continue recursively until repository-local call edges are exhausted or you reach already-analyzed nodes.
- For every record_call_relation call, set description to the call context: for example forwarding, validation, liquidity update, accounting update, oracle read, or access-control check. If it relates to a Project Semantic entry, include that semantic context.
- First call record_call_relation with relative_file_path, contract, and name only. If the tool reports multiple matches, retry with the exact line_number and line_column from one of the returned candidates. Do not rely on args or kind for endpoint matching.
- If multiple contracts share the same name, disambiguate endpoints with relative_file_path first; use line_number and line_column only after the tool reports ambiguity.
- After calling record_call_relation, follow the tool result. If it says the callee or caller is already fully analyzed, stop that recursive branch instead of reading more source for it.
- If a call is ambiguous, read more source and then call record_call_relation with a more precise selector.
- Do not finish an inspected branch as a leaf unless it either has at least one recorded outgoing call edge or has been sent through record_terminal_node.

Callee resolution method:
- For the starting callable, use the caller selector from the prompt first. It intentionally omits line_number and line_column.
- If a target function is declared in the same contract or the current source context already gives its file, contract/interface/library, and callable name, record the edge directly without line_number/line_column.
- Otherwise, inspect imports, inheritance, using-for/library calls, interface types, and qualifiers in the source. Search/read the short-term callable index memory for the relevant contract or function, then call record_call_relation with relative_file_path, contract, and name.
- If record_call_relation returns ambiguity, choose the intended candidate from the returned line_number/line_column list and retry.
- If lookup and the tool result confirm no repository-local tree-sitter callable exists for the target, record it as a missing_tree_sitter_node terminal, then call record_call_relation again.

Final response:
- Return a concise plain-text summary of what you inspected and any unresolved ambiguities.
- Do not return JSON and do not include call edges or terminal claims that were not sent through the tools.
"#
    .to_string()
}

pub fn function_agent_user_prompt(input: FunctionAgentPromptInput) -> String {
    format!(
        "Analyze one Solidity callable and recursively expand repository-local call edges.\n\nKnown semantic entries loaded in long-term memory: {}.\n\nSource manifest:\n{}\n\nCallable index memory:\nShort-term memory contains one tree-sitter callable index entry per contract/interface/library. Entry titles are relative_file_path::contract_name, and each entry lists callable kind, name, args, and source locations. Endpoint matching first uses relative_file_path, contract, and name; omit line_number and line_column on the first record_call_relation call. If the tool reports ambiguity, retry with the exact line_number and line_column from a returned candidate. Args and kind are ignored for matching. Search/read those entries when resolving callees not already identified by current context.\n\nAlready analyzed callables:\n{}\n\nStarting contract: {}\n\nStarting contract source:\n```solidity\n{}\n```\n\nStarting callable: {}\nStarting selector for record_call_relation caller:\n{}\n\nStarting callable source:\n```solidity\n{}\n```\n\nUse read_file whenever you need imported files or callee implementations. Call record_call_relation for every repository-local edge, including modifier invocations and calls to interface declarations. If record_call_relation says a callee selector does not exist, confirm it from source/memory; if truly absent, call record_terminal_node with reason=missing_tree_sitter_node for that callee and then retry record_call_relation. If an inspected callable has no outgoing call edges, call record_terminal_node with reason=no_external_call before the summary.",
        input.semantic_count,
        input.source_manifest,
        input.analyzed_functions_manifest,
        input.start_contract_label,
        input.start_contract_source,
        input.start_function_label,
        input.start_function_selector,
        input.start_function_source,
    )
}

pub fn default_memory_prompt_criteria() -> AgentMemorySystemPromptCriteria {
    AgentMemorySystemPromptCriteria::builder()
        .append_long_term_memory_criteria(
            "Durable protocol semantics, category facts, and stable contract/function role knowledge"
                .to_string(),
        )
        .append_short_term_memory_criteria(
            "Tree-sitter Solidity callable index entries keyed by relative source path and contract/interface/library name, including callable names, informational kind/args, and source line/column ranges returned for ambiguity retries"
                .to_string(),
        )
        .append_short_term_memory_criteria(
            "Current callgraph extraction notes, unresolved source ranges, and file-reading progress"
                .to_string(),
        )
        .append_short_term_memory_operator(
            "Read preloaded tree-sitter callable index entries as authoritative extraction output; do not update or delete them from the agent"
                .to_string(),
        )
        .append_short_term_memory_trigger(
            "When resolving a callee, inherited function, imported contract, interface declaration, overloaded selector, or record_call_relation ambiguity retry"
                .to_string(),
        )
        .append_short_term_memory_trigger(
            "When the analysis spans many files or context compaction may be needed".to_string(),
        )
        .build()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn user_prompt_points_to_callable_index_memory_without_dumping_manifest() {
        let rendered = function_agent_user_prompt(FunctionAgentPromptInput {
            source_manifest: "- src/App.sol".to_string(),
            analyzed_functions_manifest: "<none>".to_string(),
            start_contract_label: "contract App (src/App.sol)".to_string(),
            start_contract_source: "contract App {}".to_string(),
            start_function_label: "Function(App::run() [contract_function_definition; analysis_scope=true] @ src/App.sol (id=1))".to_string(),
            start_function_selector: "relative_file_path=src/App.sol; contract=App; name=run".to_string(),
            start_function_source: "function run() external {}".to_string(),
            semantic_count: 0,
        });

        assert!(rendered.contains("Callable index memory:"));
        assert!(rendered.contains("relative_file_path::contract_name"));
        assert!(!rendered.contains("Known repository callables:"));
    }
}
