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

pub fn issue_page(details: IssueDetails) -> IssuePage {
    IssuePage {
        title: details
            .issue
            .title
            .clone()
            .unwrap_or_else(|| "Untitled issue".into()),
        state: work_item_state(details.issue.state.as_deref()),
        meta: issue_meta(&details.issue),
        milestone: issue_milestone(&details.issue),
        labels: details
            .issue
            .labels
            .as_deref()
            .unwrap_or_default()
            .iter()
            .filter_map(label_row)
            .collect(),
        body: details
            .issue
            .body
            .filter(|body| !body.is_empty())
            .unwrap_or_else(|| "No description provided.".into()),
        comments: details
            .comments
            .iter()
            .map(|comment| comment_row(comment, details.viewer_id))
            .collect(),
        has_more: details.has_more,
    }
}

pub fn issue_editor_page(data: IssueEditorData) -> IssueEditorPage {
    let selected_labels: std::collections::BTreeSet<_> = data
        .issue
        .as_ref()
        .and_then(|issue| issue.labels.as_deref())
        .unwrap_or_default()
        .iter()
        .filter_map(|label| label.id)
        .collect();
    let selected_milestone = data
        .issue
        .as_ref()
        .and_then(|issue| issue.milestone.as_ref())
        .and_then(|milestone| milestone.id);
    let mut labels: Vec<_> = data
        .labels
        .into_iter()
        .filter_map(|label| {
            let id = label.id?;
            Some(IssueEditorLabel {
                id,
                name: label.name?,
                selected: selected_labels.contains(&id),
            })
        })
        .collect();
    labels.sort_by_key(|label| label.name.to_lowercase());
    let mut milestones: Vec<_> = data
        .milestones
        .into_iter()
        .filter(|milestone| {
            milestone.state.as_deref() != Some("closed") || milestone.id == selected_milestone
        })
        .filter_map(|milestone| {
            let id = milestone.id?;
            Some(IssueEditorMilestone {
                id,
                title: milestone.title?,
                selected: Some(id) == selected_milestone,
            })
        })
        .collect();
    milestones.sort_by_key(|milestone| milestone.title.to_lowercase());
    IssueEditorPage {
        title: data
            .issue
            .as_ref()
            .and_then(|issue| issue.title.clone())
            .unwrap_or_default(),
        body: data
            .issue
            .as_ref()
            .and_then(|issue| issue.body.clone())
            .unwrap_or_default(),
        due_date: data
            .issue
            .as_ref()
            .and_then(|issue| issue.due_date.as_deref())
            .and_then(parse_api_date),
        closed: data.issue.as_ref().and_then(|issue| issue.state.as_deref()) == Some("closed"),
        labels,
        milestones,
    }
}

pub fn pull_page(details: PullDetails) -> PullPage {
    let pull = &details.pull;
    let author = pull
        .user
        .as_ref()
        .and_then(|user| user.login.as_deref())
        .unwrap_or("unknown");
    let head = pull
        .head
        .as_ref()
        .and_then(|branch| branch.label.as_deref().or(branch.r#ref.as_deref()))
        .unwrap_or("head");
    let base = pull
        .base
        .as_ref()
        .and_then(|branch| branch.label.as_deref().or(branch.r#ref.as_deref()))
        .unwrap_or("base");
    let state = pull_state(pull);
    PullPage {
        title: pull
            .title
            .clone()
            .unwrap_or_else(|| "Untitled pull request".into()),
        state: work_item_state(pull.state.as_deref()),
        meta: format!(
            "#{}  ·  {state}  ·  {author}  ·  {head} → {base}\n{} files  ·  +{} −{}  ·  {} comments",
            pull.number.unwrap_or_default(),
            pull.changed_files.unwrap_or_default(),
            pull.additions.unwrap_or_default(),
            pull.deletions.unwrap_or_default(),
            pull.comments.unwrap_or_default(),
        ),
        body: pull
            .body
            .clone()
            .filter(|body| !body.is_empty())
            .unwrap_or_else(|| "No description provided.".into()),
        files_ref: pull_files_ref(pull),
        files: details.files.iter().filter_map(file_row).collect(),
        comments: details
            .comments
            .iter()
            .map(|comment| comment_row(comment, None))
            .collect(),
        has_more: details.has_more,
    }
}

pub fn file_rows(files: Vec<models::ChangedFile>) -> Vec<FileRow> {
    files.iter().filter_map(file_row).collect()
}

pub fn commit_file_rows(files: Vec<models::CommitAffectedFiles>) -> Vec<FileRow> {
    files
        .into_iter()
        .filter_map(|file| {
            Some(FileRow {
                path: file.filename?,
                status: file.status.unwrap_or_else(|| "modified".into()),
            })
        })
        .collect()
}

pub fn commit_details_page(commit: models::Commit, branch: Option<String>) -> CommitDetailsPage {
    let details = commit.commit.as_deref();
    let message = details
        .and_then(|details| details.message.as_deref())
        .unwrap_or("Commit");
    let mut lines = message.lines();
    let title = lines.next().unwrap_or("Commit").to_string();
    let description = lines.collect::<Vec<_>>().join("\n").trim().to_string();
    let author = details
        .and_then(|details| details.author.as_deref())
        .and_then(commit_user)
        .or_else(|| {
            commit
                .author
                .as_deref()
                .and_then(|author| author.login.clone())
        });
    let committer = details
        .and_then(|details| details.committer.as_deref())
        .and_then(commit_user)
        .or_else(|| {
            commit
                .committer
                .as_deref()
                .and_then(|committer| committer.login.clone())
        });
    let date = details
        .and_then(|details| details.committer.as_deref())
        .and_then(|committer| committer.date.as_deref())
        .or_else(|| {
            details
                .and_then(|details| details.author.as_deref())
                .and_then(|author| author.date.as_deref())
        })
        .or(commit.created.as_deref());
    let files = commit.files.unwrap_or_default();
    let mut metadata = Vec::new();
    if let Some(author) = author {
        metadata.push(metadata_row("Author", author, false));
    }
    if let Some(committer) = committer {
        metadata.push(metadata_row("Committer", committer, false));
    }
    if date.is_some() {
        metadata.push(metadata_row("Committed", compact_date(date), false));
    }
    if let Some(branch) = branch.filter(|branch| !branch.is_empty()) {
        metadata.push(metadata_row("Branch", branch, false));
    }
    metadata.push(metadata_row(
        "Commit",
        commit.sha.clone().unwrap_or_else(|| "unknown".into()),
        true,
    ));
    let verification = details.and_then(|details| details.verification.as_deref());
    metadata.push(metadata_row(
        "Signature",
        match verification.filter(|verification| {
            verification
                .signature
                .as_deref()
                .is_some_and(|signature| !signature.is_empty())
        }) {
            Some(verification) if verification.verified.unwrap_or(false) => "Verified".into(),
            Some(_) => "Unverified".into(),
            None => "Unsigned".into(),
        },
        false,
    ));
    CommitDetailsPage {
        title,
        description,
        metadata,
        files: commit_file_rows(files),
    }
}

fn commit_user(user: &models::CommitUser) -> Option<String> {
    match (user.name.as_deref(), user.email.as_deref()) {
        (Some(name), Some(email)) => Some(format!("{name} <{email}>")),
        (Some(name), None) => Some(name.into()),
        (None, Some(email)) => Some(email.into()),
        (None, None) => None,
    }
}

fn metadata_row(label: &str, value: String, monospaced: bool) -> CommitMetadataRow {
    CommitMetadataRow {
        label: label.into(),
        value,
        monospaced,
    }
}
