//! Task tools built-in feature module.
//!
//! The built-in Task feature owns the session-lifetime [`TaskStore`] shared by
//! the Task tools and reminder hooks. Worker hosts install this module through the
//! feature contribution boundary and use its narrow snapshot surface for
//! restore/rewind/compaction compatibility.

use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};

use agen::Item;
use async_trait::async_trait;

mod store;
mod tool_impl;

pub(crate) use self::tool_impl::task_tools;
#[cfg(test)]
pub(crate) use store::TaskStatus;
use store::snapshot_overview;
pub(crate) use store::{TaskEntry, TaskStore};

use crate::feature::{
    FeatureDescriptor, FeatureHookPoint, FeatureInstallContext, FeatureInstallError, FeatureModule,
    HookDeclaration, ToolContribution, ToolDeclaration,
};
use crate::hook::{
    Hook, HookPreRequestAction, HookPreToolAction, PreLlmRequest, PreRequestContext, PreToolCall,
    ToolCallSummary,
};

const TASK_REMINDER_REQUEST_THRESHOLD: usize = 24;
const TASK_REMINDER_COOLDOWN_REQUESTS: usize = 24;
const TASK_MANAGEMENT_TOOL_NAMES: [&str; 2] = ["TaskCreate", "TaskUpdate"];

/// Construct the built-in Task feature module with a fresh session store.
///
/// The returned module contributes `TaskCreate`, `TaskUpdate`, `TaskGet`, and
/// `TaskList` through descriptor-approved tool registration, plus built-in hooks
/// that maintain Task-reminder state. Normal ToolRegistry and PreToolCall
/// permission policy still applies at call time.
pub fn task_tools_feature() -> TaskFeature {
    TaskFeature::new()
}

/// Built-in Task feature state and contribution module.
#[derive(Clone, Debug)]
pub struct TaskFeature {
    state: Arc<TaskFeatureState>,
}

#[derive(Debug)]
struct TaskFeatureState {
    task_store: TaskStore,
    reminder_state: TaskReminderState,
}

impl TaskFeature {
    pub fn new() -> Self {
        Self::from_store(TaskStore::new())
    }

    pub fn from_history(history: &[Item]) -> Self {
        Self::from_store(TaskStore::from_history(history))
    }

    fn from_store(task_store: TaskStore) -> Self {
        Self {
            state: Arc::new(TaskFeatureState {
                task_store,
                reminder_state: TaskReminderState::new(),
            }),
        }
    }

    /// Restore the feature-owned store by replaying durable history into the
    /// existing shared store handle. Existing Task tool instances and hooks keep
    /// pointing at the same feature-owned store after rewind.
    pub fn restore_from_history(&self, history: &[Item]) {
        let restored = TaskStore::from_history(history);
        self.state.task_store.replace_with(restored.list());
    }

    /// Feature-owned snapshot text used by compaction to preserve Task state.
    pub fn snapshot_text(&self) -> String {
        self.state.task_store.snapshot_text()
    }

    /// Feature-owned compact summary used for the synthetic TaskList result.
    pub fn snapshot_overview(&self) -> String {
        snapshot_overview(&self.state.task_store.list())
    }

    #[cfg(test)]
    fn task_store(&self) -> TaskStore {
        self.state.task_store.clone()
    }
}

impl Default for TaskFeature {
    fn default() -> Self {
        Self::new()
    }
}

impl FeatureModule for TaskFeature {
    fn descriptor(&self) -> FeatureDescriptor {
        FeatureDescriptor::builtin("task-tools", "Task tools")
            .with_description("Session-lifetime task tracking builtin tools")
            .with_tool(ToolDeclaration::new(
                "TaskCreate",
                "Create a session-lifetime user-visible task",
            ))
            .with_tool(ToolDeclaration::new(
                "TaskUpdate",
                "Update a session-lifetime user-visible task",
            ))
            .with_tool(ToolDeclaration::new(
                "TaskGet",
                "Get one session-lifetime user-visible task",
            ))
            .with_tool(ToolDeclaration::new(
                "TaskList",
                "List session-lifetime user-visible tasks",
            ))
            .with_hook(HookDeclaration::new(
                "task-reminder-pre-request",
                FeatureHookPoint::PreRequest,
            ))
            .with_hook(HookDeclaration::new(
                "task-reminder-tool-usage",
                FeatureHookPoint::PreToolCall,
            ))
    }

    fn install(&self, context: &mut FeatureInstallContext<'_>) -> Result<(), FeatureInstallError> {
        let names = ["TaskCreate", "TaskList", "TaskGet", "TaskUpdate"];
        for (name, definition) in names
            .into_iter()
            .zip(task_tools(self.state.task_store.clone()))
        {
            context
                .tools()
                .register(ToolContribution::new(name, definition))?;
        }

        context.hooks().add_pre_request(
            "task-reminder-pre-request",
            TaskReminderPreRequestHook {
                state: Arc::clone(&self.state),
            },
        )?;
        context.hooks().add_pre_tool_call(
            "task-reminder-tool-usage",
            TaskReminderToolUsageHook {
                state: Arc::clone(&self.state),
            },
        )?;
        Ok(())
    }
}

#[derive(Debug)]
struct TaskReminderState {
    requests_since_last_task_management: AtomicUsize,
    requests_since_last_reminder: AtomicUsize,
}

impl Default for TaskReminderState {
    fn default() -> Self {
        Self {
            requests_since_last_task_management: AtomicUsize::new(0),
            requests_since_last_reminder: AtomicUsize::new(TASK_REMINDER_COOLDOWN_REQUESTS),
        }
    }
}

impl TaskReminderState {
    fn new() -> Self {
        Self::default()
    }

    fn note_request(&self) -> (usize, usize) {
        let since_task_management = self
            .requests_since_last_task_management
            .fetch_add(1, Ordering::Relaxed)
            .saturating_add(1);
        let since_reminder = self
            .requests_since_last_reminder
            .fetch_add(1, Ordering::Relaxed)
            .saturating_add(1);
        (since_task_management, since_reminder)
    }

    fn note_task_management(&self) {
        self.requests_since_last_task_management
            .store(0, Ordering::Relaxed);
    }

    fn note_reminder(&self) {
        self.requests_since_last_reminder
            .store(0, Ordering::Relaxed);
    }
}

struct TaskReminderPreRequestHook {
    state: Arc<TaskFeatureState>,
}

#[async_trait]
impl Hook<PreLlmRequest> for TaskReminderPreRequestHook {
    async fn call(&self, input: &PreRequestContext) -> HookPreRequestAction {
        let tasks = self.state.task_store.list();
        if tasks.is_empty() {
            return HookPreRequestAction::Continue;
        }

        let (since_task_management, since_reminder) = self.state.reminder_state.note_request();
        if since_task_management < TASK_REMINDER_REQUEST_THRESHOLD
            || since_reminder < TASK_REMINDER_COOLDOWN_REQUESTS
        {
            return HookPreRequestAction::Continue;
        }

        if let Some(system_items) = input.system_items() {
            self.state.reminder_state.note_reminder();
            system_items.append_task_reminder(render_task_reminder_body(&tasks));
        }
        HookPreRequestAction::Continue
    }
}

struct TaskReminderToolUsageHook {
    state: Arc<TaskFeatureState>,
}

#[async_trait]
impl Hook<PreToolCall> for TaskReminderToolUsageHook {
    async fn call(&self, input: &ToolCallSummary) -> HookPreToolAction {
        if is_task_management_tool(&input.tool_name) {
            self.state.reminder_state.note_task_management();
        }
        HookPreToolAction::Continue
    }
}

fn is_task_management_tool(name: &str) -> bool {
    TASK_MANAGEMENT_TOOL_NAMES.contains(&name)
}

fn render_task_reminder_body(tasks: &[TaskEntry]) -> String {
    let mut body = String::from(
        "Current session steps are listed below. Call TaskUpdate at each natural boundary: keep the current step inprogress, mark it completed when finished, and delete older completed steps once their result is no longer useful context.\n",
    );
    for task in tasks {
        body.push_str(&format!(
            "- taskid {} ({}) {}\n",
            task.taskid, task.status, task.subject
        ));
    }
    body.trim_end_matches('\n').to_string()
}

#[cfg(test)]
mod tests {
    use std::sync::{Arc, Mutex};

    use session_store::{SystemItem, SystemReminderSource};

    use super::*;
    use crate::hook::{PreRequestInfo, SystemItemAppendHandle};

    fn pre_request_context(pending: Arc<Mutex<Vec<SystemItem>>>) -> PreRequestContext {
        PreRequestContext::new(
            PreRequestInfo {
                item_count: 1,
                estimated_tokens: None,
                turn_index: 0,
                tool_calls_this_turn: 0,
            },
            Some(SystemItemAppendHandle::new(pending)),
        )
    }

    fn tool_summary(name: &str) -> ToolCallSummary {
        ToolCallSummary {
            call_id: "call-id".into(),
            tool_name: name.into(),
            arguments: serde_json::json!({}),
        }
    }

    #[tokio::test]
    async fn task_reminder_hook_appends_after_inactive_request_threshold() {
        let feature = TaskFeature::new();
        feature
            .task_store()
            .create("keep going".into(), "long task description".into());
        let hook = TaskReminderPreRequestHook {
            state: Arc::clone(&feature.state),
        };
        let pending = Arc::new(Mutex::new(Vec::new()));

        for _ in 0..TASK_REMINDER_REQUEST_THRESHOLD - 1 {
            let _ = hook.call(&pre_request_context(Arc::clone(&pending))).await;
            assert!(pending.lock().expect("pending queue poisoned").is_empty());
        }
        let _ = hook.call(&pre_request_context(Arc::clone(&pending))).await;

        let queued = pending.lock().expect("pending queue poisoned");
        assert_eq!(queued.len(), 1);
        let SystemItem::TaskReminder { body, .. } = &queued[0] else {
            panic!("unexpected system item: {:?}", queued[0]);
        };
        assert!(body.starts_with("Current session steps are listed below."));
        assert!(body.contains("taskid 1"));
        assert!(body.contains("pending"));
        assert!(body.contains("keep going"));
        assert!(!body.contains("long task description"));
    }

    #[tokio::test]
    async fn task_reminder_hook_retains_source() {
        let feature = TaskFeature::new();
        feature.task_store().create("typed".into(), String::new());
        let hook = TaskReminderPreRequestHook {
            state: Arc::clone(&feature.state),
        };
        let pending = Arc::new(Mutex::new(Vec::new()));

        for _ in 0..TASK_REMINDER_REQUEST_THRESHOLD {
            let _ = hook.call(&pre_request_context(Arc::clone(&pending))).await;
        }

        let queued = pending.lock().expect("pending queue poisoned");
        let SystemItem::TaskReminder { source, body, .. } = &queued[0] else {
            panic!("unexpected system item: {:?}", queued[0]);
        };
        assert_eq!(*source, SystemReminderSource::TaskInactivity);
        assert!(body.starts_with("Current session steps are listed below."));
        assert!(body.contains("typed"));
    }

    #[test]
    fn render_task_reminder_body_is_plain_system_text() {
        let feature = TaskFeature::new();
        let task = feature.task_store().create("body".into(), String::new());
        let body = render_task_reminder_body(&[task]);

        assert!(body.starts_with("Current session steps are listed below."));
        assert!(body.contains("TaskUpdate"));
        assert!(body.contains("taskid 1"));
    }

    #[test]
    fn task_reminder_state_starts_with_initial_cooldown_elapsed() {
        let state = TaskReminderState::new();

        assert_eq!(
            state.requests_since_last_reminder.load(Ordering::Relaxed),
            TASK_REMINDER_COOLDOWN_REQUESTS
        );
        assert_eq!(
            state
                .requests_since_last_task_management
                .load(Ordering::Relaxed),
            0
        );
    }

    #[tokio::test]
    async fn task_management_tool_call_resets_reminder_inactivity_counter() {
        let feature = TaskFeature::new();
        feature
            .task_store()
            .create("track me".into(), String::new());
        let pre_request = TaskReminderPreRequestHook {
            state: Arc::clone(&feature.state),
        };
        let pre_tool = TaskReminderToolUsageHook {
            state: Arc::clone(&feature.state),
        };
        let pending = Arc::new(Mutex::new(Vec::new()));

        for _ in 0..TASK_REMINDER_REQUEST_THRESHOLD - 1 {
            let _ = pre_request
                .call(&pre_request_context(Arc::clone(&pending)))
                .await;
            assert!(pending.lock().expect("pending queue poisoned").is_empty());
        }
        let _ = pre_tool.call(&tool_summary("TaskUpdate")).await;

        for _ in 0..TASK_REMINDER_REQUEST_THRESHOLD - 1 {
            let _ = pre_request
                .call(&pre_request_context(Arc::clone(&pending)))
                .await;
            assert!(pending.lock().expect("pending queue poisoned").is_empty());
        }
        let _ = pre_request
            .call(&pre_request_context(Arc::clone(&pending)))
            .await;
        assert_eq!(pending.lock().expect("pending queue poisoned").len(), 1);
    }

    #[tokio::test]
    async fn task_reminder_respects_cooldown_after_reminder() {
        let feature = TaskFeature::new();
        feature
            .task_store()
            .create("cooldown".into(), String::new());
        let hook = TaskReminderPreRequestHook {
            state: Arc::clone(&feature.state),
        };
        let pending = Arc::new(Mutex::new(Vec::new()));

        for _ in 0..TASK_REMINDER_REQUEST_THRESHOLD {
            let _ = hook.call(&pre_request_context(Arc::clone(&pending))).await;
        }
        pending.lock().expect("pending queue poisoned").clear();
        for _ in 0..TASK_REMINDER_COOLDOWN_REQUESTS - 1 {
            let _ = hook.call(&pre_request_context(Arc::clone(&pending))).await;
            assert!(pending.lock().expect("pending queue poisoned").is_empty());
        }
        let _ = hook.call(&pre_request_context(Arc::clone(&pending))).await;
        assert_eq!(pending.lock().expect("pending queue poisoned").len(), 1);
    }

    #[tokio::test]
    async fn task_reminder_includes_completed_tasks_until_deleted() {
        let feature = TaskFeature::new();
        let done = feature
            .task_store()
            .create("done".into(), String::new())
            .taskid;
        feature
            .task_store()
            .update(done, Some(TaskStatus::Completed), None, None)
            .expect("complete task");
        let hook = TaskReminderPreRequestHook {
            state: Arc::clone(&feature.state),
        };
        let pending = Arc::new(Mutex::new(Vec::new()));

        for _ in 0..TASK_REMINDER_REQUEST_THRESHOLD {
            let _ = hook.call(&pre_request_context(Arc::clone(&pending))).await;
        }
        let queued = pending.lock().expect("pending queue poisoned");
        assert_eq!(queued.len(), 1);
        let SystemItem::TaskReminder { body, .. } = &queued[0] else {
            panic!("unexpected system item: {:?}", queued[0]);
        };
        assert!(body.contains("taskid 1 (completed) done"));
        assert!(body.contains("delete older completed steps"));
    }

    #[tokio::test]
    async fn inactive_requests_without_active_tasks_do_not_prime_task_reminder() {
        let feature = TaskFeature::new();
        let hook = TaskReminderPreRequestHook {
            state: Arc::clone(&feature.state),
        };
        let pending = Arc::new(Mutex::new(Vec::new()));

        for _ in 0..TASK_REMINDER_REQUEST_THRESHOLD * 2 {
            let _ = hook.call(&pre_request_context(Arc::clone(&pending))).await;
            assert!(pending.lock().expect("pending queue poisoned").is_empty());
        }

        feature
            .task_store()
            .create("new active".into(), String::new());
        for _ in 0..TASK_REMINDER_REQUEST_THRESHOLD - 1 {
            let _ = hook.call(&pre_request_context(Arc::clone(&pending))).await;
            assert!(pending.lock().expect("pending queue poisoned").is_empty());
        }
        let _ = hook.call(&pre_request_context(Arc::clone(&pending))).await;
        assert_eq!(pending.lock().expect("pending queue poisoned").len(), 1);
    }

    #[tokio::test]
    async fn task_create_reset_does_not_block_first_reminder_cooldown() {
        let feature = TaskFeature::new();
        let pre_request = TaskReminderPreRequestHook {
            state: Arc::clone(&feature.state),
        };
        let pre_tool = TaskReminderToolUsageHook {
            state: Arc::clone(&feature.state),
        };
        let pending = Arc::new(Mutex::new(Vec::new()));

        for _ in 0..TASK_REMINDER_REQUEST_THRESHOLD * 2 {
            let _ = pre_request
                .call(&pre_request_context(Arc::clone(&pending)))
                .await;
            assert!(pending.lock().expect("pending queue poisoned").is_empty());
        }

        let _ = pre_tool.call(&tool_summary("TaskCreate")).await;
        feature
            .task_store()
            .create("created after idle".into(), String::new());
        assert_eq!(
            feature
                .state
                .reminder_state
                .requests_since_last_reminder
                .load(Ordering::Relaxed),
            TASK_REMINDER_COOLDOWN_REQUESTS,
            "TaskCreate reset must not clear the initial reminder cooldown"
        );

        for _ in 0..TASK_REMINDER_REQUEST_THRESHOLD - 1 {
            let _ = pre_request
                .call(&pre_request_context(Arc::clone(&pending)))
                .await;
            assert!(pending.lock().expect("pending queue poisoned").is_empty());
        }
        let _ = pre_request
            .call(&pre_request_context(Arc::clone(&pending)))
            .await;
        assert_eq!(pending.lock().expect("pending queue poisoned").len(), 1);
    }

    #[tokio::test]
    async fn missing_system_item_handle_does_not_mark_reminder_sent() {
        let feature = TaskFeature::new();
        feature.task_store().create("handle".into(), String::new());
        let hook = TaskReminderPreRequestHook {
            state: Arc::clone(&feature.state),
        };
        let no_handle = PreRequestContext::new(
            PreRequestInfo {
                item_count: 1,
                estimated_tokens: None,
                turn_index: 0,
                tool_calls_this_turn: 0,
            },
            None,
        );

        for _ in 0..TASK_REMINDER_REQUEST_THRESHOLD {
            let _ = hook.call(&no_handle).await;
        }
        assert_eq!(
            feature
                .state
                .reminder_state
                .requests_since_last_reminder
                .load(Ordering::Relaxed),
            TASK_REMINDER_COOLDOWN_REQUESTS + TASK_REMINDER_REQUEST_THRESHOLD,
            "without a handle the hook must not record a reminder as emitted"
        );
    }

    #[test]
    fn restore_from_history_keeps_existing_store_handle_for_installed_tools() {
        let feature = TaskFeature::new();
        let handle = feature.task_store();
        handle.create("old".into(), String::new());
        let history = vec![Item::tool_call(
            "c1",
            "TaskCreate",
            r#"{"subject":"restored","description":"from history"}"#,
        )];

        feature.restore_from_history(&history);

        let tasks = handle.list();
        assert_eq!(tasks.len(), 1);
        assert_eq!(tasks[0].subject, "restored");
    }
}
