use super::{helpers::*, *};

pub fn repository_rows(
    repositories: &[RepositoryData],
    is_favorite: impl Fn(&RepositoryData) -> bool,
) -> Vec<RepositoryRow> {
    let mut repositories = repositories.to_vec();
    repositories
        .sort_by_key(|repository| (!is_favorite(repository), repository.name.to_lowercase()));
    repositories
        .into_iter()
        .map(|repository| RepositoryRow {
            favorite: is_favorite(&repository),
            meta: format!(
                "{}  ·  {} open  ·  {}",
                repository.language, repository.open_issues, repository.updated
            ),
            name: repository.name,
            owner: repository.owner,
            description: repository.description,
        })
        .collect()
}

pub fn issue_rows(issues: &[models::Issue]) -> Vec<IssueRow> {
    issues
        .iter()
        .map(|issue| IssueRow {
            number: issue.number.unwrap_or_default(),
            state: work_item_state(issue.state.as_deref()),
            title: issue
                .title
                .clone()
                .unwrap_or_else(|| "Untitled issue".into()),
            summary: issue
                .body
                .as_deref()
                .map(summary)
                .unwrap_or_else(|| "No description".into()),
            meta: issue_meta(issue),
            milestone: issue_milestone(issue),
            labels: issue
                .labels
                .as_deref()
                .unwrap_or_default()
                .iter()
                .filter_map(label_row)
                .collect(),
        })
        .collect()
}

pub fn issue_filter_options(
    labels: &[models::Label],
    milestones: &[models::Milestone],
    filter: &IssueFilter,
) -> IssueFilterOptions {
    let mut labels: Vec<_> = labels
        .iter()
        .filter_map(|label| label.name.clone())
        .collect();
    let unavailable_labels = filter
        .labels
        .iter()
        .filter(|label| !labels.contains(label))
        .cloned()
        .collect();
    labels.extend(filter.labels.iter().cloned());
    labels.sort_by_key(|label| label.to_lowercase());
    labels.dedup();
    let mut milestones: Vec<_> = milestones
        .iter()
        .filter_map(|milestone| milestone.title.clone())
        .collect();
    if !filter.milestone.is_empty() {
        milestones.push(filter.milestone.clone());
    }
    milestones.sort_by_key(|milestone| milestone.to_lowercase());
    milestones.dedup();
    IssueFilterOptions {
        milestones,
        labels,
        unavailable_labels,
        selected_milestone: filter.milestone.clone(),
        selected_labels: filter.labels.iter().cloned().collect(),
        search_text: filter.search_text.clone(),
    }
}

pub fn milestone_rows(milestones: &[models::Milestone]) -> Vec<MilestoneRow> {
    let mut milestones = milestones.to_vec();
    milestones.sort_by_key(|milestone| {
        (
            milestone.state.as_deref() == Some("closed"),
            milestone
                .title
                .as_deref()
                .unwrap_or_default()
                .to_lowercase(),
        )
    });
    milestones.iter().map(milestone_row).collect()
}

pub fn milestone_page(details: MilestoneDetails) -> MilestonePage {
    MilestonePage {
        milestone: milestone_row(&details.milestone),
        issues: issue_rows(&details.issues),
        pulls: pull_rows(&details.pulls),
        has_more: details.has_more,
    }
}

pub fn milestone_editor_page(milestone: Option<models::Milestone>) -> MilestoneEditorPage {
    MilestoneEditorPage {
        title: milestone
            .as_ref()
            .and_then(|milestone| milestone.title.clone())
            .unwrap_or_default(),
        description: milestone
            .as_ref()
            .and_then(|milestone| milestone.description.clone())
            .unwrap_or_default(),
        due_date: milestone
            .as_ref()
            .and_then(|milestone| milestone.due_on.as_deref())
            .and_then(parse_api_date),
        closed: milestone
            .as_ref()
            .and_then(|milestone| milestone.state.as_deref())
            == Some("closed"),
    }
}

pub fn pull_filter_options(milestones: &[String], filter: &PullFilter) -> PullFilterOptions {
    let mut milestones = milestones.to_vec();
    if !filter.milestone.is_empty() {
        milestones.push(filter.milestone.clone());
    }
    milestones.sort_by_key(|milestone| milestone.to_lowercase());
    milestones.dedup();
    PullFilterOptions {
        milestones,
        selected_milestone: filter.milestone.clone(),
        search_text: filter.search_text.clone(),
    }
}

pub fn pull_rows(pulls: &[models::Issue]) -> Vec<PullRow> {
    pulls
        .iter()
        .filter_map(|pull| {
            let repository = pull.repository.as_ref()?;
            let author = pull
                .user
                .as_ref()
                .and_then(|user| user.login.as_deref())
                .unwrap_or("unknown");
            let kind = if pull
                .pull_request
                .as_ref()
                .and_then(|pull| pull.draft)
                .unwrap_or(false)
            {
                "Draft"
            } else {
                pull.state.as_deref().unwrap_or("unknown")
            };
            Some(PullRow {
                number: pull.number.unwrap_or_default(),
                owner: repository.owner.clone()?,
                repository: repository.name.clone()?,
                state: work_item_state(pull.state.as_deref()),
                title: format!(
                    "{}  #{}\n{}",
                    repository.name.as_deref()?,
                    pull.number.unwrap_or_default(),
                    pull.title.as_deref().unwrap_or("Untitled pull request")
                ),
                summary: pull
                    .body
                    .as_deref()
                    .map(summary)
                    .filter(|body| !body.is_empty())
                    .unwrap_or_else(|| "No description".into()),
                meta: format!(
                    "{kind}  ·  {author}  ·  {}  ·  {} comments",
                    compact_date(pull.updated_at.as_deref()),
                    pull.comments.unwrap_or_default()
                ),
            })
        })
        .collect()
}

pub fn commit_page(
    branches: Vec<String>,
    commits: &[HistoryCommit],
    show_graph: bool,
    has_more: bool,
) -> CommitPage {
    let lane_count = if show_graph {
        commits
            .iter()
            .flat_map(|commit| {
                commit
                    .top_lanes
                    .iter()
                    .chain(commit.bottom_lanes.iter())
                    .chain(commit.top_connections.iter())
                    .chain(commit.bottom_connections.iter())
                    .chain(commit.node_lane.iter())
            })
            .max()
            .map_or(0, |lane| lane + 1)
    } else {
        0
    };
    let commits = commits
        .iter()
        .filter_map(|row| {
            let commit = &row.commit;
            let sha = commit.sha.as_deref()?;
            let details = commit.commit.as_ref();
            let author = details
                .and_then(|commit| commit.author.as_ref())
                .and_then(|author| author.name.as_deref())
                .or_else(|| {
                    commit
                        .author
                        .as_ref()
                        .and_then(|author| author.login.as_deref())
                })
                .unwrap_or("unknown");
            let date = details
                .and_then(|commit| commit.author.as_ref())
                .and_then(|author| author.date.as_deref())
                .or(commit.created.as_deref());
            let mut branch_labels = row.refs.clone();
            for label in &row.branch_starts {
                if !branch_labels.contains(label) {
                    branch_labels.push(label.clone());
                }
            }
            Some(CommitRow {
                sha: sha.into(),
                title: details
                    .and_then(|commit| commit.message.as_deref())
                    .map(|message| message.lines().next().unwrap_or(message))
                    .unwrap_or("Commit")
                    .into(),
                detail: if !branch_labels.is_empty() {
                    format!("{author}  ·  {}  ·  {}", compact_date(date), short_sha(sha))
                } else {
                    format!("{author}  ·  {}\n{}", compact_date(date), short_sha(sha))
                },
                branch_label: (!branch_labels.is_empty()).then(|| branch_labels.join("  ·  ")),
                top_lanes: indices(&row.top_lanes),
                bottom_lanes: indices(&row.bottom_lanes),
                node_lane: row.node_lane.map(|lane| lane as u32),
                top_connections: indices(&row.top_connections),
                bottom_connections: indices(&row.bottom_connections),
            })
        })
        .collect();
    CommitPage {
        branches,
        commits,
        lane_count: lane_count as u32,
        has_more,
    }
}
