#![forbid(unsafe_code)]
//! `.east/` directory, workspace discovery, and state for east.

pub mod error;
mod workspace;

pub use workspace::Workspace;

#[cfg(test)]
mod tests {
    use std::fs;

    use tempfile::TempDir;

    use super::*;

    #[test]
    fn discover_from_workspace_root() {
        let dir = TempDir::new().unwrap();
        fs::create_dir_all(dir.path().join(".east")).unwrap();

        let ws = Workspace::discover(dir.path()).unwrap();
        assert_eq!(ws.root(), dir.path().canonicalize().unwrap());
    }

    #[test]
    fn discover_from_subdirectory() {
        let dir = TempDir::new().unwrap();
        fs::create_dir_all(dir.path().join(".east")).unwrap();
        let sub = dir.path().join("a/b/c");
        fs::create_dir_all(&sub).unwrap();

        let ws = Workspace::discover(&sub).unwrap();
        assert_eq!(ws.root(), dir.path().canonicalize().unwrap());
    }

    #[test]
    fn discover_fails_when_no_east_dir() {
        let dir = TempDir::new().unwrap();
        let err = Workspace::discover(dir.path()).unwrap_err();
        assert!(
            err.to_string().contains("workspace"),
            "error should mention workspace: {err}"
        );
    }

    #[test]
    fn init_creates_east_directory() {
        let dir = TempDir::new().unwrap();
        assert!(!dir.path().join(".east").exists());

        Workspace::init(dir.path()).unwrap();

        assert!(dir.path().join(".east").exists());
        assert!(dir.path().join(".east").is_dir());
    }

    #[test]
    fn init_is_idempotent() {
        let dir = TempDir::new().unwrap();
        Workspace::init(dir.path()).unwrap();
        Workspace::init(dir.path()).unwrap(); // should not fail
        assert!(dir.path().join(".east").is_dir());
    }

    #[test]
    fn east_dir_path() {
        let dir = TempDir::new().unwrap();
        fs::create_dir_all(dir.path().join(".east")).unwrap();

        let ws = Workspace::discover(dir.path()).unwrap();
        assert_eq!(
            ws.east_dir(),
            dir.path().canonicalize().unwrap().join(".east")
        );
    }

    #[test]
    fn manifest_path() {
        let dir = TempDir::new().unwrap();
        fs::create_dir_all(dir.path().join(".east")).unwrap();

        let ws = Workspace::discover(dir.path()).unwrap();
        assert_eq!(
            ws.manifest_path(),
            dir.path().canonicalize().unwrap().join("east.yml")
        );
    }

    // ── Phase 2.6: new workspace loading with manifest config ───────

    #[test]
    fn workspace_manifest_repo_path() {
        let dir = TempDir::new().unwrap();
        fs::create_dir_all(dir.path().join(".east")).unwrap();

        // Write config with [manifest] section
        let config_content = "[manifest]\npath = \"my-app\"\nfile = \"east.yml\"\n";
        fs::write(dir.path().join(".east/config.toml"), config_content).unwrap();

        // Create the manifest repo dir and file
        fs::create_dir_all(dir.path().join("my-app")).unwrap();
        fs::write(dir.path().join("my-app/east.yml"), "version: 1\n").unwrap();

        let ws = Workspace::discover(dir.path()).unwrap();
        assert_eq!(
            ws.manifest_repo_path(),
            dir.path().canonicalize().unwrap().join("my-app")
        );
    }

    #[test]
    fn workspace_manifest_file_path() {
        let dir = TempDir::new().unwrap();
        fs::create_dir_all(dir.path().join(".east")).unwrap();

        let config_content = "[manifest]\npath = \"sdk\"\nfile = \"east.yml\"\n";
        fs::write(dir.path().join(".east/config.toml"), config_content).unwrap();

        fs::create_dir_all(dir.path().join("sdk")).unwrap();
        fs::write(dir.path().join("sdk/east.yml"), "version: 1\n").unwrap();

        let ws = Workspace::discover(dir.path()).unwrap();
        assert_eq!(
            ws.manifest_file_path(),
            dir.path().canonicalize().unwrap().join("sdk/east.yml")
        );
    }

    #[test]
    fn workspace_without_manifest_config_falls_back() {
        let dir = TempDir::new().unwrap();
        fs::create_dir_all(dir.path().join(".east")).unwrap();
        // Config exists but no [manifest] section — legacy workspace
        fs::write(
            dir.path().join(".east/config.toml"),
            "[user]\nname = \"test\"\n",
        )
        .unwrap();

        let ws = Workspace::discover(dir.path()).unwrap();
        // manifest_repo_path falls back to workspace root when no config
        assert_eq!(ws.manifest_repo_path(), ws.root());
        // Legacy manifest_path() returns root/east.yml
        assert_eq!(ws.manifest_path(), ws.root().join("east.yml"));
    }

    #[test]
    fn workspace_discover_from_inside_manifest_repo() {
        // Discovery from <workspace>/manifest-repo/src/deep/ must find .east/ at <workspace>/
        let dir = TempDir::new().unwrap();
        fs::create_dir_all(dir.path().join(".east")).unwrap();
        fs::create_dir_all(dir.path().join("my-app/.git")).unwrap();
        fs::create_dir_all(dir.path().join("my-app/src/deep")).unwrap();

        let config_content = "[manifest]\npath = \"my-app\"\nfile = \"east.yml\"\n";
        fs::write(dir.path().join(".east/config.toml"), config_content).unwrap();
        fs::write(dir.path().join("my-app/east.yml"), "version: 1\n").unwrap();

        // Discover from deep inside manifest repo
        let ws = Workspace::discover(&dir.path().join("my-app/src/deep")).unwrap();
        assert_eq!(ws.root(), dir.path().canonicalize().unwrap());
    }
}
