use anyhow::{Context, Result};
use clap::Parser;
use gh_workflow::Workflow;
use std::fs;
use std::path::{Path, PathBuf};

mod after_release;
mod cherry_pick;
mod compare_perf;
mod danger;
mod extension_bump;
mod extension_release;
mod extension_tests;
mod extensions;
mod nix_build;
mod release_nightly;
mod run_bundling;

mod release;
mod run_agent_evals;
mod run_tests;
mod runners;
mod steps;
mod vars;

#[derive(Parser)]
pub struct GenerateWorkflowArgs {}

struct WorkflowFile {
    source: fn() -> Workflow,
    r#type: WorkflowType,
}

impl WorkflowFile {
    fn zed(f: fn() -> Workflow) -> WorkflowFile {
        WorkflowFile {
            source: f,
            r#type: WorkflowType::Zed,
        }
    }
    fn extension(f: fn() -> Workflow) -> WorkflowFile {
        WorkflowFile {
            source: f,
            r#type: WorkflowType::Extensions,
        }
    }

    fn generate_file(&self) -> Result<()> {
        let workflow = (self.source)();
        let workflow_folder = self.r#type.folder_path();
        let workflow_name = workflow
            .name
            .as_ref()
            .expect("Workflow must have a name at this point");
        let filename = format!(
            "{}.yml",
            workflow_name.rsplit("::").next().unwrap_or(workflow_name)
        );

        let workflow_path = workflow_folder.join(filename);

        let content = workflow
            .to_string()
            .map_err(|e| anyhow::anyhow!("{:?}: {:?}", workflow_path, e))?;

        let disclaimer = self.r#type.disclaimer(workflow_name);

        let content = [disclaimer, content].join("\n");
        fs::write(&workflow_path, content).map_err(Into::into)
    }
}

enum WorkflowType {
    Zed,
    Extensions,
}

impl WorkflowType {
    fn disclaimer(&self, workflow_name: &str) -> String {
        format!(
            concat!(
                "# Generated from xtask::workflows::{}{}\n",
                "# Rebuild with `cargo xtask workflows`.",
            ),
            workflow_name,
            matches!(self, WorkflowType::Extensions)
                .then_some(" within the Zed repository.")
                .unwrap_or_default(),
        )
    }

    fn folder_path(&self) -> PathBuf {
        match self {
            WorkflowType::Zed => PathBuf::from(".github/workflows"),
            WorkflowType::Extensions => PathBuf::from("extensions/workflows"),
        }
    }
}

pub fn run_workflows(_: GenerateWorkflowArgs) -> Result<()> {
    if !Path::new("crates/zed/").is_dir() {
        anyhow::bail!("xtask workflows must be ran from the project root");
    }
    let workflow_dir = Path::new(".github/workflows");
    let extension_workflow_dir = Path::new("extensions/workflows");

    let workflows = [
        WorkflowFile::zed(danger::danger),
        WorkflowFile::zed(run_bundling::run_bundling),
        WorkflowFile::zed(release_nightly::release_nightly),
        WorkflowFile::zed(run_tests::run_tests),
        WorkflowFile::zed(release::release),
        WorkflowFile::zed(cherry_pick::cherry_pick),
        WorkflowFile::zed(compare_perf::compare_perf),
        WorkflowFile::zed(run_agent_evals::run_unit_evals),
        WorkflowFile::zed(run_agent_evals::run_cron_unit_evals),
        WorkflowFile::zed(run_agent_evals::run_agent_evals),
        WorkflowFile::zed(after_release::after_release),
        WorkflowFile::zed(extension_tests::extension_tests),
        WorkflowFile::zed(extension_bump::extension_bump),
        WorkflowFile::zed(extension_release::extension_release),
        /* workflows used for CI/CD in extension repositories */
        WorkflowFile::extension(extensions::run_tests::run_tests),
        WorkflowFile::extension(extensions::bump_version::bump_version),
        WorkflowFile::extension(extensions::release_version::release_version),
    ];

    for directory in [&workflow_dir, &extension_workflow_dir] {
        fs::create_dir_all(directory)
            .with_context(|| format!("Failed to create directory: {}", directory.display()))?;
    }

    for workflow_file in workflows {
        workflow_file.generate_file()?;
    }

    Ok(())
}
