use super::*;

pub(super) fn label_row(label: &models::Label) -> Option<LabelRow> {
    let name = label.name.as_deref()?.trim();
    let color = label.color.as_deref()?.trim().trim_start_matches('#');
    if name.is_empty() || color.len() != 6 {
        return None;
    }
    let value = u32::from_str_radix(color, 16).ok()?;
    let red = (value >> 16) & 0xff;
    let green = (value >> 8) & 0xff;
    let blue = value & 0xff;
    Some(LabelRow {
        name: name.into(),
        color: format!("#{color}"),
        light: red * 299 + green * 587 + blue * 114 > 150_000,
    })
}

pub(super) fn comment_row(comment: &models::Comment, viewer_id: Option<i64>) -> CommentRow {
    CommentRow {
        id: comment.id.unwrap_or_default(),
        author: comment
            .user
            .as_ref()
            .and_then(|user| user.login.as_deref())
            .or(comment.original_author.as_deref())
            .unwrap_or("unknown")
            .into(),
        body: comment
            .body
            .clone()
            .filter(|body| !body.is_empty())
            .unwrap_or_else(|| "No comment text.".into()),
        meta: compact_date(
            comment
                .updated_at
                .as_deref()
                .or(comment.created_at.as_deref()),
        ),
        can_edit: comment_can_edit(comment, viewer_id),
    }
}

pub(super) fn file_row(file: &models::ChangedFile) -> Option<FileRow> {
    Some(FileRow {
        path: file.filename.clone()?,
        status: file.status.clone().unwrap_or_else(|| "modified".into()),
    })
}

pub(super) fn pull_files_ref(pull: &models::PullRequest) -> String {
    match pull_file_source(pull) {
        PullFileSource::Branch(branch) => format!("Files on {branch}"),
        PullFileSource::Commit(sha) => format!("Files at merge commit {sha}"),
        PullFileSource::Request => "Files from pull request".into(),
    }
}

pub(super) fn issue_meta(issue: &models::Issue) -> String {
    let author = issue
        .user
        .as_ref()
        .and_then(|user| user.login.as_deref())
        .unwrap_or("unknown");
    format!(
        "#{}  ·  {}  ·  updated {}  ·  {} comments",
        issue.number.unwrap_or_default(),
        author,
        compact_date(issue.updated_at.as_deref()),
        issue.comments.unwrap_or_default()
    )
}

pub(super) fn issue_milestone(issue: &models::Issue) -> String {
    issue
        .milestone
        .as_ref()
        .and_then(|milestone| milestone.title.as_deref())
        .unwrap_or_default()
        .into()
}

pub(super) fn milestone_row(milestone: &models::Milestone) -> MilestoneRow {
    let open = milestone.open_issues.unwrap_or_default();
    let closed = milestone.closed_issues.unwrap_or_default();
    let total = open + closed;
    let due = milestone
        .due_on
        .as_deref()
        .map(|date| format!("due {}", compact_date(Some(date))))
        .unwrap_or_else(|| "no due date".into());
    MilestoneRow {
        id: milestone.id.unwrap_or_default(),
        state: work_item_state(milestone.state.as_deref()),
        title: milestone
            .title
            .clone()
            .unwrap_or_else(|| "Untitled milestone".into()),
        description: milestone
            .description
            .clone()
            .filter(|description| !description.is_empty())
            .unwrap_or_else(|| "No description".into()),
        meta: format!(
            "{}  ·  {closed} of {} closed  ·  {due}",
            milestone.state.as_deref().unwrap_or("unknown"),
            open + closed
        ),
        has_issues: total > 0,
        progress: if total == 0 {
            0.0
        } else {
            closed as f64 / total as f64
        },
        progress_accessibility: format!("{closed} closed, {open} open"),
    }
}

pub(super) fn work_item_state(state: Option<&str>) -> WorkItemState {
    match state {
        Some("open") => WorkItemState::Open,
        Some("closed") => WorkItemState::Closed,
        Some(_) | None => WorkItemState::Unknown,
    }
}

pub(super) fn short_sha(sha: &str) -> &str {
    &sha[..sha.len().min(8)]
}

pub(super) fn source_language(path: &str) -> Option<&'static str> {
    let filename = path.rsplit('/').next()?.to_lowercase();
    match filename.as_str() {
        "cmakelists.txt" => return Some("cmake"),
        "dockerfile" => return Some("dockerfile"),
        "gemfile" | "podfile" => return Some("ruby"),
        "makefile" => return Some("makefile"),
        _ => {}
    }
    match filename.rsplit('.').next()? {
        "asm" => Some("x86asm"),
        "c" => Some("c"),
        "cc" | "cpp" | "h" | "hpp" => Some("cpp"),
        "clj" => Some("clojure"),
        "cs" => Some("csharp"),
        "css" => Some("css"),
        "dart" => Some("dart"),
        "ex" | "exs" => Some("elixir"),
        "fs" => Some("fsharp"),
        "go" => Some("go"),
        "groovy" => Some("groovy"),
        "hs" => Some("haskell"),
        "html" | "plist" | "xml" => Some("xml"),
        "java" => Some("java"),
        "jl" => Some("julia"),
        "js" | "jsx" => Some("javascript"),
        "json" => Some("json"),
        "kt" | "kts" => Some("kotlin"),
        "lua" => Some("lua"),
        "m" | "mm" => Some("objectivec"),
        "md" | "markdown" | "mdown" | "mkd" => Some("markdown"),
        "php" => Some("php"),
        "pl" => Some("perl"),
        "ps1" => Some("powershell"),
        "py" => Some("python"),
        "r" => Some("r"),
        "rb" => Some("ruby"),
        "rs" => Some("rust"),
        "scala" => Some("scala"),
        "scss" => Some("scss"),
        "sh" => Some("bash"),
        "sql" => Some("sql"),
        "swift" => Some("swift"),
        "toml" => Some("ini"),
        "ts" | "tsx" => Some("typescript"),
        "txt" => Some("plaintext"),
        "yaml" | "yml" => Some("yaml"),
        _ => None,
    }
}

pub(super) fn summary(body: &str) -> String {
    body.split_whitespace()
        .take(24)
        .collect::<Vec<_>>()
        .join(" ")
}

pub(super) fn indices(values: &[usize]) -> Vec<u32> {
    values.iter().map(|value| *value as u32).collect()
}

pub fn compact_date(date: Option<&str>) -> String {
    date.and_then(|date| date.get(..10))
        .unwrap_or("unknown")
        .to_string()
}
