use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};

use gotcha_gitea::{Provider, RepositoryId};

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum EditorAction {
    Issue {
        repository: RepositoryId,
        number: Option<i64>,
    },
    Comment {
        repository: RepositoryId,
        number: i64,
        comment_id: Option<i64>,
    },
    Milestone {
        repository: RepositoryId,
        id: Option<i64>,
    },
    Server {
        original_name: Option<String>,
    },
    Settings,
    IssueFilter,
    PullFilter,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Field {
    pub label: String,
    pub value: String,
    pub secret: bool,
    pub multiline: bool,
}

impl Field {
    pub fn new(label: &str, value: impl Into<String>) -> Self {
        Self {
            label: label.into(),
            value: value.into(),
            secret: false,
            multiline: false,
        }
    }

    pub fn secret(mut self) -> Self {
        self.secret = true;
        self
    }

    pub fn multiline(mut self) -> Self {
        self.multiline = true;
        self
    }
}

#[derive(Clone, Debug)]
pub struct Editor {
    pub title: String,
    pub fields: Vec<Field>,
    pub focus: usize,
    pub cursor: usize,
    pub action: EditorAction,
}

impl Editor {
    pub fn issue(repository: RepositoryId, number: Option<i64>, fields: [String; 6]) -> Self {
        let [title, body, labels, milestone, due_date, state] = fields;
        Self::new(
            if number.is_some() {
                "Edit issue"
            } else {
                "New issue"
            },
            EditorAction::Issue { repository, number },
            vec![
                Field::new("Title", title),
                Field::new("Body", body).multiline(),
                Field::new("Labels (comma separated)", labels),
                Field::new("Milestone", milestone),
                Field::new("Due date (YYYY-MM-DD)", due_date),
                Field::new("State (open/closed)", state),
            ],
        )
    }

    pub fn comment(
        repository: RepositoryId,
        number: i64,
        comment_id: Option<i64>,
        body: String,
    ) -> Self {
        Self::new(
            if comment_id.is_some() {
                "Edit comment"
            } else {
                "New comment"
            },
            EditorAction::Comment {
                repository,
                number,
                comment_id,
            },
            vec![Field::new("Comment", body).multiline()],
        )
    }

    pub fn issue_filter(state: &str, labels: &str, milestone: &str, search: &str) -> Self {
        Self::new(
            "Issue filters",
            EditorAction::IssueFilter,
            vec![
                Field::new("State (open/closed/all)", state),
                Field::new("Labels (comma separated)", labels),
                Field::new("Milestone", milestone),
                Field::new("Search", search),
            ],
        )
    }

    pub fn pull_filter(state: &str, milestone: &str, search: &str) -> Self {
        Self::new(
            "Pull request filters",
            EditorAction::PullFilter,
            vec![
                Field::new("State (open/closed)", state),
                Field::new("Milestone", milestone),
                Field::new("Search", search),
            ],
        )
    }

    pub fn milestone(
        repository: RepositoryId,
        id: Option<i64>,
        title: String,
        description: String,
        due_date: String,
        state: String,
    ) -> Self {
        Self::new(
            if id.is_some() {
                "Edit milestone"
            } else {
                "New milestone"
            },
            EditorAction::Milestone { repository, id },
            vec![
                Field::new("Title", title),
                Field::new("Description", description).multiline(),
                Field::new("Due date (YYYY-MM-DD)", due_date),
                Field::new("State (open/closed)", state),
            ],
        )
    }

    pub fn server(
        original_name: Option<String>,
        name: String,
        url: String,
        provider: Provider,
    ) -> Self {
        Self::new(
            if original_name.is_some() {
                "Edit server"
            } else {
                "Add server"
            },
            EditorAction::Server { original_name },
            vec![
                Field::new("Profile name (host[:port])", name),
                Field::new("Server URL", url),
                Field::new("Token (blank keeps existing)", "").secret(),
                Field::new("Provider (gitea/forgejo)", provider.to_string()),
            ],
        )
    }

    pub fn settings(refresh_seconds: u64) -> Self {
        Self::new(
            "TUI preferences",
            EditorAction::Settings,
            vec![Field::new(
                "Auto-reload seconds (0 disables)",
                refresh_seconds.to_string(),
            )],
        )
    }

    fn new(title: &str, action: EditorAction, fields: Vec<Field>) -> Self {
        let cursor = fields
            .first()
            .map_or(0, |field| field.value.chars().count());
        Self {
            title: title.into(),
            fields,
            focus: 0,
            cursor,
            action,
        }
    }

    pub fn handle_key(&mut self, key: KeyEvent) -> EditorEvent {
        if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('s') {
            return EditorEvent::Submit;
        }
        match key.code {
            KeyCode::Esc => EditorEvent::Cancel,
            KeyCode::Tab | KeyCode::Down => {
                self.move_focus(1);
                EditorEvent::Changed
            }
            KeyCode::BackTab | KeyCode::Up => {
                self.move_focus(self.fields.len().saturating_sub(1));
                EditorEvent::Changed
            }
            KeyCode::Left => {
                self.cursor = self.cursor.saturating_sub(1);
                EditorEvent::Changed
            }
            KeyCode::Right => {
                self.cursor = (self.cursor + 1).min(self.current_len());
                EditorEvent::Changed
            }
            KeyCode::Home => {
                self.cursor = 0;
                EditorEvent::Changed
            }
            KeyCode::End => {
                self.cursor = self.current_len();
                EditorEvent::Changed
            }
            KeyCode::Backspace => {
                if self.cursor > 0 {
                    let end = byte_index(&self.fields[self.focus].value, self.cursor);
                    let start = byte_index(&self.fields[self.focus].value, self.cursor - 1);
                    self.fields[self.focus].value.replace_range(start..end, "");
                    self.cursor -= 1;
                }
                EditorEvent::Changed
            }
            KeyCode::Delete => {
                if self.cursor < self.current_len() {
                    let start = byte_index(&self.fields[self.focus].value, self.cursor);
                    let end = byte_index(&self.fields[self.focus].value, self.cursor + 1);
                    self.fields[self.focus].value.replace_range(start..end, "");
                }
                EditorEvent::Changed
            }
            KeyCode::Enter if self.fields[self.focus].multiline => {
                self.insert('\n');
                EditorEvent::Changed
            }
            KeyCode::Enter => {
                self.move_focus(1);
                EditorEvent::Changed
            }
            KeyCode::Char(character)
                if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT =>
            {
                self.insert(character);
                EditorEvent::Changed
            }
            _ => EditorEvent::Ignored,
        }
    }

    fn insert(&mut self, character: char) {
        let index = byte_index(&self.fields[self.focus].value, self.cursor);
        self.fields[self.focus].value.insert(index, character);
        self.cursor += 1;
    }

    fn move_focus(&mut self, amount: usize) {
        self.focus = (self.focus + amount) % self.fields.len();
        self.cursor = self.current_len();
    }

    fn current_len(&self) -> usize {
        self.fields[self.focus].value.chars().count()
    }

    pub fn values(&self) -> Vec<String> {
        self.fields
            .iter()
            .map(|field| field.value.clone())
            .collect()
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EditorEvent {
    Changed,
    Submit,
    Cancel,
    Ignored,
}

fn byte_index(value: &str, character: usize) -> usize {
    value
        .char_indices()
        .nth(character)
        .map_or(value.len(), |(index, _)| index)
}

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

    #[test]
    fn editor_backspace_edits_instead_of_navigating() {
        let mut editor = Editor::settings(5);
        editor.fields[0].value = "é5".into();
        editor.cursor = 1;
        assert_eq!(
            editor.handle_key(KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE)),
            EditorEvent::Changed
        );
        assert_eq!(editor.fields[0].value, "5");
        assert_eq!(editor.cursor, 0);
        assert_eq!(
            editor.handle_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE)),
            EditorEvent::Changed
        );
    }
}
