use std::path::Path;

use miette::{Context, IntoDiagnostic};

const API_DTS: &str = include_str!("../runtime/api.d.ts");

const CHECK_WORKFLOW: &str = "\
# Do not edit: this file is generated by `ghat generate`.
name: ghat check
on:
  push:
  pull_request:
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: taiki-e/install-action@v2
        with:
          tool: ghat
      - run: ghat generate
      - run: git diff --exit-code
";

const TSCONFIG: &str = r#"{
  "compilerOptions": {
    "strict": true,
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "noEmit": true,
    "skipLibCheck": true
  },
  "include": ["workflows/**/*.ts", "types/**/*.d.ts", "actions/**/*.d.ts"]
}
"#;

pub fn run() -> miette::Result<()> {
    let base = Path::new(super::common::BASE_DIR);

    if base.exists() {
        log::info!("{} already exists", base.display());
    }

    let dirs = [
        base.join("types"),
        base.join("workflows"),
        base.join("actions"),
    ];
    for dir in &dirs {
        std::fs::create_dir_all(dir)
            .into_diagnostic()
            .wrap_err_with(|| format!("failed to create directory: {}", dir.display()))?;
    }

    let api_dts = format!("// Do not edit: this file is generated by `ghat generate`.\n{API_DTS}");
    write_if_changed(&base.join("types/api.d.ts"), &api_dts)?;
    write_if_changed(&base.join("tsconfig.json"), TSCONFIG)?;

    write_if_changed(
        &base.join(".gitattributes"),
        "actions/** linguist-generated\ntypes/** linguist-generated\n",
    )?;

    let lockfile = base.join("ghat.lock");
    if !lockfile.exists() {
        std::fs::write(&lockfile, "")
            .into_diagnostic()
            .wrap_err("failed to create lockfile")?;
        log::info!("created {}", lockfile.display());
    }

    write_check_workflow()?;

    super::style::status("Initialized", format!("ghat project in {}", base.display()));
    Ok(())
}

/// Write the `ghat_check.yaml` workflow to `.github/workflows/`.
pub fn write_check_workflow() -> miette::Result<()> {
    let output_dir = Path::new(".github/workflows");
    std::fs::create_dir_all(output_dir)
        .into_diagnostic()
        .wrap_err("failed to create .github/workflows directory")?;
    write_if_changed(&output_dir.join("ghat_check.yaml"), CHECK_WORKFLOW)
}

/// Write a file only if the contents have changed (or it doesn't exist yet).
fn write_if_changed(path: &Path, contents: &str) -> miette::Result<()> {
    if path.exists() {
        let existing = std::fs::read_to_string(path)
            .into_diagnostic()
            .wrap_err_with(|| format!("failed to read {}", path.display()))?;
        if existing == contents {
            log::debug!("{} is up to date", path.display());
            return Ok(());
        }
        log::info!("updating {}", path.display());
    } else {
        log::info!("creating {}", path.display());
    }

    std::fs::write(path, contents)
        .into_diagnostic()
        .wrap_err_with(|| format!("failed to write {}", path.display()))?;
    Ok(())
}
