use std::sync::Mutex;
use thiserror::Error;

mod api;
mod core;
mod domain;
mod presentation;
mod storage;

use api::*;
use domain::*;
pub use presentation::*;
use storage::*;

uniffi::setup_scaffolding!();

#[derive(Clone, Copy, Debug, uniffi::Enum)]
pub enum RepositoryPane {
    Issues,
    Commits,
    Milestones,
}

#[derive(Clone, Copy, Debug, uniffi::Enum)]
pub enum ServerProvider {
    Gitea,
    Forgejo,
}

impl From<ServerProvider> for gotcha_gitea::Provider {
    fn from(provider: ServerProvider) -> Self {
        match provider {
            ServerProvider::Gitea => Self::Gitea,
            ServerProvider::Forgejo => Self::Forgejo,
        }
    }
}

impl From<gotcha_gitea::Provider> for ServerProvider {
    fn from(provider: gotcha_gitea::Provider) -> Self {
        match provider {
            gotcha_gitea::Provider::Gitea => Self::Gitea,
            gotcha_gitea::Provider::Forgejo => Self::Forgejo,
        }
    }
}

impl RepositoryPane {
    const ALL: [Self; 3] = [Self::Issues, Self::Commits, Self::Milestones];

    const fn key(self) -> &'static str {
        match self {
            Self::Issues => "issues",
            Self::Commits => "commits",
            Self::Milestones => "milestones",
        }
    }
}

#[derive(Debug, Error, uniffi::Error)]
pub enum GotchaError {
    #[error("{message}")]
    Message { message: String },
}

impl From<String> for GotchaError {
    fn from(message: String) -> Self {
        Self::Message { message }
    }
}

impl From<&str> for GotchaError {
    fn from(message: &str) -> Self {
        message.to_string().into()
    }
}

#[derive(Clone, uniffi::Record)]
pub struct Settings {
    pub issue_status: String,
    pub pull_status: String,
    pub appearance: u32,
    pub notifications_enabled: bool,
}

#[derive(uniffi::Object)]
pub struct GotchaCore {
    state: Mutex<State>,
    server_activity: tokio::sync::Mutex<Option<ServerActivitySession>>,
    startup_error: Option<String>,
}

struct ServerActivitySession {
    server_id: String,
    next_page: u32,
    pager: gotcha_gitea::ServerActivityPager,
}

fn validate_repository<'a>(
    owner: &'a str,
    repository: &'a str,
) -> Result<(&'a str, &'a str), GotchaError> {
    let owner = owner.trim();
    let repository = repository.trim();
    if owner.is_empty() || repository.is_empty() {
        return Err("Select a repository first.".into());
    }
    Ok((owner, repository))
}

fn valid_page(page: u32) -> Result<i32, GotchaError> {
    (page > 0)
        .then(|| i32::try_from(page).ok())
        .flatten()
        .ok_or_else(|| "Invalid page number.".into())
}

impl GotchaCore {
    fn server(&self) -> Result<Server, GotchaError> {
        let state = self.state.lock().unwrap();
        state
            .active_server
            .and_then(|index| state.preferences.servers.get(index))
            .cloned()
            .ok_or_else(|| "Select a server first.".to_string().into())
    }

    fn server_by_id(&self, id: &str) -> Result<Server, GotchaError> {
        self.state
            .lock()
            .unwrap()
            .preferences
            .servers
            .iter()
            .find(|server| server.credential_account == id)
            .cloned()
            .ok_or_else(|| "That server is no longer configured.".into())
    }

    fn repository_rows(&self, state: &State, pane: RepositoryPane) -> Vec<RepositoryRow> {
        let server_url = state
            .active_server
            .and_then(|index| state.preferences.servers.get(index))
            .map(|server| server.url.as_str())
            .unwrap_or_default();
        repository_rows(&state.repositories, |repository| {
            state.preferences.favorites.contains(&favorite_key(
                pane,
                server_url,
                &repository.owner,
                &repository.name,
            ))
        })
    }
}
