use std::collections::BTreeSet;

use anyhow::Result;

use crate::labels::normalize_label;
use crate::tui::app::{App, FooterChoiceMode};
use crate::tui::overlay::{
    MultilineInputState, OverlayRoute, OverlayState, SearchPurpose, SearchState,
};
use crate::tui::platform::edit_text_externally;

pub(crate) const EDIT_TITLE_TITLE: &str = "Edit title";
pub(crate) const EDIT_DESCRIPTION_TITLE: &str = "Edit description";
pub(crate) const EDIT_PROJECT_TITLE: &str = "Edit project";
pub(crate) const EDIT_LABELS_TITLE: &str = "Edit task: labels";
pub(crate) const REMOVE_DEPENDENCY_TITLE: &str = "Remove dependency";

impl App {
    pub(super) async fn update_status(&mut self, status: &'static str) -> Result<()> {
        let task_ids = self.marked_task_ids_in_view();
        let result = if task_ids.is_empty() {
            let preserve_done_detail = status == "done"
                && (self.detail_context
                    || matches!(self.overlay, Some(OverlayState::Detail { .. })));
            if preserve_done_detail {
                self.store
                    .update_status_preserving_task(self.widgets.table.selected(), status)
                    .await?
            } else {
                self.store
                    .update_status(self.widgets.table.selected(), status)
                    .await?
            }
        } else {
            self.store
                .update_status_for_tasks(self.widgets.table.selected(), &task_ids, status)
                .await?
        };
        if let Some(result) = result {
            self.apply_mutation_result(result);
        } else {
            self.set_info("no selected task to edit");
        }
        Ok(())
    }

    pub(super) async fn set_exact_priority(&mut self, priority: &'static str) -> Result<()> {
        let task_ids = self.marked_task_ids_in_view();
        let result = if task_ids.is_empty() {
            self.store
                .set_exact_priority(self.widgets.table.selected(), priority)
                .await?
        } else {
            self.store
                .set_exact_priority_for_tasks(self.widgets.table.selected(), &task_ids, priority)
                .await?
        };
        if let Some(result) = result {
            self.apply_mutation_result(result);
        } else {
            self.set_info("no selected task to edit");
        }
        Ok(())
    }

    pub(super) async fn update_priority(&mut self, reverse: bool) -> Result<()> {
        let task_ids = self.marked_task_ids_in_view();
        let result = if task_ids.is_empty() {
            self.store
                .update_priority(self.widgets.table.selected(), reverse)
                .await?
        } else {
            self.store
                .update_priority_for_tasks(self.widgets.table.selected(), &task_ids, reverse)
                .await?
        };
        if let Some(result) = result {
            self.apply_mutation_result(result);
        } else {
            self.set_info("no selected task to edit");
        }
        Ok(())
    }

    pub(super) async fn update_deleted(&mut self, deleted: bool) -> Result<()> {
        let task_ids = self.marked_task_ids_in_view();
        let result = if task_ids.is_empty() {
            self.store
                .update_deleted(self.widgets.table.selected(), deleted)
                .await?
        } else {
            self.store
                .update_deleted_for_tasks(self.widgets.table.selected(), &task_ids, deleted)
                .await?
        };
        if let Some(result) = result {
            self.apply_mutation_result(result);
        } else {
            self.set_info("no selected task to edit");
        }
        Ok(())
    }

    pub(super) async fn undo_last(&mut self) -> Result<()> {
        let selected =
            if self.detail_context || matches!(self.overlay, Some(OverlayState::Detail { .. })) {
                None
            } else {
                self.widgets.table.selected()
            };
        match self.store.undo_last(selected).await? {
            Some(result) => self.apply_mutation_result(result),
            None => self.set_info("nothing to undo"),
        }
        Ok(())
    }

    fn guard_selected_task(&mut self) -> Option<usize> {
        self.pending_shortcut.clear();
        let index = self.widgets.table.selected();
        if index.is_some_and(|i| self.store.selected_task(Some(i)).is_some()) {
            index
        } else {
            self.set_info("no selected task to edit");
            None
        }
    }

    fn apply_edit_mutation<F>(
        &mut self,
        result: Result<Option<crate::tui::store::MutationMessage>>,
        on_error: F,
    ) where
        F: FnOnce(&mut Self),
    {
        match result {
            Ok(Some(result)) => self.apply_mutation_result(result),
            Ok(None) => self.set_info("no selected task to edit"),
            Err(error) => {
                self.set_error(format!("{error:#}"));
                on_error(self);
            }
        }
    }

    pub(super) fn begin_status_picker(&mut self) {
        let task_ids = self.marked_task_ids_in_view();
        if !task_ids.is_empty() {
            let items = self.store.status_picker_items(None);
            let count = task_ids.len();
            self.open_picker_overlay(
                OverlayRoute::EditStatus,
                format!("Edit status: {count} marked tasks"),
                items,
                false,
            );
            return;
        }
        if self.guard_selected_task().is_none() {
            return;
        }
        self.footer_choice_mode = Some(FooterChoiceMode::Status);
    }

    pub(super) fn begin_edit_title(&mut self) {
        let Some(index) = self.guard_selected_task() else {
            return;
        };
        let title = self
            .store
            .selected_task(Some(index))
            .unwrap()
            .task
            .title
            .clone();
        self.open_edit_title_overlay(title);
    }

    fn open_edit_title_overlay(&mut self, input: String) {
        self.detail_context_scroll = 0;
        self.overlay = Some(OverlayState::text_input(
            OverlayRoute::EditTitle,
            EDIT_TITLE_TITLE,
            "",
            input,
        ));
    }

    fn open_edit_description_overlay(&mut self, value: String) {
        self.overlay = Some(OverlayState::multiline_input(
            OverlayRoute::EditDescription,
            EDIT_DESCRIPTION_TITLE,
            "",
            value,
        ));
    }

    pub(super) fn begin_edit_description(&mut self) {
        let Some(index) = self.guard_selected_task() else {
            return;
        };
        let description = self
            .store
            .selected_task(Some(index))
            .unwrap()
            .task
            .description
            .clone();
        self.open_edit_description_overlay(description);
    }

    pub(super) fn begin_edit_project(&mut self) {
        let task_ids = self.marked_task_ids_in_view();
        if !task_ids.is_empty() {
            let items = self.store.existing_project_picker_items("");
            let count = task_ids.len();
            self.open_picker_overlay(
                OverlayRoute::EditProject,
                format!("Edit project: {count} marked tasks"),
                items,
                false,
            );
            return;
        }
        let Some(index) = self.guard_selected_task() else {
            return;
        };
        let selected = self
            .store
            .selected_task(Some(index))
            .unwrap()
            .task
            .project_key
            .as_str();
        let items = self.store.existing_project_picker_items(selected);
        self.open_picker_overlay(OverlayRoute::EditProject, EDIT_PROJECT_TITLE, items, false);
    }

    pub(super) fn begin_edit_priority(&mut self) {
        let task_ids = self.marked_task_ids_in_view();
        if !task_ids.is_empty() {
            let items = self.store.priority_picker_items("");
            let count = task_ids.len();
            self.open_picker_overlay(
                OverlayRoute::EditPriority,
                format!("Edit priority: {count} marked tasks"),
                items,
                false,
            );
            return;
        }
        if self.guard_selected_task().is_none() {
            return;
        }
        self.footer_choice_mode = Some(FooterChoiceMode::Priority);
    }

    pub(super) fn begin_edit_labels(&mut self) {
        let task_ids = self.marked_task_ids_in_view();
        if !task_ids.is_empty() {
            self.open_edit_labels_multi(task_ids);
            return;
        }
        let Some(index) = self.guard_selected_task() else {
            return;
        };
        let labels = self
            .store
            .selected_task(Some(index))
            .unwrap()
            .labels
            .clone();
        self.overlay = Some(OverlayState::tag_combobox(
            OverlayRoute::EditLabels,
            EDIT_LABELS_TITLE,
            self.store.labels.clone(),
            labels,
        ));
    }

    pub(super) async fn begin_add_dependency(&mut self) -> Result<()> {
        let Some(index) = self.guard_selected_task() else {
            return Ok(());
        };
        let item = self.store.selected_task(Some(index)).unwrap();
        let task_id = item.task.id.clone();
        let display_ref = item.display_ref.clone();
        self.clear_live_search_preview();
        self.overlay = Some(OverlayState::Search(SearchState::for_purpose(
            SearchPurpose::AddDependency {
                task_id,
                display_ref,
            },
        )));
        Ok(())
    }

    pub(super) fn begin_remove_dependency(&mut self) {
        let Some(index) = self.guard_selected_task() else {
            return;
        };
        let items = self.store.selected_dependency_picker_items(Some(index));
        self.open_picker_overlay(
            OverlayRoute::RemoveDependency,
            REMOVE_DEPENDENCY_TITLE,
            items,
            false,
        );
    }

    pub(super) async fn submit_edit_status(&mut self, status: String) -> Result<()> {
        let task_ids = self.marked_task_ids_in_view();
        let result = if task_ids.is_empty() {
            let preserve_done_detail = status == "done"
                && (self.detail_context
                    || matches!(self.overlay, Some(OverlayState::Detail { .. })));
            if preserve_done_detail {
                self.store
                    .update_status_preserving_task(self.widgets.table.selected(), &status)
                    .await
            } else {
                self.store
                    .update_status(self.widgets.table.selected(), &status)
                    .await
            }
        } else {
            self.store
                .update_status_for_tasks(self.widgets.table.selected(), &task_ids, &status)
                .await
        };
        self.apply_edit_mutation(result, |app| app.begin_status_picker());
        Ok(())
    }

    pub(super) async fn submit_edit_title(&mut self, value: String) -> Result<()> {
        let trimmed = value.trim().to_string();
        if trimmed.is_empty() {
            self.set_warning("task title is required");
            self.open_edit_title_overlay(value);
            return Ok(());
        }
        match self
            .store
            .update_title(self.widgets.table.selected(), trimmed)
            .await
        {
            Ok(Some(result)) => self.apply_mutation_result(result),
            Ok(None) => self.set_info("no selected task to edit"),
            Err(error) => {
                self.set_error(format!("{error:#}"));
                self.open_edit_title_overlay(value);
            }
        }
        Ok(())
    }

    pub(super) async fn submit_edit_description(&mut self, value: String) -> Result<()> {
        let result = self
            .store
            .update_description(self.widgets.table.selected(), value.clone())
            .await;
        self.apply_edit_mutation(result, |app| app.open_edit_description_overlay(value));
        Ok(())
    }

    pub(super) async fn submit_edit_project(&mut self, project: String) -> Result<()> {
        let task_ids = self.marked_task_ids_in_view();
        let result = if task_ids.is_empty() {
            self.store
                .update_project(self.widgets.table.selected(), project)
                .await
        } else {
            self.store
                .update_project_for_tasks(self.widgets.table.selected(), &task_ids, project)
                .await
        };
        self.apply_edit_mutation(result, |app| app.begin_edit_project());
        Ok(())
    }

    pub(super) async fn submit_edit_priority(&mut self, priority: String) -> Result<()> {
        let task_ids = self.marked_task_ids_in_view();
        let result = if task_ids.is_empty() {
            self.store
                .set_exact_priority(self.widgets.table.selected(), &priority)
                .await
        } else {
            self.store
                .set_exact_priority_for_tasks(self.widgets.table.selected(), &task_ids, &priority)
                .await
        };
        self.apply_edit_mutation(result, |app| app.begin_edit_priority());
        Ok(())
    }

    pub(super) async fn submit_edit_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_edit_labels();
                return Ok(());
            }
        }
        let result = self
            .store
            .update_labels(self.widgets.table.selected(), labels)
            .await;
        self.apply_edit_mutation(result, |app| app.begin_edit_labels());
        Ok(())
    }

    fn open_edit_labels_multi(&mut self, task_ids: Vec<String>) {
        let labels = self.store.union_labels_for_tasks(&task_ids);
        let count = task_ids.len();
        self.overlay = Some(OverlayState::tag_combobox(
            OverlayRoute::EditLabelsMulti,
            format!("Edit labels: {count} marked tasks"),
            self.store.labels.clone(),
            labels,
        ));
    }

    pub(super) async fn submit_edit_labels_multi(&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_edit_labels();
                return Ok(());
            }
        }
        let selected = self.widgets.table.selected();
        let task_ids = self.marked_task_ids_in_view();
        let result = self
            .store
            .update_labels_for_tasks(selected, &task_ids, labels)
            .await;
        self.apply_edit_mutation(result, |app| app.begin_edit_labels());
        Ok(())
    }

    pub(super) fn toggle_mark_selected(&mut self) {
        self.pending_shortcut.clear();
        let Some(index) = self.widgets.table.selected() else {
            self.set_info("no selected task to mark");
            return;
        };
        let Some((id, display_ref)) = self
            .store
            .selected_task(Some(index))
            .map(|item| (item.task.id.clone(), item.display_ref.clone()))
        else {
            self.set_info("no selected task to mark");
            return;
        };
        if self.widgets.marked_task_ids.insert(id.clone()) {
            self.set_info(format!("marked {display_ref}"));
        } else {
            self.widgets.marked_task_ids.remove(&id);
            self.set_info(format!("unmarked {display_ref}"));
        }
    }

    pub(super) fn toggle_mark_all_in_view(&mut self) {
        self.pending_shortcut.clear();
        let visible = self
            .store
            .tasks
            .iter()
            .map(|item| item.task.id.clone())
            .collect::<BTreeSet<_>>();
        if visible.is_empty() {
            self.set_info("no visible tasks to mark");
            return;
        }
        if visible
            .iter()
            .all(|id| self.widgets.marked_task_ids.contains(id))
        {
            let count = visible.len();
            self.widgets
                .marked_task_ids
                .retain(|id| !visible.contains(id));
            self.set_info(format!("unmarked {count} tasks"));
        } else {
            let count = visible.len();
            self.widgets.marked_task_ids.extend(visible);
            self.set_info(format!("marked {count} tasks"));
        }
    }

    pub(super) fn clear_marks(&mut self) {
        self.pending_shortcut.clear();
        let count = self.widgets.marked_task_ids.len();
        self.widgets.marked_task_ids.clear();
        self.set_info(format!("cleared {count} task marks"));
    }

    pub(super) fn marked_task_ids_in_view(&self) -> Vec<String> {
        self.store
            .tasks
            .iter()
            .filter(|item| self.widgets.marked_task_ids.contains(&item.task.id))
            .map(|item| item.task.id.clone())
            .collect()
    }

    pub(super) fn prune_task_marks(&mut self) {
        let visible = self
            .store
            .tasks
            .iter()
            .map(|item| item.task.id.clone())
            .collect::<BTreeSet<_>>();
        self.widgets
            .marked_task_ids
            .retain(|id| visible.contains(id));
    }

    pub(super) async fn submit_add_dependency(&mut self, depends_on_task_id: String) -> Result<()> {
        match self
            .store
            .add_dependency(self.widgets.table.selected(), &depends_on_task_id)
            .await
        {
            Ok(Some(result)) => self.apply_mutation_result(result),
            Ok(None) => self.set_info("no selected task to edit"),
            Err(error) => {
                self.set_error(format!("{error:#}"));
                self.begin_add_dependency().await?;
            }
        }
        Ok(())
    }

    pub(super) async fn submit_remove_dependency(
        &mut self,
        depends_on_task_id: String,
    ) -> Result<()> {
        match self
            .store
            .remove_dependency(self.widgets.table.selected(), &depends_on_task_id)
            .await
        {
            Ok(Some(result)) => self.apply_mutation_result(result),
            Ok(None) => self.set_info("no selected task to edit"),
            Err(error) => {
                self.set_error(format!("{error:#}"));
                self.begin_remove_dependency();
            }
        }
        Ok(())
    }
}

impl App {
    pub(super) fn open_description_external_editor(&mut self, state: MultilineInputState) {
        self.needs_terminal_clear = true;
        match edit_text_externally(state.lines.join("\n"), "description.md") {
            Ok(value) => self.overlay = Some(description_overlay_from_value(value)),
            Err(error) => {
                self.set_error(format!("editor failed: {error:#}"));
                self.overlay = Some(OverlayState::MultilineInput(state));
            }
        }
    }
}

fn description_overlay_from_value(value: String) -> OverlayState {
    OverlayState::multiline_input(
        OverlayRoute::EditDescription,
        EDIT_DESCRIPTION_TITLE,
        "",
        value,
    )
}
