use anyhow::Result;

use crate::labels::normalize_label;
use crate::operations::TaskDraft;
use crate::tui::app::{App, NaturalRetry, Notification, PendingTaskIntake, ReadyTaskIntake};
use crate::tui::authoring::{
    ADD_NOTE_TITLE, ADD_TASK_LABELS_TITLE, ADD_TASK_TITLE_PROJECT_TITLE, AddNoteSubmit,
    AddTaskStep, AddTaskTitleSubmit,
};
use crate::tui::natural_add_runtime::{spawn_add_task_only_natural, task_intake_log_path};
use crate::tui::overlay::{
    AddTaskState, LineEdit, MultilineInputState, OverlayRoute, OverlayState,
};
use crate::tui::platform::edit_text_externally;
use crate::tui::store::TaskScope;

pub(crate) const ADD_TASK_NATURAL_TITLE: &str = "Add task: natural language";

impl App {
    pub(super) async fn begin_add_task(&mut self) -> Result<()> {
        self.pending_shortcut.clear();
        let active_project = match &self.store.view_state.scope {
            TaskScope::Project(project) => Some(project.clone()),
            TaskScope::Workspace => None,
        };
        let inferred_project = if active_project.is_none() {
            self.store.inferred_add_project().await?
        } else {
            None
        };
        self.authoring
            .begin_add_task(active_project, inferred_project);
        self.begin_add_task_title();
        Ok(())
    }

    pub(super) fn begin_add_task_title(&mut self) {
        self.begin_add_task_overlay();
    }

    fn begin_add_task_overlay(&mut self) {
        let Some(context) = self.authoring.add_task_context() else {
            return;
        };
        self.overlay = Some(OverlayState::AddTask(AddTaskState {
            title: LineEdit::new(context.title),
            description: MultilineInputState::from_value(
                OverlayRoute::AddTaskDescription,
                "Add task: description",
                "",
                context.description,
            ),
            focus: context.step,
            project: context.project,
            status: context.status,
            priority: context.priority,
            labels: context.labels,
        }));
    }

    pub(super) fn begin_add_task_step(&mut self) {
        self.begin_add_task_overlay();
    }

    pub(super) fn open_add_task_description_editor(&mut self) {
        let Some(context) = self.authoring.add_task_context() else {
            return;
        };
        self.needs_terminal_clear = true;
        match edit_text_externally(context.description.clone(), "description.md") {
            Ok(value) => {
                self.authoring.capture_add_task_fields(
                    context.title,
                    value,
                    AddTaskStep::Description,
                );
                self.begin_add_task_step();
            }
            Err(error) => {
                self.set_error(format!("editor failed: {error:#}"));
                self.begin_add_task_step();
            }
        }
    }

    pub(super) fn capture_add_task_state(&mut self, state: &AddTaskState) -> bool {
        self.authoring.capture_add_task_fields(
            state.title.text.clone(),
            state.description.lines.join("\n"),
            state.focus,
        )
    }

    pub(super) fn set_add_task_status(&mut self, status: &str) {
        if let Some(status) = self.authoring.apply_add_task_status(status) {
            if let Some(OverlayState::AddTask(state)) = self.overlay.as_mut() {
                state.status = status.clone();
            } else {
                self.begin_add_task_step();
            }
            self.set_info(format!("add task status={status}"));
        }
    }

    pub(super) fn set_add_task_priority(&mut self, priority: &str) {
        if let Some(priority) = self.authoring.apply_add_task_priority_value(priority) {
            if let Some(OverlayState::AddTask(state)) = self.overlay.as_mut() {
                state.priority = priority.clone();
            } else {
                self.begin_add_task_step();
            }
            self.set_info(format!("add task priority={priority}"));
        }
    }

    pub(super) async fn submit_add_task_from_authoring(&mut self) -> Result<()> {
        match self.authoring.submit_add_task() {
            AddTaskTitleSubmit::ReopenTitle { message } => {
                self.set_warning(message);
                self.begin_add_task_title();
            }
            AddTaskTitleSubmit::Create(draft) => {
                self.submit_created_task(draft).await?;
            }
            AddTaskTitleSubmit::Inactive => {}
        }
        Ok(())
    }

    pub(super) fn begin_add_note(&mut self) {
        self.pending_shortcut.clear();
        let Some(item) = self
            .store
            .selected_task(self.widgets.table.selected())
            .cloned()
        else {
            self.set_info("no selected task for note");
            return;
        };
        let return_to_detail =
            self.detail_context || matches!(self.overlay, Some(OverlayState::Detail { .. }));
        self.authoring.begin_add_note(
            item.task.id.clone(),
            item.display_ref.clone(),
            return_to_detail,
        );
        self.detail_context = return_to_detail;
        self.overlay = Some(OverlayState::blank_multiline_input(
            OverlayRoute::AddNote,
            ADD_NOTE_TITLE,
            "note body:",
        ));
    }

    pub(super) fn begin_add_task_title_project(&mut self) {
        let Some(selected) = self.authoring.selected_add_task_project() else {
            return;
        };
        let items = self.store.project_picker_items(selected.as_deref());
        self.open_picker_overlay(
            OverlayRoute::AddTaskTitleProject,
            ADD_TASK_TITLE_PROJECT_TITLE,
            items,
            false,
        );
    }

    pub(super) fn begin_add_task_title_labels(&mut self) {
        let Some(context) = self.authoring.add_task_context() else {
            return;
        };
        self.overlay = Some(OverlayState::tag_combobox(
            OverlayRoute::AddTaskTitleLabels,
            ADD_TASK_LABELS_TITLE,
            self.store.labels.clone(),
            context.labels,
        ));
    }

    pub(super) async fn submit_add_task_title_labels(&mut self, labels: Vec<String>) -> Result<()> {
        for label in &labels {
            let label = normalize_label(label);
            if !self.store.labels.contains(&label)
                && let Err(error) = self.store.create_label(label).await
            {
                self.set_error(format!("{error:#}"));
                self.begin_add_task_title_labels();
                return Ok(());
            }
        }
        if self.authoring.apply_add_task_labels(labels) {
            self.begin_add_task_step();
        }
        Ok(())
    }

    pub(super) fn begin_add_task_natural(&mut self) {
        self.begin_add_task_natural_with_value(String::new());
    }

    fn begin_add_task_natural_with_value(&mut self, value: String) {
        self.overlay = Some(OverlayState::multiline_input(
            OverlayRoute::AddTaskNatural,
            ADD_TASK_NATURAL_TITLE,
            "",
            value,
        ));
    }

    pub(super) async fn submit_add_task_title_natural(
        &mut self,
        title: String,
        description: String,
    ) -> Result<()> {
        let value = add_task_natural_intake(&title, &description);
        if self.add_task_only {
            self.submit_add_task_only_natural(value, NaturalRetry::AddTask)
                .await
        } else {
            self.submit_add_task_natural_with_retry(value, NaturalRetry::AddTask, true)
                .await
        }
    }

    async fn submit_add_task_only_natural(
        &mut self,
        value: String,
        retry: NaturalRetry,
    ) -> Result<()> {
        let raw = value.trim();
        if raw.is_empty() {
            self.set_warning("task description is required");
            self.retry_add_task_natural(value, retry);
            return Ok(());
        }
        let project = self.add_task_project_context();
        spawn_add_task_only_natural(
            raw,
            self.store.active_workspace.id.as_str(),
            self.add_task_db_path.as_deref(),
            project.as_deref(),
            false,
        )?;
        self.add_task_only_message = Some("adding task in background".to_string());
        self.should_quit = true;
        Ok(())
    }

    pub(super) async fn submit_add_task_natural(&mut self, value: String) -> Result<()> {
        if self.add_task_only {
            self.submit_add_task_only_natural(value, NaturalRetry::Dialog)
                .await
        } else {
            self.submit_add_task_natural_with_retry(value, NaturalRetry::Dialog, false)
                .await
        }
    }

    async fn submit_add_task_natural_with_retry(
        &mut self,
        value: String,
        retry: NaturalRetry,
        create_on_success: bool,
    ) -> Result<()> {
        let raw = value.trim();
        if raw.is_empty() {
            self.set_warning("task description is required");
            self.retry_add_task_natural(value, retry);
            return Ok(());
        }
        let project = self.add_task_project_context();
        if create_on_success {
            spawn_add_task_only_natural(
                raw,
                self.store.active_workspace.id.as_str(),
                self.add_task_db_path.as_deref(),
                project.as_deref(),
                true,
            )?;
            self.overlay = None;
            self.set_info("adding task in background");
            return Ok(());
        }
        let handle = self.store.spawn_task_intake(
            self.add_task_config.agent.task_intake.clone(),
            raw.to_string(),
            project,
        );
        self.notification = Some(Notification::loading("parsing task with LLM"));
        self.pending_task_intake = Some(PendingTaskIntake {
            handle,
            retry,
            value: value.clone(),
            create_on_success,
        });
        self.retry_add_task_natural(value, retry);
        Ok(())
    }

    pub(super) async fn poll_pending_task_intake(&mut self) -> Result<bool> {
        if let Some(ready) = self.ready_task_intake.take() {
            self.finish_ready_task_intake(ready).await?;
            return Ok(true);
        }

        let Some(pending) = self
            .pending_task_intake
            .take_if(|pending| pending.handle.is_finished())
        else {
            return Ok(false);
        };
        let ready = ReadyTaskIntake {
            outcome: pending.handle.await?,
            retry: pending.retry,
            value: pending.value,
            create_on_success: pending.create_on_success,
        };
        if ready.outcome.is_err() {
            self.set_error("task intake failed");
        }
        self.ready_task_intake = Some(ready);
        Ok(true)
    }

    async fn finish_ready_task_intake(&mut self, ready: ReadyTaskIntake) -> Result<()> {
        match ready.outcome {
            Ok(draft) if ready.create_on_success => {
                self.submit_created_task(draft).await?;
            }
            Ok(draft) => {
                if self.authoring.apply_add_task_draft(draft) {
                    self.set_success("parsed task draft, review and save");
                    self.begin_add_task_step();
                }
            }
            Err(error) => {
                let log_path = task_intake_log_path();
                tracing::warn!(error = %error, "task intake failed");
                self.set_error(format!(
                    "task intake failed: {error:#}; logged to {}",
                    log_path.display()
                ));
                self.retry_add_task_natural(ready.value, ready.retry);
            }
        }
        Ok(())
    }

    fn retry_add_task_natural(&mut self, value: String, retry: NaturalRetry) {
        match retry {
            NaturalRetry::AddTask => self.begin_add_task_step(),
            NaturalRetry::Dialog => self.begin_add_task_natural_with_value(value),
        }
    }

    fn add_task_project_context(&self) -> Option<String> {
        self.authoring
            .selected_add_task_project()
            .flatten()
            .or_else(|| match &self.store.view_state.scope {
                TaskScope::Project(project) => Some(project.clone()),
                TaskScope::Workspace => None,
            })
    }

    async fn submit_created_task(&mut self, draft: TaskDraft) -> Result<()> {
        let current_selected = self.widgets.table.selected();
        let (message, selected) = self.store.create_task(draft, current_selected).await?;
        self.widgets.table.select(selected);
        self.preserve_or_restore_sidebar_selection();
        self.prune_task_marks();
        if selected.is_none() {
            self.restore_selection_after_mutation();
        }
        self.set_success(message.clone());
        if self.add_task_only {
            self.add_task_only_message = Some(message);
            self.should_quit = true;
        }
        Ok(())
    }

    pub(super) async fn submit_add_note(&mut self, body: String) -> Result<()> {
        match self.authoring.submit_add_note(body) {
            AddNoteSubmit::Create {
                task_id,
                display_ref,
                body,
                return_to_detail,
            } => {
                let note_id = self.store.add_note_to_task(&task_id, body).await?;
                self.refresh().await?;
                self.restore_detail_overlay(return_to_detail);
                self.set_success(format!("added note {note_id} to {display_ref}"));
            }
            AddNoteSubmit::Blank {
                return_to_detail,
                message,
            } => {
                self.restore_detail_overlay(return_to_detail);
                self.set_warning(message);
            }
            AddNoteSubmit::Inactive { message } => {
                self.set_info(message);
            }
        }
        Ok(())
    }

    pub(super) fn cancel_authoring_overlay(&mut self) {
        self.pending_shortcut.clear();
        let return_to_detail = self.authoring.cancel() || self.detail_context;
        self.overlay = None;
        self.conflict_flow.clear();
        self.pending_rename_project = None;
        self.pending_delete_project = None;
        self.detail_context = false;
        self.restore_detail_overlay(return_to_detail);
    }
}

fn add_task_natural_intake(title: &str, description: &str) -> String {
    let title = title.trim();
    let description = description.trim();
    match (title.is_empty(), description.is_empty()) {
        (false, false) => format!("Title:\n{title}\n\nDescription:\n{description}"),
        (false, true) => title.to_string(),
        (true, false) => format!("Description:\n{description}"),
        (true, true) => String::new(),
    }
}

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

    #[test]
    fn add_task_natural_intake_combines_title_and_description() {
        assert_eq!(
            add_task_natural_intake("Write docs", "Include setup details"),
            "Title:\nWrite docs\n\nDescription:\nInclude setup details"
        );
    }
}
